// 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.

/*++
                                                                              
                                                                        
    Abstract:
        This file contains the definition of a Base class that defines
        the common functionality required to serialie on type in a 
        graph of types rooted by some object instance
                
--*/
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Xml;
using System.IO;
using System.Security;
using System.ComponentModel.Design.Serialization;
using System.Windows.Xps.Packaging;
using System.Windows.Documents;
using System.Windows.Media;

namespace System.Windows.Xps.Serialization
{
    /// <summary>
    /// Base class defining common functionalities required to
    /// serialize one type.
    /// </summary>
    internal abstract class ReachSerializerAsync :
                            ReachSerializer
    {
        #region Constructor
        
        /// <summary>
        /// Constructor for class ReachSerializer
        /// </summary>
        /// <param name="manager">
        /// The serializtion manager, the services of which are
        /// used later for the serialization process of the type.
        /// </param>
        public
        ReachSerializerAsync(
            PackageSerializationManager   manager
            )
        {
            ArgumentNullException.ThrowIfNull(manager);

            _serializationManager = manager as IXpsSerializationManagerAsync;

            if(_serializationManager == null)
            {
                throw new XpsSerializationException(SR.ReachSerialization_NotXpsSerializationManagerAsync);
            }

            _xmlWriter            = null;
        }

        #endregion Constructor

        #region Public Methods


        public
        virtual
        void
        AsyncOperation(
            ReachSerializerContext context
            )
        {
            if(context == null)
            {

            }
           
            switch (context.Action) 
            {
                case SerializerAction.endSerializeObject:
                {
                    EndSerializeObject(context.ObjectContext);
                    break;
                }

                case SerializerAction.serializeNextProperty:
                {
                    SerializeNextProperty(context.ObjectContext);
                    break;
                }
            }
        }

        /// <summary>
        /// The main method that is called to serialize the object of
        /// that given type.
        /// </summary>
        /// <param name="serializedObject">
        /// Instance of object to be serialized.
        /// </param>
        public
        override
        void
        SerializeObject(
            Object serializedObject
            )
        {
            BeginSerializeObject(serializedObject);
        }

        #endregion Public Methods
        
        
        #region Internal Methods

        /// <summary>
        /// The main method that is called to serialize the object of
        /// that given type and that is usually called from within the
        /// serialization manager when a node in the graph of objects is
        /// at a turn where it should be serialized.
        /// </summary>
        /// <param name="serializedProperty">
        /// The context of the property being serialized at this time and
        /// it points internally to the object encapsulated by that node.
        /// </param>
        internal
        override
        void
        SerializeObject(
            SerializablePropertyContext serializedProperty
            )
        {
            BeginSerializeObject(serializedProperty);
        }

        internal
        virtual
        void
        BeginSerializeObject(
            SerializablePropertyContext serializedProperty
            )
        {
            ArgumentNullException.ThrowIfNull(serializedProperty);

            if (SerializationManager == null)
            {
                throw new XpsSerializationException(SR.ReachSerialization_MustHaveSerializationManager);
            }

            //
            // At this stage discover the graph of properties of the object that
            // need to be serialized
            //                      
            SerializableObjectContext serializableObjectContext = DiscoverObjectData(serializedProperty.Value,
                                                                                     serializedProperty);

            if(serializableObjectContext!=null)
            {
                //
                // Push the object at hand on the context stack
                //
                SerializationManager.GraphContextStack.Push(serializableObjectContext);

                ReachSerializerContext context = new ReachSerializerContext(this,
                                                                            serializableObjectContext,
                                                                            SerializerAction.endSerializeObject);

                _serializationManager.OperationStack.Push(context);
                //
                // At this stage we should start streaming the markup representing the
                // object graph to the corresponding destination
                //
                PersistObjectData(serializableObjectContext);

            }
        }
        
        /// <summary>
        ///
        /// </summary>
        internal
        virtual
        void
        BeginSerializeObject(
            Object serializedObject
            )
        {
            ArgumentNullException.ThrowIfNull(serializedObject);
            if (SerializationManager == null)
            {
                throw new XpsSerializationException(SR.ReachSerialization_MustHaveSerializationManager);
            }
            //
            // At this stage discover the graph of properties of the object that
            // need to be serialized
            //                      
            SerializableObjectContext serializableObjectContext = DiscoverObjectData(serializedObject,
                                                                                     null);

            if(serializableObjectContext!=null)
            {
                //
                // Push the object at hand on the context stack
                //
                SerializationManager.GraphContextStack.Push(serializableObjectContext);

                ReachSerializerContext context = new ReachSerializerContext(this,
                                                                            serializableObjectContext,
                                                                            SerializerAction.endSerializeObject);

                _serializationManager.OperationStack.Push(context);
                //
                // At this stage we should start streaming the markup representing the
                // object graph to the corresponding destination
                //
                PersistObjectData(serializableObjectContext);
            }
         }

        internal
        virtual
        void
        EndSerializeObject(
            SerializableObjectContext   serializableObjectContext
            )
        {
            //
            // Pop the object from the context stack
            //
            SerializationManager.GraphContextStack.Pop();

            //
            // Recycle the used SerializableObjectContext
            //
            SerializableObjectContext.RecycleContext(serializableObjectContext);
        }


        /*/// <summary>
        /// The method is called once the object data is discovered at that 
        /// point of the serialization process.
        /// </summary>
        /// <param name="serializableObjectContext">
        /// The context of the object to be serialized at this time.
        /// </param>
        internal
        abstract
        void
        PersistObjectData(
            SerializableObjectContext   serializableObjectContext
            );*/


        /// <summary>
        ///
        /// </summary>
        internal
        virtual
        void
        EndPersistObjectData(
            )
        {
            //
            // Do nothing in the base class
            //
        }


        /// <summary>
        ///     Serialize the properties within the object
        ///     context into METRO
        /// </summary>
        /// <remarks>
        ///     Method follows these steps
        ///     1. Serializes the instance as string content 
        ///         if is not meant to be a complex value. Else ...
        ///     2. Serialize Properties as attributes
        ///     3. Serialize Complex Properties as separate parts
        ///         through calling separate serializers
        ///     Also this is the virtual to override custom attributes or 
        ///     contents need to be serialized
        /// </remarks>
        /// <param name="serializableObjectContext">
        /// The context of the object to be serialized at this time.
        /// </param>
        internal
        override
        void
        SerializeObjectCore(
            SerializableObjectContext   serializableObjectContext
            )
        {
            ArgumentNullException.ThrowIfNull(serializableObjectContext);

            if (!serializableObjectContext.IsReadOnlyValue && 
                serializableObjectContext.IsComplexValue)
            {
                SerializeProperties(serializableObjectContext);
            }
        }

        /// <summary>
        /// This method is the one that writes out the attribute within
        /// the xml stream when serializing simple properites.
        /// </summary>
        /// <param name="serializablePropertyContext">
        /// The property that is to be serialized as an attribute at this time.
        /// </param>
        internal
        override
        void
        WriteSerializedAttribute(
            SerializablePropertyContext serializablePropertyContext
            )
        {
            ArgumentNullException.ThrowIfNull(serializablePropertyContext);
        }

        #endregion Internal Methods
        
        #region Private Methods

        /// <summary>
        /// This method is the one that parses down the object at hand
        /// to discover all the properties that are expected to be serialized
        /// at that object level.
        /// the xml stream when serializing simple properties.
        /// </summary>
        /// <param name="serializedObject">
        /// The instance of the object being serialized.
        /// </param>
        /// <param name="serializedProperty">
        /// The instance of property on the parent object from which this 
        /// object stemmed. This could be null if this is the node object
        /// or the object has no parent.
        /// </param>
        private
        SerializableObjectContext
        DiscoverObjectData(
            Object                      serializedObject,
            SerializablePropertyContext serializedProperty
            )
        {
            //
            // Trying to figure out the parent of this node, which is at this stage
            // the same node previously pushed on the stack or in other words it is
            // the node that is currently on the top of the stack
            //
            SerializableObjectContext 
            serializableObjectParentContext = (SerializableObjectContext)SerializationManager.
                                              GraphContextStack[typeof(SerializableObjectContext)];
            //
            // Create the context for the current object
            //
            SerializableObjectContext serializableObjectContext = 
            SerializableObjectContext.CreateContext(SerializationManager, 
                                                    serializedObject, 
                                                    serializableObjectParentContext,
                                                    serializedProperty);

            //
            // Set the root object to be serialized at the level of the SerializationManager
            //
            if(SerializationManager.RootSerializableObjectContext == null)
            {
                SerializationManager.RootSerializableObjectContext = serializableObjectContext;
            }

            return serializableObjectContext;
        }

        /// <summary>
        /// Trigger all properties serialization
        /// </summary>
        private
        void
        SerializeProperties(
            SerializableObjectContext   serializableObjectContext
            )
        {
            ArgumentNullException.ThrowIfNull(serializableObjectContext);

            SerializablePropertyCollection propertyCollection = serializableObjectContext.PropertiesCollection;

            if(propertyCollection!=null)
            {
                //for(propertyCollection.Reset();
                //    propertyCollection.MoveNext();)
                //{
                //    SerializablePropertyContext serializablePropertyContext = 
                //    (SerializablePropertyContext)propertyCollection.Current;
                //
                //    if(serializablePropertyContext!=null)
                //    {
                //        SerializeProperty(serializablePropertyContext);
                //    }
                //}
                propertyCollection.Reset();
                ReachSerializerContext context = new ReachSerializerContext(this,
                                                                            serializableObjectContext,
                                                                            SerializerAction.serializeNextProperty);

                _serializationManager.OperationStack.Push(context);
            }
        }

        private
        void
        SerializeNextProperty(
            SerializableObjectContext   serializableObjectContext
            )
        {
            SerializablePropertyCollection propertyCollection = serializableObjectContext.PropertiesCollection;

            if(propertyCollection.MoveNext())
            {
                SerializablePropertyContext serializablePropertyContext = 
                (SerializablePropertyContext)propertyCollection.Current;

                if(serializablePropertyContext!=null)
                {

                    ReachSerializerContext context = new ReachSerializerContext(this,
                                                                                serializableObjectContext,
                                                                                SerializerAction.serializeNextProperty);

                    _serializationManager.OperationStack.Push(context);
                    SerializeProperty(serializablePropertyContext);
                }

            }
        }

        /// <summary>
        /// Trigger serializing one property at a time.
        /// </summary>
        private
        void
        SerializeProperty(
            SerializablePropertyContext serializablePropertyContext
            )
        {
            ArgumentNullException.ThrowIfNull(serializablePropertyContext);

            if (!serializablePropertyContext.IsComplex)
            {
                //
                // Non-Complex Properties are serialized as attributes
                //
                WriteSerializedAttribute(serializablePropertyContext);
            }
            else
            {
                //
                // Complex properties could be treated in different ways
                // based on their type. Examples of that are:
                //
                //
                //
                ReachSerializer serializer = SerializationManager.GetSerializer(serializablePropertyContext.Value);

                // If there is no serializer for this type, we won't serialize this property
                if(serializer!=null)
                {
                    serializer.SerializeObject(serializablePropertyContext);
                }
            }
        }

        #endregion Private Methods
        
        #region Public Properties

        /// <summary>
        /// Query / Set Xml Writer for the equivelan part
        /// </summary>
        public
        override
        XmlWriter
        XmlWriter
        {
            get
            {
                return _xmlWriter;
            }

            set
            {
                _xmlWriter = value;
            }
        }

        /// <summary>
        /// Query the SerializationManager used by this serializer.
        /// </summary>
        public
        override
        PackageSerializationManager
        SerializationManager
        {
            get
            {
                return (PackageSerializationManager)_serializationManager;
            }
        }

        #endregion Public Properties
        
        #region Private Data members

        private
        IXpsSerializationManagerAsync   _serializationManager;

        private 
        XmlWriter                        _xmlWriter;
        
        #endregion Private Data members
    };

