using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Net;

namespace Misc
{
    public class SaveToXmlHepler
    {
        #region IBaseSaveToXml,ISaveToXml 列表
        public static void Regist(Type type, IBaseSaveToXml ie)
        {
            mBase.Regist(type, ie);
        }
        public static void Regist(Type type)
        {
            mBase.Regist(type);
        }
        private class BASE
        {
            public Dictionary<Type, IBaseSaveToXml> base_elements = new Dictionary<Type, IBaseSaveToXml>();
            public List<Type> elements = new List<Type>();
            public BASE()
            {
                //base object
                base_elements.Add(typeof(bool), new BaseSaveToXml_Bool());
                base_elements.Add(typeof(int), new BaseSaveToXml_Int());
                base_elements.Add(typeof(uint), new BaseSaveToXml_UInt32());
                base_elements.Add(typeof(double), new BaseSaveToXml_Double());
                base_elements.Add(typeof(float), new BaseSaveToXml_Float());
                base_elements.Add(typeof(string), new BaseSaveToXml_String());
                base_elements.Add(typeof(TimeSpan), new BaseSaveToXml_TimeSpan());
                base_elements.Add(typeof(DateTime), new BaseSaveToXml_DateTime());
                base_elements.Add(typeof(IPEndPoint), new BaseSaveToXml_IPEndPoint());
            }
            public void Regist(Type type, IBaseSaveToXml ie)
            {
                if (!base_elements.Keys.Contains(type))
                {
                    base_elements.Add(type, ie);
                }
            }
            public void Regist(Type type)
            {
                int cnt = (from e in elements where e.Equals(type) select e).Count();
                if (cnt == 0)
                    elements.Add(type);
            }

