using System;
using System.IO;
using System.Windows; 
using System.Windows.Documents; 
using System.Windows.Media; 
using System.IO.Packaging; 
using System.Windows.Xps.Packaging; 
using System.Windows.Xps.Serialization; 
using System.Windows.Markup; 
using System.Windows.Controls;

using pdftron;
using pdftron.Common;
using pdftron.SDF;
using pdftron.PDF;
using System.Drawing;

namespace pdftron
{
    namespace Xaml
    {
        /// <summary>
        /// pdftron.Xaml.Convert can be used to to convert .xaml files into PDF documents
        /// with control over headers and footers, main body placement,
        /// and column widths.  Pagination is controlled by specifying the page size and 
        /// body size (margins) and all pages are appended to a PDFDoc which can then be
        /// further manipulated using the PDFNet API.
        /// 
        /// Three types of XAML objects are convertible using pdftron.Xaml.Convert:
        /// - FlowDocument's which describe content that is reflowable from page to page
        /// - FixedDocument's which describe content that has been placed on a fixed page
        /// - Blocks such as Canvas, RichTextBox, Section etc which can be wrapped or inserted 
        ///   directly into a FlowDocument
        ///
        /// Limitations:
        /// There are many Xaml classes that cannot be added to FlowDocument or FixedDocuments and
        /// therefore this sample converter cannot convert them.  Examples include 
        /// Page, Window or Frame objects.
        /// </summary>
        class Convert
        {
            /// <summary>
            /// Convert the specified .xaml file to PDF and append the results to pdfdoc
            /// </summary>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="inputXamlFile">Path to .xaml file</param>
            public static void ToPdf(PDFDoc pdfdoc, string inputXamlFile)
            {
                ToPdf(pdfdoc, inputXamlFile, new ConverterOptions());
            }

            /// <summary>
            /// Convert the specified .xaml file to PDF and append the results to pdfdoc
            /// </summary>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="inputXamlFile">Path to .xaml file</param>
            /// <param name="options">specification of conversion options</param>
            public static void ToPdf(PDFDoc pdfdoc, string inputXamlFile, ConverterOptions options)
            {
                FileInfo fileInfo = new FileInfo(inputXamlFile);

                using (FileStream fileStream = fileInfo.OpenRead())
                {
                    // define the parserContext to include the typical xaml namespace 
                    System.Windows.Markup.ParserContext parserContext = new System.Windows.Markup.ParserContext();
                    parserContext.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
                    parserContext.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
                    parserContext.BaseUri = new Uri(fileInfo.FullName, UriKind.RelativeOrAbsolute);
                    ToPdf(pdfdoc, fileStream, parserContext, options);
                }
            }
            
            /// <summary>
            /// Convert the xaml file in the input stream to PDF and append the results to pdfdoc
            /// </summary>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="inputXamlStream">The input stream containing the xaml</param>
            /// <param name="parserContext">context defining xaml namespaces</param>
            /// <param name="options">specification of conversion options</param> 
            public static void ToPdf(PDFDoc pdfdoc, Stream inputXamlStream, System.Windows.Markup.ParserContext parserContext)
            {
                ToPdf(pdfdoc, inputXamlStream, new ConverterOptions());
            }

            /// <summary>
            /// Convert the xaml file in the input stream to PDF and append the results to pdfdoc
            /// </summary>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="inputXamlStream">The input stream containing the xaml</param>
            /// <param name="parserContext">context defining xaml namespaces</param>
            /// <param name="options">specification of conversion options</param>
            public static void ToPdf(PDFDoc pdfdoc, Stream inputXamlStream, System.Windows.Markup.ParserContext parserContext, ConverterOptions options)
            {
                object doc = System.Windows.Markup.XamlReader.Load(inputXamlStream, parserContext);
                FlowDocument flowDoc = doc as FlowDocument;
                FixedDocument fixedDoc = doc as FixedDocument;

                if( fixedDoc != null)
                {
                    Console.WriteLine("Input document is a FixedDocument, it will not reflow.");
                }
                if( flowDoc != null )
                {
                    Console.WriteLine("Input document is a FlowDocument, it will reflow.");
                }

                ToPdf(pdfdoc, doc, options);
            }

