using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Top.Api.Util
{
    public class XmlWriter
    {
        private StringBuilder buf = new StringBuilder();
        private Stack<object> calls = new Stack<object>();
        private string rootTagName;
        private Type stopType;
        private IDictionary<string, object> stopProps;

        public XmlWriter(string rootTagName, Type stopType)
        {
            this.rootTagName = rootTagName;
            this.stopType = stopType;
            if (stopType != null)
            {
                this.stopProps = GetStopProps(stopType);
            }
        }

        public XmlWriter()
            : this(null, null)
        {
        }

        public string Write(object obj)
        {
            buf.Length = 0;
            string tagName = rootTagName;
            if (tagName == null)
            {
                tagName = StringUtil.ToCamelStyle(obj.GetType().Name);
            }
            AddPair(tagName, obj);
            return buf.ToString();
        }

        private void Value(object obj)
        {
            if (obj == null || Cyclic(obj))
            {
                AddObject(null);
            }
            else
            {
                calls.Push(obj);
                Type objType = obj.GetType();
                if (typeof(IDictionary).IsAssignableFrom(objType))
                {
                    AddDictionary(obj as IDictionary);
                }
                else if (typeof(ICollection).IsAssignableFrom(objType))
                {
                    AddCollection(obj as ICollection);
                }
                else if (objType.IsArray)
                {
                    AddArray(obj);
                }
                else if (typeof(DateTime) == objType)
                {
                    AddDateTime((DateTime)obj);
                }
                else if (typeof(bool) == objType)
                {
                    AddBool((bool)obj);
                }
                else if (typeof(string) == objType || typeof(Type) == objType)
                {
                    AddString(obj);
                }
                else if (objType.IsPrimitive)
                {
                    AddString(obj);
                }
                else
                {
                    AddBean(obj);
                }
                calls.Pop();
            }
        }

        private bool Cyclic(object obj)
        {
            Stack<object>.Enumerator em = calls.GetEnumerator();
            while (em.MoveNext())
            {
                object called = em.Current;
                if (obj == called) return true;
            }
            return false;
        }

        private void AddBean(object obj)
        {
            bool isChildren = stopType != null && stopType.IsAssignableFrom(obj.GetType());
            PropertyInfo[] props = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty);
            foreach (PropertyInfo prop in props)
            {
                string name = prop.Name;
                if (isChildren && stopProps.ContainsKey(name))
                {
                    continue;
                }
                object value = prop.GetValue(obj, new object[] { });
                if (value != null)
                {
                    string newName = StringUtil.ToCamelStyle(name);
                    AddPair(newName, value);
                }
            }
        }

        private void AddDictionary(IDictionary obj)
        {
            foreach (DictionaryEntry entry in obj)
            {
                string strKey = entry.Key.ToString();
                AddPair(strKey, entry.Value);
            }
        }

        private void AddCollection(ICollection obj)
        {
            string tagName = null;
            foreach (object item in obj)
            {
                if (tagName == null)
                {
                    object[] apiListTypes = item.GetType().GetCustomAttributes(typeof(ApiListTypeAttribute), false);
                    if (apiListTypes != null && apiListTypes.Length > 0)
                    {
                        tagName = ((ApiListTypeAttribute) apiListTypes[0]).Value;
                    }
                    else
                    {
                        tagName = StringUtil.ToCamelStyle(obj.GetType().Name);
                    }
                }
                AddPair(tagName, item);
            }
        }

        private void AddArray(object obj)
        {
            string tagName = null;
            Array.ForEach(obj as object[], item =>
            {
                if (tagName == null)
                {
                    tagName = StringUtil.ToCamelStyle(obj.GetType().Name);
                }
                AddPair(tagName, item);
            });
        }

        private void AddBool(bool value)
        {
            AddObject(value ? "true" : "false");
        }

        private void AddDateTime(DateTime value)
        {
            AddObject(StringUtil.FormatDateTime(value));
        }

        private void AddString(object obj)
        {
            AddObject(StringUtil.EscapeXml(obj.ToString()));
        }

        private void AddObject(object obj)
        {
            buf.Append(obj);
        }

        private void AddPair(string name, object value)
        {
            StartTag(name);
            Value(value);
            EndTag(name);
        }

        private void StartTag(string tagName)
        {
            buf.Append("<");
            buf.Append(tagName);
            buf.Append(">");
        }

        private void EndTag(string tagName)
        {
            buf.Append("</");
            buf.Append(tagName);
            buf.Append(">");
        }

        private IDictionary<string, object> GetStopProps(Type type)
        {
            IDictionary<string, object> stopProps = new Dictionary<string, object>();
            PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty);
            foreach (PropertyInfo prop in props)
            {
                stopProps.Add(prop.Name, null);
            }
            return stopProps;
        }
    }
}