            /// <summary>
            /// Save, 支持数组
            /// </summary>
            /// <param name="name"></param>
            /// <param name="value"></param>
            /// <returns></returns>
            public XObject GetXObject(string name, object value) 
            {
                if (value == null)
                    return null;
                
                Type type = value.GetType();

                //if( type. System.Collections.IEnumerable
                if ((typeof(System.Collections.IList).IsAssignableFrom(type))||
                    (type.BaseType.Equals(typeof(Array))))
                {
                    XElement root = new XElement(name);
                    
                    System.Collections.IList list = (System.Collections.IList)value;

                    foreach (object obj in list) 
                    {
                        XObject xobj = GetXObject(obj.GetType().Name, obj);
                        
                        if (xobj != null)
                        {
                            root.Add(xobj);
                        }
                    }
                    return root;
                }

                else if (base_elements.Keys.Contains(type)) 
                {
                    IBaseSaveToXml e = base_elements[type];

                    return e.GetXObject(name, value);
                }
                else if (type.BaseType.Equals(typeof(Enum))) 
                {
                    return new XAttribute(name,  value.ToString());
                }
                //else if (typeof(ISaveToXml).IsAssignableFrom(type))
                else
                {
                    List<string> propertyNames = new List<string>();
                    if (typeof(ISaveToXml).IsAssignableFrom(type)) 
                    {
                        propertyNames.AddRange((value as ISaveToXml).GetSavePropertyNames());
                    }


                    if (propertyNames.Count() == 0)
                    {
                        propertyNames.AddRange(type.GetProperties().Select(new Func<System.Reflection.PropertyInfo, string>(
                            delegate(System.Reflection.PropertyInfo p)
                            {
                                return p.Name;
                            })));
                        propertyNames.AddRange(type.GetFields().Select(new Func<System.Reflection.FieldInfo, string>(
                            delegate(System.Reflection.FieldInfo p)
                            {
                                return p.Name;
                            })));
                    }

                    XElement root = new XElement(name);

                    foreach (string propertyName in propertyNames)
                    {
                        System.Reflection.PropertyInfo pi = type.GetProperty(propertyName);
                        if (pi != null)
                        {
                            object v = pi.GetValue(value, null);
                            XObject xobj = GetXObject(propertyName, v);
                            if (xobj != null)
                                root.Add(xobj);
                            continue;
                        }

                        System.Reflection.FieldInfo fi = type.GetField(propertyName);
                        if (fi != null)
                        {
                            object v = fi.GetValue(value);
                            XObject xobj = GetXObject(propertyName, v);
                            if (xobj != null)
                                root.Add(xobj);
                            continue;
                        }
                    }
                    return root;
                }               
            }
            //Load
            public bool TryParse(Type type, XObject xobj, ref object value) 
            {
                if ((typeof(System.Collections.IList).IsAssignableFrom(type)) ||
                    ((type.BaseType!=null) && (type.BaseType.Equals(typeof(Array)))))
                {
                    if (value == null)//数组不能为空
                        return false;

                    System.Collections.IList list = (System.Collections.IList)value;
                    list.Clear();
                    
                    //数组怎样移除？ 
                    //当为数组时, 如 int[] a=new int[3];
                    //执行 list.Clear() , a.length 还是=3， 但a[0]=0， 当类型不是常量 a[0]=null
                    
                    XElement e = xobj as XElement;//肯定是 XElement
                    int idx = 0;
                    foreach (XElement _e in e.Elements())
                    {
                        //可以从 _e.Name 解析出类型
                        {
                            var kvs = from __e in base_elements where __e.Key.Name.CompareTo(_e.Name.LocalName) == 0 select __e;
                            if (kvs.Count() > 0)
                            {
                                var kv = kvs.First();
                                object v = null;
                                kv.Value.TryParse(_e, ref v);
                                if (list.IsFixedSize)//固定长度
                                {
                                    if (idx < list.Count) 
                                    {
                                        list[idx] = v;
                                        idx++;
                                    }
                                }
                                else
                                {
                                    list.Add(v);
                                }
                                continue;
                            }
                        }
                        {
                            var kvs = from __e in elements where __e.Name.CompareTo(_e.Name.LocalName) == 0 select __e;
                            if (kvs.Count() > 0)
                            {
                                var kv = kvs.First();
                                object v = Activator.CreateInstance(kv);
                                TryParse(kv, _e, ref v);
                                if (list.IsFixedSize)//固定长度
                                {
                                    if (idx < list.Count)
                                    {
                                        list[idx] = v;
                                        idx++;
                                    }
                                }
                                else
                                {
                                    list.Add(v);
                                }
                                continue;
                            }
                        }

                    }
                    return true;
                }
                else if(base_elements.Keys.Contains(type))
                {
                    IBaseSaveToXml basesavetoxml = base_elements[type];
                    return basesavetoxml.TryParse(xobj, ref value);
                }

                else if ( (type.BaseType!=null) &&  (type.BaseType.Equals(typeof(Enum))))
                {
                    string xmlvalue="";
                    if (xobj is XElement)
                    {
                        XAttribute attr = (xobj as XElement).Attribute("value");

                        if (attr != null)
                            xmlvalue = attr.Value;
                    }
                    else if (xobj is XAttribute)
                    {

                        xmlvalue = (xobj as XAttribute).Value;
                    }
                    value = Enum.Parse(type, xmlvalue);

                    return true;
                }
                else// if (typeof(ISaveToXml).IsAssignableFrom(type))
                {
                    XElement e = xobj as XElement;//肯定是 XElement

                    foreach (XAttribute attr in e.Attributes())
                    {
                        System.Reflection.PropertyInfo pi = type.GetProperty(attr.Name.LocalName);
                        if (pi != null)
                        {
                            Type propertyType = pi.PropertyType;

                            object obj = pi.GetValue(value, null);
                            TryParse(propertyType, attr, ref obj);
                            try
                            {
                                pi.SetValue(value, obj, null);
                            }
                            catch 
                            {
                                //失败，不处理
                            }
                            continue;
                        }
                        System.Reflection.FieldInfo fi = type.GetField(attr.Name.LocalName);
                        if (fi != null)
                        {
                            Type propertyType = fi.FieldType;

                            object obj = fi.GetValue(value);
                            TryParse(propertyType, attr, ref obj);
                            fi.SetValue(value, obj);
                            continue;
                        }

                    }
                    foreach (XElement element in e.Elements())
                    {
                        System.Reflection.PropertyInfo pi = type.GetProperty(element.Name.LocalName);
                        if (pi != null)
                        {
                            Type propertyType = pi.PropertyType;

                            object obj = pi.GetValue(value, null);
                            TryParse(propertyType, element, ref obj);
                        }
                        System.Reflection.FieldInfo fi = type.GetField(element.Name.LocalName);
                        if (fi != null)
                        {
                            Type propertyType = fi.FieldType;

                            object obj = fi.GetValue(value);
                            TryParse(propertyType, element, ref obj);
                            fi.SetValue(value, obj);
                            continue;
                        }
                    }
                    return true;
                }
            }
            
        }
        private static BASE mBase = new BASE();
        #endregion