            /// <summary>
            /// Convert the object specified by doc to PDF
            /// </summary>
            /// <remarks>doc must implement IDocumentPaginatorSource.  
            /// doc can be a FlowDocument as object or FixedDocument as object.
            /// doc can also be a UIElement such as Canvas.
            /// doc cannot be a Page, Window or Frame.</remarks>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="doc">The object containing a Xaml document</param>
            public static void ToPdf(PDFDoc pdfdoc, object doc)
            {
                ToPdf(pdfdoc, doc, new ConverterOptions());
            }

            /// <summary>
            /// Convert the object specified by doc to PDF
            /// </summary>
            /// <remarks>doc must implement IDocumentPaginatorSource.  
            /// doc can be a FlowDocument as object or FixedDocument as object.
            /// doc can also be a UIElement such as Canvas.
            /// doc cannot be a Page, Window or Frame.</remarks>
            /// <param name="pdfdoc">The PDFDoc to append the converted Xaml to</param>
            /// <param name="doc">The object containing a Xaml document</param>
            /// <param name="options">specification of conversion options</param>
            public static void ToPdf(PDFDoc pdfdoc, object doc, ConverterOptions options) 
            {
                FlowDocument flowDoc = null;
                try 
                {
                    if (!(doc is IDocumentPaginatorSource))
                    {
                        if (doc is UIElement 
                            && !(doc is System.Windows.Controls.Page)
                            && !(doc is System.Windows.Window)
                            && !(doc is System.Windows.Controls.Frame))
                        {
                            // Try to wrap the object to create FlowDocument(Paragraph(Floater(BlockUIContainer(object))))
                            BlockUIContainer container = new BlockUIContainer();
                            container.Child = (UIElement)doc;
                            Floater floater = new Floater(container);
                            Paragraph paragraph = new Paragraph();
                            paragraph.Inlines.Add(floater);
                            flowDoc = new FlowDocument();
                            flowDoc.Blocks.Add(paragraph);
                            flowDoc.ColumnWidth = options.BodyRect.Width();

                            ToPdf(pdfdoc, flowDoc, options);
                        }
                        else if( doc is System.Windows.Documents.Block )
                        {
                            flowDoc = new FlowDocument();
                            flowDoc.Blocks.Add((System.Windows.Documents.Block)doc);
                            ToPdf(pdfdoc, flowDoc, options);
                        }
                        else
                        {
                            throw new Exception("Cannot wrap Xaml object of type " + doc + " in a FlowDocument");
                        }
                    }
                    else
                    {
                        string tempFilename = System.IO.Path.GetTempFileName();
                        try
                        {
                            using (Package container = Package.Open(tempFilename, FileMode.Create))
                            {
                                using (XpsDocument xpsDoc = new XpsDocument(container, CompressionOption.Maximum))
                                {
                                    flowDoc = doc as FlowDocument;
                                    if (options.NumColumns > 0 && flowDoc != null)
                                    {
                                        flowDoc.ColumnWidth = options.BodyRect.Width() / options.NumColumns;
                                        doc = flowDoc;
                                    }
                                    XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);
                                    DocumentPaginator paginator = ((IDocumentPaginatorSource)doc).DocumentPaginator;

                                    paginator = new DocumentPaginatorWrapper(paginator, options);

                                    // saving the document performs pagination, addition of elements in 
                                    // DocumentPaginatorWrapper::GetPage and writes the .xps file
                                    rsm.SaveAsXaml(paginator);
                                }
                            }
                            // now convert from .xps to pdf.
                            pdftron.PDF.Convert.FromXps(pdfdoc, tempFilename);
                        }
                        catch(PDFNetException e)
                        {
                            throw (e);
                        }
                        finally
                        {
                            System.IO.File.Delete(tempFilename);
                        }
                    }
                }
                finally
                {
                    flowDoc = null;
                }
            }

