using System;
using System.Text;
using System.IO;
using System.Xml;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using CommonLang.Property;
using CommonLang.IO;
using CommonLang.Log;
using CommonLang.Concurrent;

namespace CommonLang.Xml
{
    public enum XmlSerializableProperty : uint
    {
        Mark = 0,
        IgnoreClone = 0x0001,
        NoSerialize = 0x0002,
    }
    /// <summary>
    /// 标记当前Field或者Property是否参与Xml序列化
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class XmlSerializableAttribute : System.Attribute
    {
        private readonly XmlSerializableProperty Prop;
        public XmlSerializableAttribute(XmlSerializableProperty attr = XmlSerializableProperty.Mark)
        {
            this.Prop = attr;
        }
        public bool IgnoreClone { get { return (Prop & XmlSerializableProperty.IgnoreClone) != 0; } }
        public bool NoSerialize { get { return (Prop & XmlSerializableProperty.NoSerialize) != 0; } }
    }


    /// <summary>
    /// Xml序列化与反序列化
    /// </summary>
    public static class XmlUtil
    {
        private static Logger s_log;
        private static Logger log
        {
            get
            {
                if (s_log == null) { s_log = LoggerFactory.GetLogger("XmlUtil"); }
                return s_log;
            }
        }

        static public XmlDocument LoadXML(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                return LoadXML(ms);
            }
        }
        static public XmlDocument LoadXML(Stream input, bool autoDisposeStream = false)
        {
            try
            {
                using (XmlReader xml = XmlReader.Create(input))
                {
                    XmlDocument doc = new XmlDocument();
                    doc.Load(xml);
                    return doc;
                }
            }
            finally
            {
                if (autoDisposeStream)
                {
                    input.Close();
                    input.Dispose();
                }
            }
        }
        static public XmlDocument LoadXML(string path)
        {
            var stream = Resource.LoadDataAsStream(path);
            if (stream != null)
            {
                try
                {
                    return LoadXML(stream);
                }
                finally
                {
                    stream.Dispose();
                }
            }
            return null;
        }