        //数据加载到指定 object
        public static bool Load(string path, Object obj)
        {
            Type type = obj.GetType();
            //if (!typeof(ISaveToXml).IsAssignableFrom(type))
            //{
            //    return false;
            //}

            SaveToXmlHepler.Regist(obj.GetType());

            if (!System.IO.File.Exists(path))
            {
                return Save(path, obj);
            }
            XElement root = null;
            try
            {
                root = XElement.Load(path);
            }
            catch (Exception e) 
            {
            
            }
            if (root == null)
            {
                return Save(path, obj);
            }

            //从 mBase 获取 ISaveToXml 列表 信息
            mBase.TryParse(type, root, ref obj);
            return true;
        }
        public static bool Save(string path, Object obj)
        {
            if (obj == null)//没数据，不用保存
                return false;
            Type type = obj.GetType();

            //SaveToXmlHepler.Regist(obj.GetType());//没用，都要求在类中 static classname(){ SaveToXmlHepler.Regist(typeof(classname));  }


            XObject xobj = mBase.GetXObject(obj.GetType().Name, obj);


            string dirpath=System.IO.Path.GetDirectoryName(path);
            if (!string.IsNullOrEmpty(dirpath))
            {
                if (!System.IO.Directory.Exists(dirpath))
                {
                    System.IO.Directory.CreateDirectory(dirpath);
                }
            }

            if (xobj != null)
                (xobj as XElement).Save(path);
            return true;
        }
    }
    public interface ISaveToXml 
    {
        /// <summary>
        /// 获取需要保存的属性名字列表
        /// </summary>
        /// <returns></returns>
        string[] GetSavePropertyNames(); 
    }

    public interface IBaseSaveToXml
    {
        XObject GetXObject(string name, object value);
        bool TryParse(XObject xobj, ref object value);
    }
    #region base object
    class BaseSaveToXml_UInt32 : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(int).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public virtual bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            uint result = 0;

            if (uint.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_Int : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if(name.CompareTo(typeof(int).Name)==0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public virtual bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue="";

            if (xobj is XElement) 
            {
                XAttribute attr = (xobj as XElement).Attribute("value");
                
                if(attr!=null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }
            
            int result = 0;
            
            if (int.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_String : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if(name.CompareTo(typeof(string).Name)==0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            value = xmlvalue;
            return true;

        }
    }
    class BaseSaveToXml_Double : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(double).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            double result = 0;

            if (double.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_Float : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(double).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            float result = 0;

            if (float.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_Bool : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(bool).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            bool result = false;

            if (bool.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_TimeSpan : IBaseSaveToXml
    {

        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(TimeSpan).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            TimeSpan result = new TimeSpan(1);

            if (TimeSpan.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_DateTime : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(DateTime).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            DateTime result = DateTime.MinValue;

            if (DateTime.TryParse(xmlvalue, out result))
            {
                value = result;
                return true;
            }

            return false;
        }
    }
    class BaseSaveToXml_IPEndPoint : IBaseSaveToXml
    {
        public XObject GetXObject(string name, object value)
        {
            if (name.CompareTo(typeof(IPEndPoint).Name) == 0)
            {
                return new XElement(name, GetXObject("value", value));
            }
            else
            {
                return new XAttribute(name, value.ToString());
            }
        }
        public bool TryParse(XObject xobj, ref object value)
        {
            string xmlvalue = "";

            if (xobj is XElement)
            {
                XAttribute attr = (xobj as XElement).Attribute("value");

                if (attr != null)
                    xmlvalue = attr.Value;
            }
            else if (xobj is XAttribute)
            {
                xmlvalue = (xobj as XAttribute).Value;
            }

            IPEndPoint result = Misc.StringConverter.ToIPEndPoint(xmlvalue);
            value = result;
            return true;
        }
    }

    #endregion

    #region 测试
    public enum Sex
    {
        Male,
        FeMale
    }

    public class ISaveToXml_Test// : ISaveToXml
    {
        public int A { get; set; }

        private List<int> _B = new List<int>();
        public List<int> B
        {
            get { return _B; }
        }

        private int[] _C = new int[3] { 0, 0, 2 };
        public int[] C
        {
            get { return _C; }
        }

        public Sex Sex;



        #region ISaveToXml 成员

        //public string[] GetSavePropertyNames()
        //{
        //    return new string[]{
        //            "A","B","C","Sex"};
        //}
        #endregion
        public static void Main()
        {
            ISaveToXml_Test test = new ISaveToXml_Test() { A = 1, Sex = Sex.FeMale };
            test.B.Add(3);
            test.B.Add(4);

            test.C[1] = 10;
            SaveToXmlHepler.Save(@"D:\123.xml", test);

            test.C[1] = 3;

            SaveToXmlHepler.Load(@"D:\123.xml", test);
        }
        
    }
    #endregion
}