            public static void ToPdf(PDFDoc pdfdoc, DocumentPaginator paginator)
            {
                ToPdf( pdfdoc, paginator, new ConverterOptions() );
            }
            public static void ToPdf(PDFDoc pdfdoc, DocumentPaginator in_paginator, ConverterOptions options)
            {
                string tempFilename = System.IO.Path.GetTempFileName();
                try
                {
                    using (Package container = Package.Open(tempFilename, FileMode.Create))
                    {
                        using (XpsDocument xpsDoc = new XpsDocument(container, CompressionOption.Maximum))
                        {
                            XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDoc), false);
                            DocumentPaginator paginator = new DocumentPaginatorWrapper(in_paginator, options);

                            // saving the document performs pagination, addition of elements in 
                            // DocumentPaginatorWrapper::GetPage and writes the .xps file
                            rsm.SaveAsXaml(paginator);
                        }
                    }
                    // now convert from .xps to pdf.
                    pdftron.PDF.Convert.FromXps(pdfdoc, tempFilename);
                }
                catch (PDFNetException e)
                {
                    throw (e);
                }
                finally
                {
                    System.IO.File.Delete(tempFilename);
                }
            }
        }

        class ConverterOptions
        {
            /// <summary>
            /// Delegates for drawing headers and footers.
            /// </summary>
            /// <remarks>All resources that you use in the header and footer delegates 
            /// must persist until the document is serialized.</remarks>
            /// <param name="context">This is the drawing context that should be used</param>
            /// <param name="bounds">The bounds of the header with (x1, y1) upper left corner to (x2, y2)</param>
            /// <param name="bodyBounds">The bounds of the body area with (x1, y1) upper left corner to (x2, y2)</param>
            /// <param name="pageSize">The size of the page in pixels at 96 dpi</param>
            /// <param name="pageNumber">The page number is 0 based</param>
            public delegate void DrawHeaderFooter(DrawingContext context,
                                                  pdftron.PDF.Rect bounds,
                                                  pdftron.PDF.Rect bodyBounds,
                                                  System.Windows.Size pageSize,
                                                  int pageNumber);
            /// <summary>
            /// The page size in 96 dpi units (as used in XPS)
            /// </summary>
            private System.Windows.Size m_PageSize;

            /// <summary>
            /// The Rectangle bounding the body area for the content.  FlowDocuments will reflow
            /// their content to fit within this bounding area as best they can.  FixedDocuments
            /// may reflow.  Relative to PageSize, origin is at upper left (x1, y1), and 
            /// increases down to right.
            /// </summary>
            private pdftron.PDF.Rect m_BodyRect;  

            /// <summary>
            /// The Rectangle bounding the Header area, typically above the body area.
            /// Relative to the PageSize, origin is at upper left (x1, y1), and increases 
            /// down to right.
            /// </summary>
            private pdftron.PDF.Rect m_HeaderRect;

            /// <summary>
            /// The Rectangle bounding the Footer area, typically below the body area.
            /// Relative to the PageSize, origin is at the upper left (x1, y1), and increases 
            /// down to right.
            /// </summary>
            private pdftron.PDF.Rect m_FooterRect;

            /// <summary>
            /// Number of columns for FlowDocument layout as an override for any defined
            /// column width in the FlowDocument. If NumColumns == 0, then the default
            /// FlowDocument column widths are used, otherwise BodyRect.Width()/NumColumns 
            /// is used.  See the example CreateXamlTableAndConvert.
            /// </summary>
            private int m_NumColumns; 

            /// <summary>
            /// Store for the Header drawing function.  Drawn before the body is drawn.
            /// </summary>
            private DrawHeaderFooter m_HeaderFunc;

            /// <summary>
            /// Store for the Footer drawing function. Drawn after the header, and before
            /// the body is drawn.
            /// </summary>
            private DrawHeaderFooter m_FooterFunc;

            /// <summary>
            /// The page size in 96 dpi units (as used in XPS)
            /// </summary>
            public System.Windows.Size PageSize
            {
                get { return m_PageSize; }
                set { m_PageSize = value; }
            }

            /// <summary>
            /// The Rectangle bounding the body area for the content.  FlowDocuments will reflow
            /// their content to fit within this bounding area as best they can.  FixedDocuments
            /// may reflow.  Relative to PageSize, origin is at upper left (x1, y1), and 
            /// increases down to right.
            /// </summary>
            public pdftron.PDF.Rect BodyRect
            {
                get { return m_BodyRect; }
                set { m_BodyRect = value; }
            }

            /// <summary>
            /// The Rectangle bounding the Header area, typically above the body area.
            /// Relative to the PageSize, origin is at upper left (x1, y1), and increases 
            /// down to right.
            /// </summary>
            public pdftron.PDF.Rect HeaderRect
            {
                get { return m_HeaderRect; }
                set { m_HeaderRect = value; }
            }

            /// <summary>
            /// The Rectangle bounding the Footer area, typically below the body area.
            /// Relative to the PageSize, origin is at the upper left (x1, y1), and increases 
            /// down to right.
            /// </summary>            
            public pdftron.PDF.Rect FooterRect
            {
                get { return m_FooterRect; }
                set { m_FooterRect = value; }
            }

            /// <summary>
            /// Number of columns for FlowDocument layout as an override for any defined
            /// column width in the FlowDocument. If NumColumns == 0, then the default
            /// FlowDocument column widths are used, otherwise BodyRect.Width()/NumColumns 
            /// is used.  See the example CreateXamlTableAndConvert.
            /// </summary>
            public int NumColumns
            {
                get { return m_NumColumns; }
                set { m_NumColumns = value; }
            }

            /// <summary>
            /// Store for the Header drawing function.  Drawn before the body is drawn.
            /// </summary>
            public DrawHeaderFooter HeaderFunc
            {
                get { return m_HeaderFunc; }
                set { m_HeaderFunc = value; }
            }

            /// <summary>
            /// Store for the Footer drawing function. Drawn after the header, and before
            /// the body is drawn.
            /// </summary>
            public DrawHeaderFooter FooterFunc
            {
                get { return m_FooterFunc; }
                set { m_FooterFunc = value; }
            }

            /// <summary>
            /// Default constructor.  Sets the page size to (8.5 x 11), and margins to 1/2 inch.
            /// Number of columns to 0 (don't override input) and no header and footer functions.
            /// </summary>
            public ConverterOptions()
            {
                // in the world of xaml and xps, there are 96 units per inch.
                // here we create a 8.5 x 11 inch page
                m_PageSize = new System.Windows.Size(8.5 * 96, 11 * 96);

                // the body portion of the page is a 1/2 inch inside the page boundary
                // coordinates are in XAML space, so origin in is upper left
                m_BodyRect = new pdftron.PDF.Rect(96 / 2, 96 / 2, m_PageSize.Width - 96/2, m_PageSize.Height - 96/2);

                // the bounds of the header (top of page)
                m_HeaderRect = new pdftron.PDF.Rect(0, 0, m_PageSize.Width, 96 / 2);

                // the bounds of the footer (bottom of page)
                m_FooterRect = new pdftron.PDF.Rect(0, m_BodyRect.y2, m_PageSize.Width, m_PageSize.Height);

                // the number of columns to force the FlowDocument into, 0 = do not change
                m_NumColumns = 0;
            }
        }

        /// <summary>
        /// DocumentPaginatorWrapper places the body of the paginated page of BodyRect on each page
        /// along with the header and footer.
        /// </summary>
        class DocumentPaginatorWrapper : DocumentPaginator
        {
            System.Windows.Size m_PageSize;
            pdftron.PDF.Rect m_BodyRect;
            DocumentPaginator m_Paginator;
            ConverterOptions m_Options;

            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="paginator">paginator from document</param>
            /// <param name="options">Converter options such as page size, body rect, header and footer functions, etc.</param>
            public DocumentPaginatorWrapper(DocumentPaginator paginator, ConverterOptions options)
            {
                m_Options = options;
                m_PageSize = options.PageSize;
                m_Paginator = paginator;
                m_BodyRect = options.BodyRect;
                m_Paginator.PageSize = new System.Windows.Size(m_BodyRect.Width(), m_BodyRect.Height());

                // This is not entirely necessary unless the header/footer require it.
                // m_Paginator.ComputePageCount();
            }

            /// <summary>
            /// Utility function to translate the body content on the page from 
            /// the page origin (upper left corner) to the specified BodyRect origin.
            /// </summary>
            /// <param name="rect">Input rect</param>
            /// <returns>Translated rectangle</returns>
            System.Windows.Rect Move(System.Windows.Rect rect)
            {
                if (rect.IsEmpty)
                {
                    return rect;
                }
                else
                {
                    return new System.Windows.Rect(rect.Left + m_BodyRect.x1, rect.Top + m_BodyRect.y1,
                                                   rect.Width, rect.Height);
                }
            }

            // For each page, this method will be called, starting from pageNumber = 0.
            /// <summary>
            /// The output serializer will call this function for each page, starting at pageNumber = 0
            /// </summary>
            /// <param name="pageNumber">Page number (0-based) to return</param>
            /// <returns>Formated page</returns>
            public override DocumentPage GetPage(int pageNumber)
            {
                DocumentPage page = m_Paginator.GetPage(pageNumber);
                
                // Create a wrapper visual for transformation and add extras
                ContainerVisual newPage = new ContainerVisual();
                DrawingVisual title = new DrawingVisual();

                // Create headers and footers
                if (m_Options.HeaderFunc != null)
                {
                    newPage.Children.Add(CreateHeaderFooterVisual(m_Options.HeaderFunc, 
                        m_Options.HeaderRect, m_BodyRect, m_PageSize, pageNumber));
                }
                if (m_Options.FooterFunc != null)
                {
                    newPage.Children.Add(CreateHeaderFooterVisual(m_Options.FooterFunc, 
                        m_Options.FooterRect, m_BodyRect, m_PageSize, pageNumber));
                }

                newPage.Children.Add(page.Visual);
                newPage.Children.Add(title);

                newPage.Transform = new TranslateTransform(m_BodyRect.x1, m_BodyRect.y1);

                return new DocumentPage(newPage, m_PageSize, Move(page.BleedBox), Move(page.ContentBox));
            }

            /// <summary>
            /// Utility function to create the visual for the header and footers and call the appropriate function.
            /// </summary>
            /// <param name="draw">Function to draw header or footer</param>
            /// <param name="bounds">Rectangle bounds of header or footer area.</param>
            /// <param name="bodyBounds">Rectangle bounds of body.</param>
            /// <param name="pageSize">Page size</param>
            /// <param name="pageNumber">Page number (0-based)</param>
            /// <returns></returns>
            private Visual CreateHeaderFooterVisual(ConverterOptions.DrawHeaderFooter draw, pdftron.PDF.Rect bounds,
                                                    pdftron.PDF.Rect bodyBounds, System.Windows.Size pageSize, int pageNumber)
            {
                DrawingVisual visual = new DrawingVisual();
                using (DrawingContext context = visual.RenderOpen())
                {
                    draw(context, bounds, bodyBounds, pageSize, pageNumber);
                }
                return visual;
            }

            public override bool IsPageCountValid
            {
                get { return m_Paginator.IsPageCountValid; }
            }

            public override int PageCount
            {
                get { return m_Paginator.PageCount; }
            }

            public override System.Windows.Size PageSize
            {
                get { return m_Paginator.PageSize; }
                set { m_Paginator.PageSize = value; }
            }

            public override IDocumentPaginatorSource Source
            {
                get { return m_Paginator.Source; }
            }
        }
    }
}
