// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using MS.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Printing;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Xps.Packaging;
using System.Xml;


#pragma warning disable 1634, 1691 //Allows suppression of certain PreSharp messages

namespace System.Windows.Xps.Serialization
{
    internal class XpsOMSerializationManager : 
                          PackageSerializationManager,
                          IXpsSerializationManager        
    {
        #region Constructor
        public 
        XpsOMSerializationManager(
            XpsOMPackagingPolicy xpsOMManager, 
            bool batchMode
            ):
        base()
        {
            _packagingPolicy = xpsOMManager;
            _isBatchMode = batchMode;

            _reachSerializationServices = new ReachSerializationServices();
            _reachSerializationServices.RegisterNameSpacesForTypes();
            _reachSerializationServices.RegisterSerializableDependencyPropertiesForReachTypes();
            _reachSerializationServices.RegisterNoneSerializableClrPropertiesForReachTypes();
            _visualSerializationService = new VisualSerializationService(this);

            XpsResourcePolicy resourcePolicy = new XpsResourcePolicy(XpsResourceSharing.NoResourceSharing);
            resourcePolicy.RegisterService(new XpsImageSerializationService(), typeof(XpsImageSerializationService));
            resourcePolicy.RegisterService(new XpsFontSerializationService(_packagingPolicy), typeof(XpsFontSerializationService));
            _resourcePolicy = resourcePolicy;

            SetFontSubsettingPolicy(FontSubsetterCommitPolicies.CommitPerPage);
            SetFontSubsettingCountPolicy(4);
        }

        #endregion Constructor

        #region PackageSerializationManager override
        public
        override
        void
        SaveAsXaml(
            object serializedObject
            )
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXSaveXpsBegin);

            if (_packagingPolicy.IsValid)
            {

                XmlWriter pageWriter = null;

                ArgumentNullException.ThrowIfNull(serializedObject);

                if (!XpsSerializationManager.IsSerializedObjectTypeSupported(serializedObject, _isBatchMode))
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
                }

                if (serializedObject is DocumentPaginator)
                {
                    // Prefast complains that serializedObject is not tested for null
                    // It is tested a few lines up
                    #pragma warning suppress 56506
                    if ((serializedObject as DocumentPaginator).Source is FixedDocument &&
                        serializedObject.GetType().ToString().Contains("FixedDocumentPaginator"))
                    {
                        serializedObject = (serializedObject as DocumentPaginator).Source;
                    }
                    else
                        if ((serializedObject as DocumentPaginator).Source is FixedDocumentSequence &&
                            serializedObject.GetType().ToString().Contains("FixedDocumentSequencePaginator"))
                        {
                            serializedObject = (serializedObject as DocumentPaginator).Source;
                        }
                }

                if (_simulator == null)
                {
                    _simulator = new XpsOMHierarchySimulator(this,
                                                             serializedObject);

                }

                if (!_isSimulating)
                {
                    _simulator.BeginConfirmToXPSStructure(_isBatchMode);
                    _isSimulating = true;
                }

                if (_isBatchMode)
                {
                    pageWriter = _simulator.SimulateBeginFixedPage();
                }

                ReachSerializer reachSerializer = GetSerializer(serializedObject);

