#region MigraDoc - Creating Documents on the Fly
//
// Authors:
//   Klaus Potzesny
//
// Copyright (c) 2001-2017 empira Software GmbH, Cologne Area (Germany)
//
// http://www.pdfsharp.com
// http://www.migradoc.com
// http://sourceforge.net/projects/pdfsharp
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using MigraDoc.DocumentObjectModel;
using PdfSharp.Pdf;
using PdfSharp.Drawing;
using MigraDoc.DocumentObjectModel.Visitors;
using MigraDoc.DocumentObjectModel.Shapes;
using MigraDoc.DocumentObjectModel.Tables;
using MigraDoc.Rendering.Resources;

namespace MigraDoc.Rendering
{
    /// <summary>
    /// Provides methods to render the document or single parts of it to a XGraphics object.
    /// </summary>
    /// <remarks>
    /// One prepared instance of this class can serve to render several output formats.
    /// </remarks>
    public class DocumentRenderer
    {
        /// <summary>
        /// Initializes a new instance of the DocumentRenderer class.
        /// </summary>
        /// <param name="document">The migradoc document to render.</param>
        public DocumentRenderer(Document document)
        {
            _document = document;
        }

        /// <summary>
        /// Prepares this instance for rendering.
        /// </summary>
        public void PrepareDocument()
        {
            PdfFlattenVisitor visitor = new PdfFlattenVisitor();
            visitor.Visit(_document);
            _previousListNumbers = new Dictionary<ListType, int>(3);
            _previousListNumbers[ListType.NumberList1] = 0;
            _previousListNumbers[ListType.NumberList2] = 0;
            _previousListNumbers[ListType.NumberList3] = 0;
            _formattedDocument = new FormattedDocument(_document, this);
            //REM: Size should not be necessary in this case.
#if true
            XGraphics gfx = XGraphics.CreateMeasureContext(new XSize(2000, 2000), XGraphicsUnit.Point, XPageDirection.Downwards);
#else
#if GDI
      XGraphics gfx = XGraphics.FromGraphics(Graphics.FromHwnd(IntPtr.Zero), new XSize(2000, 2000));
#endif
#if WPF
      XGraphics gfx = XGraphics.FromDrawingContext(null, new XSize(2000, 2000), XGraphicsUnit.Point);
#endif
#endif
            //      _previousListNumber = int.MinValue;
            //gfx.MUH = _unicode;
            //gfx.MFEH = _fontEmbedding;

            _previousListInfo = null;
            _formattedDocument.Format(gfx);
        }

        /// <summary>
        /// Occurs while the document is being prepared (can be used to show a progress bar).
        /// </summary>
        public event PrepareDocumentProgressEventHandler PrepareDocumentProgress;