        static public void SaveXML(string path, XmlDocument xml)
        {
            using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                XmlUtil.SaveXML(fs, xml, false);
            }
        }

        static public string ToString(XmlDocument doc)
        {
            using (StringWriter sw = new StringWriter())
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.Encoding = CUtils.UTF8;
                using (XmlWriter xml = XmlWriter.Create(sw, settings))
                {
                    doc.Save(xml);
                    xml.Flush();
                }
                return sw.ToString();
            }
        }

        static public XmlDocument FromString(string xmltext)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(xmltext);
            return doc;
        }

        static public bool SaveXML(String path, XmlDocument doc, out string errMessage)
        {
            try
            {
                using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Indent = true;
                    settings.Encoding = Encoding.UTF8;
                    using (XmlWriter xml = XmlWriter.Create(fs, settings))
                    {
                        doc.Save(xml);
                        xml.Flush();
                    }
                    errMessage = null;
                    fs.Close();
                }
            }
            catch(Exception e)
            {
                errMessage = e.Message;
            }
            return errMessage == null;
        }

        static public void SaveXML(Stream output, XmlDocument doc, bool autoDisposeStream)
        {
            try
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.Encoding = Encoding.UTF8;
                using (XmlWriter xml = XmlWriter.Create(output, settings))
                {
                    doc.Save(xml);
                    xml.Flush();
                }
            }
            finally
            {
                if (autoDisposeStream)
                {
                    output.Close();
                    output.Dispose();
                }
            }
        }

        static public string GetTextValue(XmlElement e)
        {
            if (e.FirstChild != null && e.FirstChild.NodeType == XmlNodeType.Text)
            {
                return (e.FirstChild as XmlText).Data;
            }
            else { return null; }
        }

        static public void SaveToXML(Stream output, object mData, bool autoDisposeStream = false)
        {
            try
            {
                Type type = mData.GetType();
                XmlDocument doc = XmlUtil.ObjectToXml(mData);
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;
                settings.Encoding = Encoding.UTF8;
                using (XmlWriter xml = XmlWriter.Create(output, settings))
                {
                    doc.Save(xml);
                    xml.Flush();
                }
            }
            finally
            {
                if (autoDisposeStream)
                {
                    output.Close();
                    output.Dispose();
                }
            }
        }

        //----------------------------------------------------------------------------------------------------------

        #region _converter_

        static public T XmlToObject<T>(XmlDocument doc, Action<Exception> error = null)
        {
            object ret = XmlToObject(typeof(T), doc, false, error);
            if (ret != null)
            {
                return (T)ret;
            }
            return default(T);
        }
        static public object XmlToObject(XmlDocument doc, Action<Exception> error = null)
        {
            XmlElement e = (XmlElement)doc.DocumentElement;
            Type type = GetTypeFromAttribute(e, "type");
            return XmlToObject(type, doc, false, error);
        }
        static public object XmlToObject(Type type, XmlDocument doc, Action<Exception> error = null)
        {
            XmlElement e = (XmlElement)doc.DocumentElement;
            return XmlToObject(type, doc, false, error);
        }
        static public object XmlToObject(Type type, XmlDocument doc, bool cloning, Action<Exception> error = null)
        {
            XmlElement e = (XmlElement)doc.DocumentElement;
            object data = ObjectFromXmlElement(e, type, cloning, error);
            return data;
        }

        static public XmlDocument ObjectToXml(object data, string root_name = null)
        {
            return ObjectToXml(data, root_name, false);
        }
        static public XmlDocument ObjectToXml(object data, string root_name, bool cloning)
        {
            Type type = data.GetType();
            if (root_name == null)
            {
                root_name = type.Name;
            }
            XmlDocument doc = new XmlDocument();
            XmlElement e = doc.CreateElement(root_name);
            ObjectToXmlElement(e, data, cloning);
            doc.AppendChild(e);
            return doc;
        }

        static public T CloneObject<T>(T src)
        {
            Type type = src.GetType();
            if (type.IsPrimitive)
            {
                return src;
            }
            else if (type.IsEnum)
            {
                return src;
            }
            else if (type.IsAssignableFrom(typeof(string)))
            {
                return src;
            }
            else if (type.IsClass || type.IsArray)
            {
                XmlDocument xml = XmlUtil.ObjectToXml(src, "cloning", true);
                T obj = (T)XmlUtil.XmlToObject(type, xml, true);
                return obj;
            }
            return src;
        }

        #endregion

        //----------------------------------------------------------------------------------------------------------

        #region _internal_




        static private void ObjectFieldsFromXMLElement(XmlElement data_element, object data, bool cloning, Action<Exception> error)
        {
            Type type = data.GetType();
            foreach (XmlNode fe in data_element.ChildNodes)
            {
                XmlElement fee = fe as XmlElement;
                FieldInfo fii = type.GetField(fe.Name);
                if (fii != null)
                {
                    object fd = ObjectFromXmlElement(fee, fii.FieldType, cloning, error);
                    fii.SetValue(data, fd);
                }
            }
        }

        static private void ObjectToXmlElement(XmlElement data_element, object data, bool cloning)
        {
            if (data != null)
            {
                Type type = data.GetType();
                if (type.IsPrimitive)
                {
                    data_element.InnerText = Parser.ObjectToString(data);
                }
                else if (type.IsEnum)
                {
                    data_element.InnerText = Parser.ObjectToString(data);
                }
                else if (type.IsAssignableFrom(typeof(string)))
                {
                    data_element.InnerText = Parser.ObjectToString(data);
                }
                else if (type.IsArray)
                {
                    ObjectToXMLElementArray(data_element, (Array)data, cloning);
                }
                else if (type.IsClass)
                {
                    XmlDocument doc = data_element.OwnerDocument;
                    if (type.GetInterface(typeof(IDictionary).Name) != null)
                    {
                        ObjectToXMLElementMap(data_element, (IDictionary)data, cloning);
                    }
                    else if (type.GetInterface(typeof(IList).Name) != null)
                    {
                        ObjectToXMLElementList(data_element, (IList)data, cloning);
                    }
                    else
                    {
                        SetTypeToAttribute(data_element, "type", type);
                        foreach (FieldInfo field in PropertyUtil.SortFields(type.GetFields()))
                        {
                            if (!field.IsStatic)
                            {
                                object fd = field.GetValue(data);
                                if (fd != null)
                                {
                                    XmlElement fe = doc.CreateElement(field.Name);
                                    ObjectToXmlElement(fe, fd, cloning);
                                    data_element.AppendChild(fe);
                                }
                            }
                        }
                        foreach (PropertyInfo property in PropertyUtil.SortProperties(type.GetProperties()))
                        {
                            XmlSerializableAttribute attr = PropertyUtil.GetAttribute<XmlSerializableAttribute>(property);
                            if (attr != null)
                            {
                                if (attr.NoSerialize)
                                {

                                }
                                else if (cloning && attr.IgnoreClone)
                                {

                                }
                                else
                                {
                                    object fd = property.GetValue(data, null);
                                    if (fd != null)
                                    {
                                        XmlElement fe = doc.CreateElement("property." + property.Name);
                                        ObjectToXmlElement(fe, fd, cloning);
                                        data_element.AppendChild(fe);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        static private object ObjectFromXmlElement(XmlElement data_element, Type type, bool cloning, Action<Exception> error)
        {
            try
            {
                Type s_type = GetTypeFromAttribute(data_element, "type", type);
                if (s_type != null)
                {
                    type = s_type;
                }

                if (type.IsPrimitive)
                {
                    return Parser.StringToObject(data_element.InnerText, type);
                }
                else if (type.IsEnum)
                {
                    return Parser.StringToObject(data_element.InnerText, type);
                }
                else if (type.IsAssignableFrom(typeof(string)))
                {
                    return data_element.InnerText;
                }
                else if (type.IsArray)
                {
                    return ObjectFromXMLElementArray(data_element, type, cloning, error);
                }
                else if (type.IsClass)
                {
                    if (type.GetInterface(typeof(IDictionary).Name) != null)
                    {
                        return ObjectFromXMLElementMap(data_element, type, cloning, error);
                    }
                    else if (type.GetInterface(typeof(IList).Name) != null)
                    {
                        return ObjectFromXMLElementList(data_element, type, cloning, error);
                    }
                    else
                    {
                        try
                        {
                            object data = ReflectionUtil.CreateInstance(type);
                            foreach (XmlNode fe in data_element.ChildNodes)
                            {
                                XmlElement fee = fe as XmlElement;
                                if (fe.Name.StartsWith("property."))
                                {
                                    PropertyInfo property = type.GetProperty(fe.Name.Substring("property.".Length));
                                    if (property != null)
                                    {
                                        XmlSerializableAttribute attr = PropertyUtil.GetAttribute<XmlSerializableAttribute>(property);
                                        if (attr != null)
                                        {
                                            if (attr.NoSerialize)
                                            {

                                            }
                                            else if (cloning && attr.IgnoreClone)
                                            {

                                            }
                                            else
                                            {
                                                object fd = ObjectFromXmlElement(fee, property.PropertyType, cloning, error);
                                                if (fd != null)
                                                {
                                                    property.SetValue(data, fd, null);
                                                }
                                            }
                                        }
                                    }
                                }
                                else
                                {
                                    FieldInfo fii = type.GetField(fe.Name);
                                    if (fii != null)
                                    {
                                        object fd = ObjectFromXmlElement(fee, fii.FieldType, cloning, error);
                                        if (fd != null)
                                        {
                                            fii.SetValue(data, fd);
                                        }
                                    }
                                }
                            }
                            return data;
                        }
                        catch (Exception err)
                        {
                            log.Warn(err.Message, err);
                            if (error != null) error(err);
                        }
                    }
                }
            }
            catch (Exception err)
            {
                log.Warn(err.Message, err);
                if (error != null) error(err);
            }
            return null;
        }

        static private void ObjectToXMLElementArray(XmlElement data_element, Array array, bool cloning)
        {
            XmlDocument doc = data_element.OwnerDocument;
            Type type = array.GetType();
            int rank = type.GetArrayRank();
            int[] ranges = new int[rank];
            for (int i = 0; i < rank; i++)
            {
                ranges[i] = array.GetLength(i);
            }
            SetTypeToAttribute(data_element, "type", type);
            SetTypeToAttribute(data_element, "element_type", type.GetElementType());
            data_element.SetAttribute("rank", rank + "");
            data_element.SetAttribute("ranges", Parser.ObjectToString(ranges));
            foreach (object k in array)
            {
                XmlElement ei = doc.CreateElement("element");
                ObjectToXmlElement(ei, k, cloning);
                data_element.AppendChild(ei);
            }
        }

        static private Array ObjectFromXMLElementArray(XmlElement data_element, Type type, bool cloning, Action<Exception> error)
        {
            int rank = int.Parse(data_element.GetAttribute("rank"));
            Type ctype = GetTypeFromAttribute(data_element, "type");
            Type etype = GetTypeFromAttribute(data_element, "element_type");
            int[] ranges = (int[])Parser.StringToObject(data_element.GetAttribute("ranges"), typeof(int[]));
            Array array = Array.CreateInstance(etype, ranges);
            int total_index = 0;
            foreach (XmlNode fe in data_element.ChildNodes)
            {
                XmlElement fee = fe as XmlElement;
                object fdd = ObjectFromXmlElement(fee, etype, cloning, error);
                int[] indices = CUtils.GetArrayRankIndex(ranges, total_index);
                array.SetValue(fdd, indices);
                total_index++;
            }
            return array;
        }


        static private void ObjectToXMLElementMap(XmlElement data_element, IDictionary map, bool cloning)
        {
            XmlDocument doc = data_element.OwnerDocument;
            Type type = map.GetType();
            if (type.IsGenericType)
            {
                SetTypeToAttribute(data_element, "key_type", type.GetGenericArguments()[0]);
                SetTypeToAttribute(data_element, "value_type", type.GetGenericArguments()[1]);
            }
            foreach (object k in map.Keys)
            {
                object v = map[k];
                XmlElement epair = doc.CreateElement("element");
                XmlElement ek = doc.CreateElement("key");
                XmlElement ev = doc.CreateElement("value");
                if (!type.IsGenericType)
                {
                    SetTypeToAttribute(ek, "type", k.GetType());
                    SetTypeToAttribute(ev, "type", v.GetType());
                }
                ObjectToXmlElement(ek, k, cloning);
                ObjectToXmlElement(ev, v, cloning);
                epair.AppendChild(ek);
                epair.AppendChild(ev);
                data_element.AppendChild(epair);
            }
        }

        static private IDictionary ObjectFromXMLElementMap(XmlElement data_element, Type type, bool cloning, Action<Exception> error)
        {
            Type k_type = GetTypeFromAttribute(data_element, "key_type");
            Type v_type = GetTypeFromAttribute(data_element, "value_type");
            IDictionary map = (IDictionary)Activator.CreateInstance(type);
            foreach (XmlNode fe in data_element.ChildNodes)
            {
                XmlElement epair = fe as XmlElement;
                XmlElement ek = epair.ChildNodes[0] as XmlElement;
                XmlElement ev = epair.ChildNodes[1] as XmlElement;
                k_type = GetTypeFromAttribute(ek, "type", k_type);
                v_type = GetTypeFromAttribute(ev, "type", v_type);
                object k = ObjectFromXmlElement(ek, k_type, cloning, error);
                object v = ObjectFromXmlElement(ev, v_type, cloning, error);
                map.Add(k, v);
            }
            return map;
        }

        static private void ObjectToXMLElementList(XmlElement data_element, IList list, bool cloning)
        {
            XmlDocument doc = data_element.OwnerDocument;
            Type type = list.GetType();
            if (type.IsGenericType)
            {
                SetTypeToAttribute(data_element, "element_type", type.GetGenericArguments()[0]);
            }
            foreach (object k in list)
            {
                XmlElement ei = doc.CreateElement("element");
                if (!type.IsGenericType)
                {
                    SetTypeToAttribute(ei, "type", k.GetType());
                }
                ObjectToXmlElement(ei, k, cloning);
                data_element.AppendChild(ei);
            }
        }

        static private IList ObjectFromXMLElementList(XmlElement data_element, Type type, bool cloning, Action<Exception> error)
        {
            Type element_type = GetTypeFromAttribute(data_element, "element_type");
            IList list = (IList)Activator.CreateInstance(type);
            foreach (XmlNode fe in data_element.ChildNodes)
            {
                XmlElement ei = fe as XmlElement;
                element_type = GetTypeFromAttribute(ei, "type", element_type);
                Object fd = ObjectFromXmlElement(ei, element_type, cloning, error);
                list.Add(fd);
            }
            return list;
        }

        static private void SetTypeToAttribute(XmlElement e, string name, Type type)
        {
            e.SetAttribute(name, type.FullName);
        }

        static private Type GetTypeFromAttribute(XmlElement e, string name)
        {
            string vtype = e.GetAttribute(name);
            if (vtype != null)
            {
                Type ret = ReflectionUtil.GetType(vtype);
                if (ret != null)
                {
                    return ret;
                }
            }
            return null;
        }
        static private Type GetTypeFromAttribute(XmlElement e, string name, Type defaultType)
        {
            string vtype = e.GetAttribute(name);
            if (!String.IsNullOrEmpty(vtype))
            {
                Type ret = ReflectionUtil.GetType(vtype);
                if (ret != null)
                {
                    return ret;
                }
            }
            return defaultType;
        }

        #endregion

        //----------------------------------------------------------------------------------------------------------

        #region _utils_

        public static bool TryGetAttribute(XmlNode e, string key, out string value, bool NotNull = true)
        {
            XmlAttribute attr = e.Attributes[key];
            if (attr != null)
            {
                if (NotNull && string.IsNullOrEmpty(attr.Value))
                {
                    value = null;
                    return false;
                }
                value = attr.Value;
                return true;
            }
            value = null;
            return false;
        }
        public static string GetAttribute(XmlNode e, string key, bool NotNull = true)
        {
            XmlAttribute attr = e.Attributes[key];
            if (attr != null)
            {
                if (NotNull && string.IsNullOrEmpty(attr.Value))
                {
                    return null;
                }
                return attr.Value;
            }
            return null;
        }


        public static XmlNode FindChild(XmlNode e, string childName)
        {
            foreach (XmlNode cc in e.ChildNodes)
            {
                if (cc.Name == childName)
                {
                    return cc;
                }
            }
            return null;
        }

        #endregion

        //----------------------------------------------------------------------------------------------------
    }
}