    internal enum SerializerAction
    {
        endSerializeObject             = 1,
        endPersistObjectData           = 2,
        serializeNextProperty          = 3,
        serializeNextDocumentReference = 4,
        serializeDocument              = 5,
        serializeNextPageContent       = 6,
        serializePage                  = 7,
        endSerializeReachFixedPage     = 8,
        serializeNextUIElement         = 9,
        serializeNextTreeNode          = 10,
        serializeNextDocumentPage      = 11,
        endSerializeDocumentPage       = 12
    };

    internal class ReachSerializerContext
    {

        public
        ReachSerializerContext(
            ReachSerializerAsync        serializer,
            SerializerAction            action
            )
        {
            this._action        = action;
            this._serializer    = serializer;
            this._objectContext = null;
        }

        public
        ReachSerializerContext(
            ReachSerializerAsync        serializer,
            SerializableObjectContext   objectContext,
            SerializerAction            action
            )
        {
            this._action        = action;
            this._serializer    = serializer;
            this._objectContext = objectContext;
        }

        public
        virtual
        SerializerAction
        Action
        {
            get
            {
                return _action;
            }
        }

        public
        virtual
        ReachSerializerAsync
        Serializer
        {
            get
            {
                return _serializer;
            }
        }

        public
        virtual
        SerializableObjectContext
        ObjectContext
        {
            get
            {
                return _objectContext;
            }
        }

        private
        SerializerAction            _action;

        private
        ReachSerializerAsync        _serializer;

        private
        SerializableObjectContext   _objectContext;
    };
}
