/*
 * Copyright (C) 2013 FooProject
 * * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using Windows.Graphics.Display;
using Windows.Graphics.Printing;
using System.Runtime.InteropServices;
using Windows.UI;
using FooEditEngine.Metro;

namespace FooEditEngine
{
    /// <summary>
    /// イベントデータ
    /// </summary>
    public sealed class ParseCommandEventArgs
    {
        /// <summary>
        /// 印刷中のページ番号
        /// </summary>
        public int PageNumber;
        /// <summary>
        /// ページ範囲内で許容されている最大の番号
        /// </summary>
        public int MaxPageNumber;
        /// <summary>
        /// 処理前の文字列
        /// </summary>
        public string Original;
        /// <summary>
        /// コンストラクター
        /// </summary>
        /// <param name="nowPage">印刷中のページ番号</param>
        /// <param name="maxPage">印刷すべき最大のページ番号</param>
        /// <param name="org">処理前の文字列</param>
        public ParseCommandEventArgs(int nowPage, int maxPage, string org)
        {
            this.PageNumber = nowPage;
            this.MaxPageNumber = maxPage;
            this.Original = org;
        }
    }

    /// <summary>
    /// コマンド処理用デリゲート
    /// </summary>
    /// <param name="sender">送信元のクラス</param>
    /// <param name="e">イベントデータ</param>
    /// <returns>処理後の文字列</returns>
    public delegate string ParseCommandHandler(object sender, ParseCommandEventArgs e);


    sealed class PrintableViewFactory
    {
        Windows.UI.Color foreground, comment, keyword1, keyword2, literal, url;
        string fontName;
        double fontSize;
        LineBreakMethod lineBreakMethod;
        int lineBreakCharCount, tabCount;
        Document document;
        bool urlMark;
        IHilighter hilighter;
        float displayDpi;
        Padding padding;
        public PrintableViewFactory(FooTextBox textbox,Padding padding)
        {
            this.foreground = textbox.Foreground;
            this.comment = textbox.Comment;
            this.keyword1 = textbox.Keyword1;
            this.keyword2 = textbox.Keyword2;
            this.literal = textbox.Literal;
            this.url = textbox.URL;
            this.fontName = textbox.FontFamily.Source;
            this.fontSize = textbox.FontSize;
            this.tabCount = textbox.TabChars;
            this.document = textbox.Document;
            this.lineBreakMethod = textbox.LineBreakMethod;
            this.lineBreakCharCount = textbox.LineBreakCharCount;
            this.drawLineNumber = textbox.DrawLineNumber;
            this.urlMark = textbox.MarkURL;
            this.hilighter = textbox.Hilighter;
            this.displayDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
            this.padding = padding;
            this.EnableHilight = true;
        }
        public D2DPrintRender CreateRender(PrintPageDescription pagedesc, IPrintDocumentPackageTarget docPackageTarget)
        {
            D2DPrintRender render;
            Size size = new Size(pagedesc.ImageableRect.Width, pagedesc.ImageableRect.Height);
            render = new D2DPrintRender(this.fontName, this.fontSize, size, Math.Min(pagedesc.DpiX, pagedesc.DpiY), docPackageTarget);
            render.Foreground = D2DRenderBase.ToColor4(this.foreground);
            render.Comment = D2DRenderBase.ToColor4(this.comment);
            render.Keyword1 = D2DRenderBase.ToColor4(this.keyword1);
            render.Keyword2 = D2DRenderBase.ToColor4(this.keyword2);
            render.Literal = D2DRenderBase.ToColor4(this.literal);
            render.Url = D2DRenderBase.ToColor4(this.url);
            return render;
        }
        public D2DPrintPreviewRender CreateRender(PrintPageDescription pagedesc)
        {
            D2DPrintPreviewRender render;
            Size size = new Size(pagedesc.ImageableRect.Width, pagedesc.ImageableRect.Height);
            render = new D2DPrintPreviewRender(this.fontName, this.fontSize, size, this.displayDpi);
            render.Foreground = D2DRenderBase.ToColor4(this.foreground);
            render.Comment = D2DRenderBase.ToColor4(this.comment);
            render.Keyword1 = D2DRenderBase.ToColor4(this.keyword1);
            render.Keyword2 = D2DRenderBase.ToColor4(this.keyword2);
            render.Literal = D2DRenderBase.ToColor4(this.literal);
            render.Url = D2DRenderBase.ToColor4(this.url);
            return render;
        }
        public PrintableView CreateView(PrintPageDescription pagedesc, IPrintableTextRender render, string header, string footer)
        {
            Document documentSnap = new Document(this.document);
            documentSnap.LayoutLines.Render = render;
            PrintableView view = new PrintableView(documentSnap, render,padding);
            view.TabStops = this.tabCount;
            view.LineBreak = this.lineBreakMethod == LineBreakMethod.None ? LineBreakMethod.PageBound : this.lineBreakMethod;
            view.LineBreakCharCount = this.lineBreakCharCount;
            view.DrawLineNumber = this.drawLineNumber;
            view.Header = header;
            view.Footer = footer;
            view.UrlMark = this.urlMark;
            view.PageBound = new Rectangle(pagedesc.ImageableRect.X, pagedesc.ImageableRect.Y, pagedesc.ImageableRect.Width, pagedesc.ImageableRect.Height);
            view.Hilighter = this.EnableHilight ? this.hilighter : null;
            view.PerfomLayouts();

            return view;
        }

        public bool drawLineNumber
        {
            get;
            set;
        }

        public bool EnableHilight
        {
            get;
            set;
        }
    }

    sealed class DocumentSource : IPrintDocumentPageSource, IPrintPreviewPageCollection, IPrintDocumentSource,IPrintPreviewSource
    {
        IPrintPreviewDxgiPackageTarget dxgiPreviewTarget;
        bool paginateCalled = false;
        Size imageRect;
        PrintableViewFactory factory;
        D2DPrintPreviewRender previewRender;
        PrintableView previewView;
        int maxPreviePageCount;

        public ParseCommandHandler ParseHF;
        public string Header = string.Empty;
        public string Fotter = string.Empty;

        public DocumentSource(FooTextBox textbox,Padding padding)
        {
            this.factory = new PrintableViewFactory(textbox, padding);
        }

        internal enum SyntaxHilightApplibility
        {
            Apply,
            NoApply,
        }

        [DisplayPrintOptionResourceID("SyntaxHilight")]
        internal SyntaxHilightApplibility EnableHilight
        {
            get
            {
                return this.factory.EnableHilight ? SyntaxHilightApplibility.Apply : SyntaxHilightApplibility.NoApply;
            }
            set
            {
                this.factory.EnableHilight = value == SyntaxHilightApplibility.Apply;
            }
        }

        internal enum LineNumberVisiblity
        {
            Visible,
            Hidden
        }

        [DisplayPrintOptionResourceID("ShowLineNumber")]
        internal LineNumberVisiblity ShowLineNumber
        {
            get
            {
                return this.factory.drawLineNumber ? LineNumberVisiblity.Visible : LineNumberVisiblity.Hidden;
            }
            set
            {
                this.factory.drawLineNumber = value == LineNumberVisiblity.Visible;
            }
        }

        public void GetPreviewPageCollection(IPrintDocumentPackageTarget docPackageTarget, out IPrintPreviewPageCollection docPageCollection)
        {
            Guid guid = new Guid(PreviewPackageIds.IID_PREVIEWPACKAGETARGET_DXGI);
            IntPtr target;
            docPackageTarget.GetPackageTarget(guid, guid, out target);
            this.dxgiPreviewTarget = (IPrintPreviewDxgiPackageTarget)Marshal.GetObjectForIUnknown(target);
            docPageCollection = (IPrintPreviewPageCollection)this;
        }

        public void MakeDocument(object printTaskOptions, IPrintDocumentPackageTarget docPackageTarget)
        {
            PrintTaskOptions options = (PrintTaskOptions)printTaskOptions;
            PrintPageDescription pagedesc = options.GetPageDescription(1);

            D2DPrintRender render = this.factory.CreateRender(pagedesc, docPackageTarget);
            PrintableView view = this.factory.CreateView(pagedesc, render, this.Header, this.Fotter);

            bool result = false;
            int currentPage = 0;

            while (!result)
            {
                if(!string.IsNullOrEmpty(this.Header))
                    view.Header = this.ParseHF(this, new ParseCommandEventArgs(currentPage, this.maxPreviePageCount, this.Header));
                if (!string.IsNullOrEmpty(this.Fotter))
                    view.Footer = this.ParseHF(this, new ParseCommandEventArgs(currentPage, this.maxPreviePageCount, this.Fotter));

                render.BegineDraw();
                view.Draw(view.PageBound);
                render.EndDraw();
                result = view.TryPageDown();
                currentPage++;
            }

            render.Dispose();
            view.Dispose();
        }

        public void Paginate(uint currentJobPage, object printTaskOptions)
        {
            PrintTaskOptions options = (PrintTaskOptions)printTaskOptions;
            PrintPageDescription pagedesc = options.GetPageDescription(currentJobPage);

            this.imageRect = new Size(pagedesc.ImageableRect.Width, pagedesc.ImageableRect.Height);

            this.previewRender = this.factory.CreateRender(pagedesc);
            this.previewView = this.factory.CreateView(pagedesc, this.previewRender, this.Header, this.Fotter);

            int maxPage = 1;
            while (!this.previewView.TryPageDown())
                maxPage++;

            this.dxgiPreviewTarget.SetJobPageCount(PageCountType.FinalPageCount, (uint)maxPage);

            this.dxgiPreviewTarget.InvalidatePreview();

            this.maxPreviePageCount = maxPage;

            this.paginateCalled = true;
        }

        public void InvalidatePreview()
        {
            this.dxgiPreviewTarget.InvalidatePreview();
        }

        public void MakePage(uint desiredJobPage, float width, float height)
        {
            if (width <= 0 || height <= 0)
                throw new COMException("", 0x70057/*E_INVALIDARG*/);
            if (!this.paginateCalled)
                return;
            if (desiredJobPage == 0xFFFFFFFF)
                desiredJobPage = 1;

            this.previewView.TryScroll(0, 0);   //元に戻さないとページ番号が変わった時に正しく動作しない

            for (int i = 1; i < desiredJobPage; i++)
                this.previewView.TryPageDown();

            if (!string.IsNullOrEmpty(this.Header))
                this.previewView.Header = this.ParseHF(this, new ParseCommandEventArgs((int)desiredJobPage, this.maxPreviePageCount, this.Header));
            if (!string.IsNullOrEmpty(this.Fotter))
                this.previewView.Footer = this.ParseHF(this, new ParseCommandEventArgs((int)desiredJobPage, this.maxPreviePageCount, this.Fotter));

            this.previewRender.Resize(width, height);
            this.previewRender.SetScale((float)(this.previewView.PageBound.Width / width)); //BeginDraw()で倍率が1に戻る

            this.previewRender.BegineDraw();
            this.previewView.Draw(this.previewView.PageBound);
            this.previewRender.EndDraw(this.dxgiPreviewTarget,desiredJobPage);
        }

        public void Dispose()
        {
            if (this.previewView != null)
                this.previewView.Dispose();
            if (this.previewRender != null)
                this.previewRender.Dispose();
        }
    }
}