        /// <summary>
        /// Allows applications to display a progress indicator while PrepareDocument() is being executed.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="maximum"></param>
        public virtual void OnPrepareDocumentProgress(int value, int maximum)
        {
            if (PrepareDocumentProgress != null)
            {
                // Invokes the delegates. 
                PrepareDocumentProgressEventArgs e = new PrepareDocumentProgressEventArgs(value, maximum);
                PrepareDocumentProgress(this, e);
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance supports PrepareDocumentProgress.
        /// </summary>
        public bool HasPrepareDocumentProgress
        {
            get { return PrepareDocumentProgress != null; }
        }

        /// <summary>
        /// Gets the formatted document of this instance.
        /// </summary>
        public FormattedDocument FormattedDocument
        {
            get { return _formattedDocument; }
        }
        FormattedDocument _formattedDocument;

        /// <summary>
        /// Renders a MigraDoc document to the specified graphics object.
        /// </summary>
        public void RenderPage(XGraphics gfx, int page)
        {
            RenderPage(gfx, page, PageRenderOptions.All);
        }

        /// <summary>
        /// Renders a MigraDoc document to the specified graphics object.
        /// </summary>
        public void RenderPage(XGraphics gfx, int page, PageRenderOptions options)
        {
            if (_formattedDocument.IsEmptyPage(page))
                return;

            FieldInfos fieldInfos = _formattedDocument.GetFieldInfos(page);

            fieldInfos.Date = _printDate != DateTime.MinValue ? _printDate : DateTime.Now;

            if ((options & PageRenderOptions.RenderHeader) == PageRenderOptions.RenderHeader)
                RenderHeader(gfx, page);
            if ((options & PageRenderOptions.RenderFooter) == PageRenderOptions.RenderFooter)
                RenderFooter(gfx, page);

            if ((options & PageRenderOptions.RenderContent) == PageRenderOptions.RenderContent)
            {
                RenderInfo[] renderInfos = _formattedDocument.GetRenderInfos(page);
                //foreach (RenderInfo renderInfo in renderInfos)
                int count = renderInfos.Length;
                for (int idx = 0; idx < count; idx++)
                {
                    RenderInfo renderInfo = renderInfos[idx];
                    Renderer renderer = Renderer.Create(gfx, this, renderInfo, fieldInfos);
                    renderer.Render();
                }
            }
        }

        /// <summary>
        /// Gets the document objects that get rendered on the specified page.
        /// </summary>
        public DocumentObject[] GetDocumentObjectsFromPage(int page)
        {
            RenderInfo[] renderInfos = _formattedDocument.GetRenderInfos(page);
            int count = renderInfos != null ? renderInfos.Length : 0;
            DocumentObject[] documentObjects = new DocumentObject[count];
            for (int idx = 0; idx < count; idx++)
                documentObjects[idx] = renderInfos[idx].DocumentObject;
            return documentObjects;
        }

        /// <summary>
        /// Gets the render information for document objects that get rendered on the specified page.
        /// </summary>
        public RenderInfo[] GetRenderInfoFromPage(int page)
        {
            return _formattedDocument.GetRenderInfos(page);
        }

        /// <summary>
        /// Renders a single object to the specified graphics object at the given point.
        /// </summary>
        /// <param name="graphics">The graphics object to render on.</param>
        /// <param name="xPosition">The left position of the rendered object.</param>
        /// <param name="yPosition">The top position of the rendered object.</param>
        /// <param name="width">The width.</param>
        /// <param name="documentObject">The document object to render. Can be paragraph, table, or shape.</param>
        /// <remarks>This function is still in an experimental state.</remarks>
        public void RenderObject(XGraphics graphics, XUnit xPosition, XUnit yPosition, XUnit width, DocumentObject documentObject)
        {
            if (graphics == null)
                throw new ArgumentNullException("graphics");

            if (documentObject == null)
                throw new ArgumentNullException("documentObject");

            if (!(documentObject is Shape) && !(documentObject is Table) &&
                !(documentObject is Paragraph))
                throw new ArgumentException(Messages2.ObjectNotRenderable, "documentObject");

            Renderer renderer = Renderer.Create(graphics, this, documentObject, null);
            renderer.Format(new Rectangle(xPosition, yPosition, width, double.MaxValue), null);

            RenderInfo renderInfo = renderer.RenderInfo;
            renderInfo.LayoutInfo.ContentArea.X = xPosition;
            renderInfo.LayoutInfo.ContentArea.Y = yPosition;

            renderer = Renderer.Create(graphics, this, renderer.RenderInfo, null);
            renderer.Render();
        }

        /// <summary>
        /// Gets or sets the working directory for rendering.
        /// </summary>
        public string WorkingDirectory
        {
            get { return _workingDirectory; }
            set { _workingDirectory = value; }
        }
        string _workingDirectory;

        private void RenderHeader(XGraphics graphics, int page)
        {
            FormattedHeaderFooter formattedHeader = _formattedDocument.GetFormattedHeader(page);
            if (formattedHeader == null)
                return;

            Rectangle headerArea = _formattedDocument.GetHeaderArea(page);
            RenderInfo[] renderInfos = formattedHeader.GetRenderInfos();
            FieldInfos fieldInfos = _formattedDocument.GetFieldInfos(page);
            foreach (RenderInfo renderInfo in renderInfos)
            {
                Renderer renderer = Renderer.Create(graphics, this, renderInfo, fieldInfos);
                renderer.Render();
            }
        }

        private void RenderFooter(XGraphics graphics, int page)
        {
            FormattedHeaderFooter formattedFooter = _formattedDocument.GetFormattedFooter(page);
            if (formattedFooter == null)
                return;

            Rectangle footerArea = _formattedDocument.GetFooterArea(page);
            RenderInfo[] renderInfos = formattedFooter.GetRenderInfos();
#if true
#if true
            // The footer is bottom-aligned and grows with its contents. topY specifies the Y position where the footer begins.
            XUnit topY = footerArea.Y + footerArea.Height - RenderInfo.GetTotalHeight(renderInfos);
            // Hack: The purpose of "topY" is unclear, but two paragraphs in the footer will use the same topY and will be rendered at the same position.
            // offsetY specifies the offset (amount of movement) for all footer items. It's the difference between topY and the position calculated for the first item.
            XUnit offsetY = 0;
            bool notFirst = false;

            FieldInfos fieldInfos = _formattedDocument.GetFieldInfos(page);
            foreach (RenderInfo renderInfo in renderInfos)
            {
                Renderer renderer = Renderer.Create(graphics, this, renderInfo, fieldInfos);
                if (!notFirst)
                {
                    offsetY = renderer.RenderInfo.LayoutInfo.ContentArea.Y - topY;
                    notFirst = true;
                }
                XUnit savedY = renderer.RenderInfo.LayoutInfo.ContentArea.Y;
                // Apply offsetY only to items that do not have an absolute position.
                if (renderer.RenderInfo.LayoutInfo.Floating != Floating.None)
                    renderer.RenderInfo.LayoutInfo.ContentArea.Y -= offsetY;
                renderer.Render();
                renderer.RenderInfo.LayoutInfo.ContentArea.Y = savedY;
            }
#else
            // TODO Document the purpose of "topY".
            XUnit topY = footerArea.Y + footerArea.Height - RenderInfo.GetTotalHeight(renderInfos);
            // Hack: The purpose of "topY" is unclear, but two paragraphs in the footer will use the same topY and will be rendered at the same position.
            XUnit offsetY = 0;

            FieldInfos fieldInfos = _formattedDocument.GetFieldInfos(page);
            foreach (RenderInfo renderInfo in renderInfos)
            {
                Renderer renderer = Renderer.Create(graphics, this, renderInfo, fieldInfos);
                XUnit savedY = renderer.RenderInfo.LayoutInfo.ContentArea.Y;
                renderer.RenderInfo.LayoutInfo.ContentArea.Y = topY + offsetY;
                renderer.Render();
                renderer.RenderInfo.LayoutInfo.ContentArea.Y = savedY;
                offsetY += renderer.RenderInfo.LayoutInfo.ContentArea.Height;
            }
#endif
#else
            XUnit topY = footerArea.Y + footerArea.Height - RenderInfo.GetTotalHeight(renderInfos);

            FieldInfos fieldInfos = _formattedDocument.GetFieldInfos(page);
            foreach (RenderInfo renderInfo in renderInfos)
            {
                Renderer renderer = Renderer.Create(graphics, this, renderInfo, fieldInfos);
                XUnit savedY = renderer.RenderInfo.LayoutInfo.ContentArea.Y;
                renderer.RenderInfo.LayoutInfo.ContentArea.Y = topY;
                renderer.Render();
                renderer.RenderInfo.LayoutInfo.ContentArea.Y = savedY;
            }
#endif
        }

        public void AddOutline(int level, string title, PdfPage destinationPage)
        {
            if (level < 1 || destinationPage == null)
                return;

            PdfDocument document = destinationPage.Owner;

            if (document == null)
                return;

            PdfOutlineCollection outlines = document.Outlines;
            while (--level > 0)
            {
                int count = outlines.Count;
                if (count == 0)
                {
                    // You cannot add empty bookmarks to PDF. So we use blank here.
                    PdfOutline outline = outlines.Add(" ", destinationPage, true);
                    outlines = outline.Outlines;
                }
                else
                    outlines = outlines[count - 1].Outlines;
            }
            outlines.Add(title, destinationPage, true);
        }

        public int NextListNumber(ListInfo listInfo)
        {
            ListType listType = listInfo.ListType;
            bool isNumberList = listType == ListType.NumberList1 ||
              listType == ListType.NumberList2 ||
              listType == ListType.NumberList3;

            int listNumber = int.MinValue;
            if (listInfo == _previousListInfo)
            {
                if (isNumberList)
                    return _previousListNumbers[listType];
                return listNumber;
            }

            //bool listTypeChanged = _previousListInfo == null || _previousListInfo.ListType != listType;

            if (isNumberList)
            {
                listNumber = 1;
                if (/*!listTypeChanged &&*/ (listInfo._continuePreviousList.IsNull || listInfo.ContinuePreviousList))
                    listNumber = _previousListNumbers[listType] + 1;

                _previousListNumbers[listType] = listNumber;
            }

            _previousListInfo = listInfo;
            return listNumber;
        }
        ListInfo _previousListInfo;
        Dictionary<ListType, int> _previousListNumbers;
        private readonly Document _document;
        public DateTime _printDate = DateTime.MinValue;

        /// <summary>
        /// Arguments for the PrepareDocumentProgressEvent which is called while a document is being prepared (you can use this to display a progress bar).
        /// </summary>
        public class PrepareDocumentProgressEventArgs : EventArgs
        {
            /// <summary>
            /// Indicates the current step reached in document preparation.
            /// </summary>
            public int Value;
            /// <summary>
            /// Indicates the final step in document preparation. The quitient of Value and Maximum can be used to calculate a percentage (e. g. for use in a progress bar).
            /// </summary>
            public int Maximum;

            /// <summary>
            /// Initializes a new instance of the <see cref="PrepareDocumentProgressEventArgs"/> class.
            /// </summary>
            /// <param name="value">The current step in document preparation.</param>
            /// <param name="maximum">The latest step in document preparation.</param>
            public PrepareDocumentProgressEventArgs(int value, int maximum)
            {
                Value = value;
                Maximum = maximum;
            }
        }

        /// <summary>
        /// The event handler that is being called for the PrepareDocumentProgressEvent event.
        /// </summary>
        public delegate void PrepareDocumentProgressEventHandler(object sender, PrepareDocumentProgressEventArgs e);

        public int ProgressMaximum;
        public int ProgressCompleted;
    }
}
