using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Reflection;
using Aitex.Core.RT.Log;

namespace Aitex.Core.Util
{
    /// <summary>
    /// class 'ObjectSerializer' provides a set of utilities which serialize objects to xml or binary file, 
    /// or deserialize xml or binary file back to objects.
    /// </summary>
    public sealed class ObjectSerializer
    {
        private ObjectSerializer() { }
        /// <summary>
        /// Serialize the supplied object To memory stream in xml format.
        /// </summary>
        /// <param name="valueToSerializeToMemoryStream"></param>
        /// <returns></returns>
        public static MemoryStream SerializeObjectToMemoryStream(object valueToSerializeToMemoryStream)
        {
            var retVal = new MemoryStream();
            var typeToSerialize = valueToSerializeToMemoryStream.GetType();
            var serializer = new XmlSerializer(typeToSerialize);
            serializer.Serialize(retVal, valueToSerializeToMemoryStream);

            retVal.Seek(0, SeekOrigin.Begin);

            return retVal;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static XmlDocument SerializeObjectToXmlDom(object obj)
        {
            Stream stream = SerializeObjectToMemoryStream(obj);

            XmlDocument xmldom = new XmlDocument();

            xmldom.Load(stream);

            return xmldom;
        }

        private static Type[] RemoveDuplicateType(Type typeOfObject, IEnumerable<Type> types)
        {
            var typeList = new List<Type>();

            foreach (var type in types)
            {
                if (!type.FullName.Equals(typeOfObject.FullName))
                {
                    typeList.Add(type);
                }
            }
            return typeList.ToArray();
        }

        /// <summary>
        /// Serialize derived types to xml stream, SeekOrigin set to Begin.
        /// </summary>
        /// <param name="valueToSerializeToMemoryStream"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public static MemoryStream SerializeObjectToMemoryStream(object valueToSerializeToMemoryStream, Type[] types)
        {
            var retVal = new MemoryStream();
            var typeToSerialize = valueToSerializeToMemoryStream.GetType();
            types = RemoveDuplicateType(typeToSerialize, types);
            var serializer = new XmlSerializer(typeToSerialize, types);
            serializer.Serialize(retVal, valueToSerializeToMemoryStream);

            retVal.Seek(0, SeekOrigin.Begin);

            return retVal;
        }

        /// <summary>
        /// Serialize the supplied object To binary stream, SeekOrigin set to Begin.
        /// </summary>
        /// <param name="valueToSerializeToMemoryStream"></param>
        /// <returns></returns>
        public static MemoryStream SerializeObjectToBinaryStream(object valueToSerializeToMemoryStream)
        {
            // Converts object [objectToSerialize] of type [typeOfObjectBeingSerialized] to a memory stream  
            var retVal = new MemoryStream();
            IFormatter formatter = new BinaryFormatter();
            formatter.Serialize(retVal, valueToSerializeToMemoryStream);
            retVal.Seek(0, SeekOrigin.Begin);
            return retVal;
        }

        /// <summary>
        /// Serializes an object into a file using binary formatter.
        /// </summary>
        /// <param name="fullFileName"></param>
        /// <param name="valueToSerialize"></param>
        /// <returns></returns>
        public static bool SerializeObjectToBinaryFile(string fullFileName, object valueToSerialize)
        {
            try
            {
                using (FileStream stream = new FileStream(fullFileName, FileMode.OpenOrCreate))
                {
                    IFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(stream, valueToSerialize);
                    stream.Seek(0, SeekOrigin.Begin);
                }
            }
            catch (Exception e)
            {
                LOG.WriteExeption(e);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Serialize the supplied object To Xml stream
        /// </summary>
        /// <param name="valueToSerializeToMemoryStream"></param>
        /// <returns></returns>
        public static string SerializeObjectToXml(object valueToSerializeToMemoryStream)
        {
            using (var memStream = SerializeObjectToMemoryStream(valueToSerializeToMemoryStream))
            using (var reader = new StreamReader(memStream))
            {
                return reader.ReadToEnd();
            }
        }

        /// <summary>
        /// Serializes an object into a Xml file.
        /// </summary>
        /// <param name="fullFileName"></param>
        /// <param name="valueToSerializeToFile"></param>
        /// <returns></returns>
        public static bool SerializeObjectToXmlFile(string fullFileName, object valueToSerializeToFile)
        {
            try
            {
                using (FileStream fs = new FileStream(fullFileName, FileMode.Create))//file existed, use "FileMode.CreateNew" will cause exception
                {
                    using (MemoryStream ms = SerializeObjectToMemoryStream(valueToSerializeToFile))
                    {
                        ms.WriteTo(fs);
                        ms.Close();
                    }
                    fs.Close();
                }
            }
            catch (Exception e)
            {
                LOG.WriteExeption(e);
                return false;
            }
            return true;
        }

        /// <summary>
        /// Deserialize supplied stream using XmlSerializer
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static T DeserializeObjectFromStream<T>(Stream stream)
        {
            //todo here always throw exception sometimes 
            var serializer = new XmlSerializer(typeof(T));
            var retVal = (T)serializer.Deserialize(stream);
            return retVal;
        }

        /// <summary>
        /// Convert binary file content to object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="binFilePath"></param>
        /// <returns></returns>
        public static T DeserializeObjectFromBinaryFile<T>(string binFilePath)
        {
            T obj = default(T);
            using (FileStream fs = new FileStream(binFilePath, FileMode.OpenOrCreate))
            {
                obj = (T)DeserializeObjectFromBinaryStream(fs);
            }
            return obj;
        }

        /// <summary>
        /// Deserialize supplied stream using BinaryFormatter
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static object DeserializeObjectFromBinaryStream(Stream stream)
        {
            IFormatter formatter = new BinaryFormatter();
            var retVal = formatter.Deserialize(stream);
            return retVal;
        }

        /// <summary>
        /// Deserialize supplied stream using XmlSerializer to specified type of object
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="typeOfObject"></param>
        /// <returns></returns>
        public static object DeserializeObjectFromStream(Stream stream, Type typeOfObject)
        {
            try
            {
                var serializer = new XmlSerializer(typeOfObject);
                object retVal = serializer.Deserialize(stream);
                return retVal;
            }
            catch (Exception ex)
            {
                LOG.WriteExeption(string.Format("界面发序列化数据出错:\n函数名:{0}\n串行数据内容:{1}",
                    System.Reflection.MethodInfo.GetCurrentMethod().Name,
                    stream.ToString()), ex);
                throw;
            }
        }

        /// <summary>
        /// Deserialize supplied stream using XmlSerializer to specified tyep of object 
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="typeOfObject"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public static object DeserializeObjectFromStream(Stream stream, Type typeOfObject, Type[] types)
        {
            types = RemoveDuplicateType(typeOfObject, types);
            var serializer = new XmlSerializer(typeOfObject, types);
            object retVal = serializer.Deserialize(stream);
            return retVal;
        }


        /// <summary>
        /// Convert supplied string to xml stream, then deserialize the xml stream to specified type of object
        /// </summary>
        /// <param name="xml"></param>
        /// <param name="typeToDeserialize"></param>
        /// <returns></returns>
        public static object DeserializeObjectFromXml(string xml, Type typeToDeserialize)
        {
            if (string.IsNullOrWhiteSpace(xml))
            {
                return null;
            }
            using (var memoryStream = new MemoryStream())
            {
                var xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(xml);
                xmlDoc.Save(memoryStream);
                memoryStream.Seek(0, SeekOrigin.Begin);

                return DeserializeObjectFromStream(memoryStream, typeToDeserialize);
            }
        }


        /// <summary>
        /// Convert xml file content to object
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlFilePath"></param>
        /// <returns></returns>
        public static T DeserializeObjectFromXmlFile<T>(string xmlFilePath)
        {
            T obj = default(T);

            FileInfo fi = new FileInfo(xmlFilePath);
            if (!fi.Exists) return obj;
            using (StreamReader reader = new StreamReader(xmlFilePath))
            {
                if (string.IsNullOrWhiteSpace(reader.ReadToEnd())) return obj;
            }

            using (FileStream fs = new FileStream(xmlFilePath, FileMode.Open))
            {
                obj = DeserializeObjectFromStream<T>(fs);
            }
            return obj;
        }
    }
}