                if (reachSerializer != null)
                {
                
                     //Things that need to be done at this stage
                     //1. Setting the stack context
                     //2. Setting the root of the graph for future references
                
                    reachSerializer.SerializeObject(serializedObject);

                    if (_isBatchMode)
                    {
                        _simulator.SimulateEndFixedPage(pageWriter);
                    }
                    else
                    {
                        _simulator.EndConfirmToXPSStructure(_isBatchMode);
                    }
                }
                else
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NoSerializer);
                }

            }
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXSaveXpsEnd);
        }

        internal
        override
        Type
        GetSerializerType(
            Type objectType
            )
        {
            Type serializerType = null;


            if ((serializerType = base.GetSerializerType(objectType)) == null)
            {
                if (typeof(System.Windows.Documents.FixedDocument).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(XpsOMFixedDocumentSerializer);
                }
                else if (typeof(System.Windows.Documents.PageContentCollection).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachPageContentCollectionSerializer);
                }
                else if (typeof(System.Windows.Documents.PageContent).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachPageContentSerializer);
                }
                else if (typeof(System.Windows.Controls.UIElementCollection).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachUIElementCollectionSerializer);
                }
                else if (typeof(System.Windows.Documents.FixedPage).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(XpsOMFixedPageSerializer);
                }
                else if (typeof(System.Windows.Documents.FixedDocumentSequence).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(XpsOMDocumentSequenceSerializer);
                }
                else if (typeof(System.Windows.Documents.DocumentReferenceCollection).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachDocumentReferenceCollectionSerializer);
                }
                else if (typeof(System.Windows.Documents.DocumentReference).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachDocumentReferenceSerializer);
                }
                else if (typeof(System.Windows.Documents.DocumentPaginator).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(XpsOMDocumentPaginatorSerializer);
                }
                else if (typeof(System.Windows.Documents.DocumentPage).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(XpsOMDocumentPageSerializer);
                }
                else if (typeof(System.Windows.Media.Visual).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(ReachVisualSerializer);
                }
                else if (typeof(System.Printing.PrintTicket).IsAssignableFrom(objectType))
                {
                    serializerType = typeof(PrintTicketSerializer);
                }
            }

            return serializerType;

        }

        /// <summary>
        ///
        /// </summary>
        internal
        override
        TypeConverter
        GetTypeConverter(
            Object serializedObject
            )
        {
            TypeConverter typeConverter = null;

            if (typeof(BitmapSource).IsAssignableFrom(serializedObject.GetType()))
            {
                return new ImageSourceTypeConverter();
            }
            else if (typeof(GlyphRun).IsAssignableFrom(serializedObject.GetType()))
            {
                return new FontTypeConverter();
            }
            else if (typeof(Color).IsAssignableFrom(serializedObject.GetType()))
            {
                return new ColorTypeConverter();
            }
            else
            {
                typeConverter = base.GetTypeConverter(serializedObject);
            }

            return typeConverter;
        }

        /// <summary>
        ///
        /// </summary>
        internal
        override
        TypeConverter
        GetTypeConverter(
            Type serializedObjectType
            )
        {
            TypeConverter typeConverter = null;

            if (typeof(BitmapSource).IsAssignableFrom(serializedObjectType))
            {
                return new ImageSourceTypeConverter();
            }
            else if (typeof(GlyphRun).IsAssignableFrom(serializedObjectType))
            {
                return new FontTypeConverter();
            }
            else if (typeof(Color).IsAssignableFrom(serializedObjectType))
            {
                return new ColorTypeConverter();
            }
            else
            {
                typeConverter = base.GetTypeConverter(serializedObjectType);
            }

            return typeConverter;
        }

        internal
        override
        bool
        CanSerializeDependencyProperty(
            Object serializableObject,
            TypeDependencyPropertyCache dependencyProperty
            )
        {
            return XpsSerializationManager.CanSerializeDependencyProperty(serializableObject, dependencyProperty, _reachSerializationServices);
        }

        internal
        override
        bool
        CanSerializeClrProperty(
            Object serializableObject,
            TypePropertyCache property
            )
        {
            return XpsSerializationManager.CanSerializeClrProperty(serializableObject, property, _reachSerializationServices);
        }
        
        internal
        override
        string
        GetXmlNSForType(
            Type objectType
            )
        {
            return (string)_reachSerializationServices.TypesXmlNSMapping[objectType];
        }

        internal override Xml.XmlWriter AcquireXmlWriter(Type writerType)
        {
            XmlWriter xmlWriter = null;
            if (_packagingPolicy != null)
            {
                if (writerType == typeof(FixedPage))
                {
                    _currentFixedPageWriterRef++;
                    xmlWriter = _packagingPolicy.AcquireXmlWriterForFixedPage();
                }
                else
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
                }
            }
            return xmlWriter;
        }

        internal
        void
        signalReleaseToFontService(
            Type writerType
            )
        {
            bool subsetComplete = false;
            int refCnt = DecrementRefCntByType(writerType);

            // signal the font sub-setter that we completed a node
            if (_resourcePolicy != null)
            {
                XpsFontSerializationService fontService = (XpsFontSerializationService)_resourcePolicy.GetService(typeof(XpsFontSerializationService));
                //
                // The font subsetter will determine if based on this
                // signal we have completed a subset
                //
                if (fontService != null && refCnt == 0)
                {
                    subsetComplete = fontService.SignalCommit(writerType);
                }
            }
        }

        internal
        void
        ReleaseXpsOMWriterForFixedDocumentSequence()
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterStart);
            signalReleaseToFontService(typeof(FixedDocumentSequence));

            _packagingPolicy?.CloseXpsOMPackageWriter();
                
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterEnd);
        }

        internal
        void
        ReleaseXpsOMWriterForFixedDocument()
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterStart);
            signalReleaseToFontService(typeof(FixedDocument));

            _packagingPolicy?.ReleaseXpsOMWriterForFixedDocument();

            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterEnd);
        }

        internal
        override
        void
        ReleaseXmlWriter(
            Type writerType
            )
        {
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterStart);

            //
            // Allow the packaging policy to release the stream
            //
            if (_packagingPolicy != null)
            {
                if (writerType == typeof(FixedPage))
                {
                    _packagingPolicy.ReleaseXmlWriterForFixedPage();
                }
                else
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
                }
            }

            signalReleaseToFontService(writerType);
            Toolbox.EmitEvent(EventTrace.Event.WClientDRXReleaseWriterEnd);
        }

        internal
        override
        XpsResourceStream
        AcquireResourceStream(
            Type resourceType
            )
        {
            throw new NotImplementedException();
        }

        internal 
        override
        XpsResourceStream
        AcquireResourceStream(
            Type resourceType,
            string resourceID
            )
        {
            XpsResourceStream resourceStream = null;

            if (_packagingPolicy != null)
            {
                if (resourceType == typeof(GlyphRun))
                {
                    resourceStream = _packagingPolicy.AcquireResourceStreamForXpsFont(resourceID);
                }
                else if (resourceType == typeof(BitmapSource))
                {
                    resourceStream = _packagingPolicy.AcquireResourceStreamForXpsImage(resourceID);
                }
                else if (resourceType == typeof(ColorContext))
                {
                    resourceStream = _packagingPolicy.AcquireResourceStreamForXpsColorContext(resourceID);
                }
                else if (resourceType == typeof(ResourceDictionary))
                {
                    resourceStream = _packagingPolicy.AcquireResourceStreamForXpsResourceDictionary(resourceID);
                }
                else
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
                }
            }

            return resourceStream;
        }

        internal
        override
        void
        ReleaseResourceStream(
            Type resourceType
            )
        {
            if (_packagingPolicy != null)
            {
                if (resourceType == typeof(GlyphRun))
                {
                    _packagingPolicy.ReleaseResourceStreamForXpsFont();
                }
                else if (resourceType == typeof(BitmapSource))
                {
                    _packagingPolicy.ReleaseResourceStreamForXpsImage();
                }
                else if (resourceType == typeof(ColorContext))
                {
                    _packagingPolicy.ReleaseResourceStreamForXpsColorContext();
                }
                else if (resourceType == typeof(ResourceDictionary))
                {
                    _packagingPolicy.ReleaseResourceStreamForXpsResourceDictionary();
                }
                else
                {
                    throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
                }
            }
        }

        /// <summary>
        /// We must implement this method from abstract class PackageSerializationManager, it is
        /// actually not used during printing/serialization, so it should throw if it is ever called
        /// </summary>
        internal
        override
        void
        ReleaseResourceStream(
            Type resourceType,
            string resourceID
            )
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// It is no longer necessary to add the relationships explicitly since XpsOM will 
        /// do it automatically, however, the serialization engine will still call into this 
        /// method, so just no-op and continue.
        /// </summary>
        internal
        override
        void
        AddRelationshipToCurrentPage(
            Uri targetUri,
            string relationshipName
            )
        {
            return;
        }

        internal
        override
        BasePackagingPolicy
        PackagingPolicy
        {
            get 
            {
                return _packagingPolicy; 
            }
        }

        internal
        override
        XpsResourcePolicy
        ResourcePolicy
        {
            get 
            {
                return _resourcePolicy;
            }
        }
        
        #endregion PackageSerializationManager override

        #region Private methods

        /// <summary>
        /// Sets the Font subsetting policy on the
        /// Font Subset service
        /// </summary>
        private
        void
        SetFontSubsettingPolicy(FontSubsetterCommitPolicies policy)
        {
            IServiceProvider resourceServiceProvider = (IServiceProvider)ResourcePolicy;
            XpsFontSerializationService fontService = (XpsFontSerializationService)resourceServiceProvider.GetService(typeof(XpsFontSerializationService));
            if (fontService != null)
            {
                XpsFontSubsetter fontSubsetter = fontService.FontSubsetter;
                fontSubsetter.SetSubsetCommitPolicy(policy);
            }
        }

        internal
        virtual
        void
        Commit()
        {
            if (_isBatchMode && _isSimulating && _packagingPolicy.IsValid)
            {
                _simulator.EndConfirmToXPSStructure(_isBatchMode);
            }
        }


        /// <summary>
        /// Sets the Font subsetting count policy on the
        /// Font Subset service.  This is used to determine
        /// how many page or document signals are required before
        /// the subsetted font is flushed
        /// </summary>
        private
        void
        SetFontSubsettingCountPolicy(int countPolicy)
        {
            IServiceProvider resourceServiceProvider = (IServiceProvider)ResourcePolicy;
            XpsFontSerializationService fontService = (XpsFontSerializationService)resourceServiceProvider.GetService(typeof(XpsFontSerializationService));
            if (fontService != null)
            {
                XpsFontSubsetter fontSubsetter = fontService.FontSubsetter;
                fontSubsetter.SetSubsetCommitCountPolicy(countPolicy);
            }
        }

        private
        int
        DecrementRefCntByType(Type writerType)
        {
            int refCnt = 0;
            if (writerType == typeof(FixedDocumentSequence))
            {
                refCnt = --_currentDocumentSequenceWriterRef;
            }
            else if (writerType == typeof(FixedDocument))
            {
                refCnt = --_currentFixedDocumentWriterRef;
            }
            else if (writerType == typeof(FixedPage))
            {
                refCnt = --_currentFixedPageWriterRef;
            }
            else
            {
                throw new XpsSerializationException(SR.ReachSerialization_NotSupported);
            }
            return refCnt;
        }

        #endregion Private methods

        #region Internal methods
        
        internal
        void
        EnsureXpsOMPackageWriter()
        {
            _currentDocumentSequenceWriterRef++;
            _packagingPolicy.EnsureXpsOMPackageWriter();
        }

        internal
        void
        StartNewDocument()
        {
            _currentFixedDocumentWriterRef++;
            _packagingPolicy.StartNewDocument();
        }

        internal
        void
        OnXPSSerializationPrintTicketRequired(
            object operationState
            )
        {
            XpsSerializationPrintTicketRequiredEventArgs e = operationState as XpsSerializationPrintTicketRequiredEventArgs;

            if (XpsSerializationPrintTicketRequired != null)
            {
                e.Modified = true;

                XpsSerializationPrintTicketRequired(this, e);
            }
        }

        internal
        void
        OnXPSSerializationProgressChanged(
            object operationState
            )
        {
            XpsSerializationProgressChangedEventArgs e = operationState as XpsSerializationProgressChangedEventArgs;

            if (XpsSerializationProgressChanged != null)
            {
                XpsSerializationProgressChanged(this, e);
            }
        }

        internal
        void 
        RegisterPageStart()
        {
            if (_pageStartState)
            {
                throw new XpsSerializationException(SR.ReachSerialization_FixedPageInPage);
            }
            //
            // Entering Page  Started state
            //
            _pageStartState = true;
            //
            // Increment the number of documents serialized
            //
            _pageNumber += 1;
        }

        internal
        void
        RegisterPageEnd()
        {
            //
            // Exiting Page  Started state
            //
            _pageStartState = false;
        }

        internal
        void 
        RegisterDocumentSequenceStart()
        {
            _documentNumber = 0;
        }

        internal
        void
        RegisterDocumentSequenceEnd()
        {
            //
            // It is invalid to have a document sequence with no fixed documents
            // If no fixed documents have been searialzed throw
            //
            if (_documentNumber <= 0)
            {
                throw new XpsSerializationException(SR.ReachSerialization_NoFixedDocuments);
            }
        }

        internal
        void
        RegisterDocumentStart()
        {
            if( _documentStartState )
            {
                throw new XpsSerializationException(SR.ReachSerialization_FixedDocumentInDocument);
            }
            if( _pageStartState)
            {
                throw new XpsSerializationException(SR.ReachSerialization_FixedDocumentInPage);
            }
            //
            // Entering Document  Started state
            //
            _documentStartState = true;
            //
            // Increment the number of documents serialized
            //
            _documentNumber += 1;
            //
            // Clearing the number of pages for this document
            //
            _pageNumber = 0;
        }

        internal
        void
        RegisterDocumentEnd()
        {
            //
            // It is invalid to have a document with no fixed pages
            // If no fixed pages have been searialzed throw
            //
            if (_pageNumber <= 0)
            {
                throw new XpsSerializationException(SR.ReachSerialization_NoFixedPages);
            }
            //
            // Exiting Document  Started state
            //
            _documentStartState = false;
        }

        #endregion Internal methods

        #region IXpsSerializationManager implementation

        void
        IXpsSerializationManager.OnXPSSerializationPrintTicketRequired(
            object operationState
            )
        {
            OnXPSSerializationPrintTicketRequired(operationState);
        }

        void
        IXpsSerializationManager.OnXPSSerializationProgressChanged(
            object operationState
            )
        {
            OnXPSSerializationProgressChanged(operationState);
        }

        void
        IXpsSerializationManager.RegisterPageStart()
        {
            RegisterPageStart();
        }

        void
        IXpsSerializationManager.RegisterPageEnd()
        {
            RegisterPageEnd();
        }

        PrintTicket
        IXpsSerializationManager.FixedPagePrintTicket
        {
            get
            {
                return FixedPagePrintTicket;
            }

            set
            {
                FixedPagePrintTicket = value;
            }
        }

        Size
        IXpsSerializationManager.FixedPageSize
        {
            get
            {
                return FixedPageSize;
            }

            set
            {
                FixedPageSize = value;
            }
        }


        VisualSerializationService
        IXpsSerializationManager.VisualSerializationService
        {
            get
            {
                return VisualSerializationService;
            }
        }

        #endregion IXpsSerializationManager properties

        #region internal properties

        internal
        PrintTicket
        FixedPagePrintTicket
        {
            get
            {
                return _fixedPagePrintTicket;
            }

            set
            {
                _fixedPagePrintTicket = value;
            }
        }

        internal
        Size
        FixedPageSize
        {
            get
            {
                return _fixedPageSize;
            }

            set
            {
                _fixedPageSize = value;
                _packagingPolicy.FixedPageSize = _fixedPageSize;
            }
        }

        internal
        VisualSerializationService
        VisualSerializationService
        {
            get
            {
                return _visualSerializationService;
            }
        }

        /// <summary>
        /// Gets/Sets the hierarchy simulator used by this manager
        /// </summary>
        internal
        XpsOMHierarchySimulator
        Simulator
        {
            get
            {
                return _simulator;
            }

            set
            {
                _simulator = value;
            }
        }

        /// <summary>
        /// Gets/sets whether Xps hierachy simulation was necessary for this manager
        /// </summary>
        internal
        bool
        IsSimulating
        {
            get
            {
                return _isSimulating;
            }

            set
            {
                _isSimulating = value;
            }
        }

        /// <summary>
        /// Returns whether this manager is accepting multiple SaveAsXaml calls
        /// to serialize each Visual individually, this is done via VisualsToXpsDocument
        /// </summary>
        internal
        bool
        IsBatchMode
        {
            get
            {
                return _isBatchMode;
            }
        }

        #endregion internal properties

        #region Private members

        private
        XpsOMPackagingPolicy _packagingPolicy;

        private
        bool _isBatchMode;

        private
        XpsOMHierarchySimulator _simulator;

        private
        bool _isSimulating;


        private
        int _currentDocumentSequenceWriterRef;

        private
        int _currentFixedDocumentWriterRef;

        private
        int _currentFixedPageWriterRef;

        //
        //  This counter is used to ensure at least one document is added
        //
        private
        int _documentNumber;

        //
        //  This counter is used to ensure at least one page is added
        //  This is reset by a document completing
        //
        private
        int _pageNumber;

        //
        //  This flag is used to test if in document serialization
        //  This is to prevent writing of a document in a document
        //
        private
        bool _documentStartState;

        //
        //  This flag is used to test if in page serialization
        //  This is to prevent writing of a page in a page
        //
        private
        bool _pageStartState;

        //
        // This print ticket is cached when we get it inside of the Page Content Serializer
        // and we need to retrieve it inside of the FixedPage Serializer
        //
        private
        PrintTicket _fixedPagePrintTicket;

        private
        Size _fixedPageSize = new Size(816, 1056);

        private
        XpsResourcePolicy _resourcePolicy;

        private
        ReachSerializationServices _reachSerializationServices;

        private 
        VisualSerializationService _visualSerializationService;

        #endregion Private members

        #region internal events

        internal
        event
        XpsSerializationPrintTicketRequiredEventHandler XpsSerializationPrintTicketRequired;

        internal
        event
        XpsSerializationProgressChangedEventHandler XpsSerializationProgressChanged;

        #endregion

    }
}
