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

namespace FairyGUI.Utils
{
    public enum XMLTagType
    {
        Start,
        End,
        Void,
        CDATA,
        Comment,
        Instruction
    }

    /// <summary>
    /// 
    /// </summary>
    public class XMLIterator
    {
        public static string tagName;
        public static XMLTagType tagType;
        public static string lastTagName;

        static string source;
        static int sourceLen;
        static int parsePos;
        static int tagPos;
        static int tagLength;
        static int lastTagEnd;
        static bool attrParsed;
        static bool lowerCaseName;
        static StringBuilder buffer = new StringBuilder();
        static Dictionary<string, string> attributes = new Dictionary<string, string>();

        const string CDATA_START = "<![CDATA[";
        const string CDATA_END = "]]>";
        const string COMMENT_START = "<!--";
        const string COMMENT_END = "-->";

        public static void Begin(string source, bool lowerCaseName = false)
        {
            XMLIterator.source = source;
            XMLIterator.lowerCaseName = lowerCaseName;
            sourceLen = source.Length;
            parsePos = 0;
            lastTagEnd = 0;
            tagPos = 0;
            tagLength = 0;
            tagName = null;
        }

        public static bool NextTag()
        {
            int pos;
            char c;
            tagType = XMLTagType.Start;
            buffer.Length = 0;
            lastTagEnd = parsePos;
            attrParsed = false;
            lastTagName = tagName;

            while ((pos = source.IndexOf('<', parsePos)) != -1)
            {
                parsePos = pos;
                pos++;

                if (pos == sourceLen)
                    break;

                c = source[pos];
                if (c == '!')
                {
                    if (sourceLen > pos + 7 && source.Substring(pos - 1, 9) == CDATA_START)
                    {
                        pos = source.IndexOf(CDATA_END, pos);
                        tagType = XMLTagType.CDATA;
                        tagName = string.Empty;
                        tagPos = parsePos;
                        if (pos == -1)
                            tagLength = sourceLen - parsePos;
                        else
                            tagLength = pos + 3 - parsePos;
                        parsePos += tagLength;
                        return true;
                    }
                    else if (sourceLen > pos + 2 && source.Substring(pos - 1, 4) == COMMENT_START)
                    {
                        pos = source.IndexOf(COMMENT_END, pos);
                        tagType = XMLTagType.Comment;
                        tagName = string.Empty;
                        tagPos = parsePos;
                        if (pos == -1)
                            tagLength = sourceLen - parsePos;
                        else
                            tagLength = pos + 3 - parsePos;
                        parsePos += tagLength;
                        return true;
                    }
                    else
                    {
                        pos++;
                        tagType = XMLTagType.Instruction;
                    }
                }
                else if (c == '/')
                {
                    pos++;
                    tagType = XMLTagType.End;
                }
                else if (c == '?')
                {
                    pos++;
                    tagType = XMLTagType.Instruction;
                }

                for (; pos < sourceLen; pos++)
                {
                    c = source[pos];
                    if (Char.IsWhiteSpace(c) || c == '>' || c == '/')
                        break;
                }
                if (pos == sourceLen)
                    break;

                buffer.Append(source, parsePos + 1, pos - parsePos - 1);
                if (buffer.Length > 0 && buffer[0] == '/')
                    buffer.Remove(0, 1);

                bool singleQuoted = false, doubleQuoted = false;
                int possibleEnd = -1;
                for (; pos < sourceLen; pos++)
                {
                    c = source[pos];
                    if (c == '"')
                    {
                        if (!singleQuoted)
                            doubleQuoted = !doubleQuoted;
                    }
                    else if (c == '\'')
                    {
                        if (!doubleQuoted)
                            singleQuoted = !singleQuoted;
                    }

                    if (c == '>')
                    {
                        if (!(singleQuoted || doubleQuoted))
                        {
                            possibleEnd = -1;
                            break;
                        }

                        possibleEnd = pos;
                    }
                    else if (c == '<')
                        break;
                }
                if (possibleEnd != -1)
                    pos = possibleEnd;

                if (pos == sourceLen)
                    break;

                if (source[pos - 1] == '/')
                    tagType = XMLTagType.Void;

                tagName = buffer.ToString();
                if (lowerCaseName)
                    tagName = tagName.ToLower();
                tagPos = parsePos;
                tagLength = pos + 1 - parsePos;
                parsePos += tagLength;

                return true;
            }

            tagPos = sourceLen;
            tagLength = 0;
            tagName = null;
            return false;
        }

        public static string GetTagSource()
        {
            return source.Substring(tagPos, tagLength);
        }

        public static string GetRawText(bool trim = false)
        {
            if (lastTagEnd == tagPos)
                return string.Empty;
            else if (trim)
            {
                int i = lastTagEnd;
                for (; i < tagPos; i++)
                {
                    char c = source[i];
                    if (!char.IsWhiteSpace(c))
                        break;
                }

                if (i == tagPos)
                    return string.Empty;
                else
                    return source.Substring(i, tagPos - i).TrimEnd();
            }
            else
                return source.Substring(lastTagEnd, tagPos - lastTagEnd);
        }

        public static string GetText(bool trim = false)
        {
            if (lastTagEnd == tagPos)
                return string.Empty;
            else if (trim)
            {
                int i = lastTagEnd;
                for (; i < tagPos; i++)
                {
                    char c = source[i];
                    if (!char.IsWhiteSpace(c))
                        break;
                }

                if (i == tagPos)
                    return string.Empty;
                else
                    return XMLUtils.DecodeString(source.Substring(i, tagPos - i).TrimEnd());
            }
            else
                return XMLUtils.DecodeString(source.Substring(lastTagEnd, tagPos - lastTagEnd));
        }

        public static bool HasAttribute(string attrName)
        {
            if (!attrParsed)
            {
                attributes.Clear();
                ParseAttributes(attributes);
                attrParsed = true;
            }

            return attributes.ContainsKey(attrName);
        }

        public static string GetAttribute(string attrName)
        {
            if (!attrParsed)
            {
                attributes.Clear();
                ParseAttributes(attributes);
                attrParsed = true;
            }

            string value;
            if (attributes.TryGetValue(attrName, out value))
                return value;
            else
                return null;
        }

        public static string GetAttribute(string attrName, string defValue)
        {
            string ret = GetAttribute(attrName);
            if (ret != null)
                return ret;
            else
                return defValue;
        }

        public static int GetAttributeInt(string attrName)
        {
            return GetAttributeInt(attrName, 0);
        }

        public static int GetAttributeInt(string attrName, int defValue)
        {
            string value = GetAttribute(attrName);
            if (value == null || value.Length == 0)
                return defValue;

            int ret;
            if (int.TryParse(value, out ret))
                return ret;
            else
                return defValue;
        }

        public static float GetAttributeFloat(string attrName)
        {
            return GetAttributeFloat(attrName, 0);
        }

        public static float GetAttributeFloat(string attrName, float defValue)
        {
            string value = GetAttribute(attrName);
            if (value == null || value.Length == 0)
                return defValue;

            float ret;
            if (float.TryParse(value, out ret))
                return ret;
            else
                return defValue;
        }

        public static bool GetAttributeBool(string attrName)
        {
            return GetAttributeBool(attrName, false);
        }

        public static bool GetAttributeBool(string attrName, bool defValue)
        {
            string value = GetAttribute(attrName);
            if (value == null || value.Length == 0)
                return defValue;

            bool ret;
            if (bool.TryParse(value, out ret))
                return ret;
            else
                return defValue;
        }

        public static Dictionary<string, string> GetAttributes(Dictionary<string, string> result)
        {
            if (result == null)
                result = new Dictionary<string, string>();

            if (attrParsed)
            {
                foreach (KeyValuePair<string, string> kv in attributes)
                    result[kv.Key] = kv.Value;
            }
            else //这里没有先ParseAttributes再赋值给result是为了节省复制的操作
                ParseAttributes(result);

            return result;
        }

        public static Hashtable GetAttributes(Hashtable result)
        {
            if (result == null)
                result = new Hashtable();

            if (attrParsed)
            {
                foreach (KeyValuePair<string, string> kv in attributes)
                    result[kv.Key] = kv.Value;
            }
            else //这里没有先ParseAttributes再赋值给result是为了节省复制的操作
                ParseAttributes(result);

            return result;
        }

        static void ParseAttributes(IDictionary attrs)
        {
            string attrName;
            int valueStart;
            int valueEnd;
            bool waitValue = false;
            int quoted;
            buffer.Length = 0;
            int i = tagPos;
            int attrEnd = tagPos + tagLength;

            if (i < attrEnd && source[i] == '<')
            {
                for (; i < attrEnd; i++)
                {
                    char c = source[i];
                    if (Char.IsWhiteSpace(c) || c == '>' || c == '/')
                        break;
                }
            }

            for (; i < attrEnd; i++)
            {
                char c = source[i];
                if (c == '=')
                {
                    valueStart = -1;
                    valueEnd = -1;
                    quoted = 0;
                    for (int j = i + 1; j < attrEnd; j++)
                    {
                        char c2 = source[j];
                        if (Char.IsWhiteSpace(c2))
                        {
                            if (valueStart != -1 && quoted == 0)
                            {
                                valueEnd = j - 1;
                                break;
                            }
                        }
                        else if (c2 == '>')
                        {
                            if (quoted == 0)
                            {
                                valueEnd = j - 1;
                                break;
                            }
                        }
                        else if (c2 == '"')
                        {
                            if (valueStart != -1)
                            {
                                if (quoted != 1)
                                {
                                    valueEnd = j - 1;
                                    break;
                                }
                            }
                            else
                            {
                                quoted = 2;
                                valueStart = j + 1;
                            }
                        }
                        else if (c2 == '\'')
                        {
                            if (valueStart != -1)
                            {
                                if (quoted != 2)
                                {
                                    valueEnd = j - 1;
                                    break;
                                }
                            }
                            else
                            {
                                quoted = 1;
                                valueStart = j + 1;
                            }
                        }
                        else if (valueStart == -1)
                        {
                            valueStart = j;
                        }
                    }

                    if (valueStart != -1 && valueEnd != -1)
                    {
                        attrName = buffer.ToString();
                        if (lowerCaseName)
                            attrName = attrName.ToLower();
                        buffer.Length = 0;
                        attrs[attrName] = XMLUtils.DecodeString(source.Substring(valueStart, valueEnd - valueStart + 1));
                        i = valueEnd + 1;
                    }
                    else
                        break;
                }
                else if (!Char.IsWhiteSpace(c))
                {
                    if (waitValue || c == '/' || c == '>')
                    {
                        if (buffer.Length > 0)
                        {
                            attrName = buffer.ToString();
                            if (lowerCaseName)
                                attrName = attrName.ToLower();
                            attrs[attrName] = string.Empty;
                            buffer.Length = 0;
                        }

                        waitValue = false;
                    }

                    if (c != '/' && c != '>')
                        buffer.Append(c);
                }
                else
                {
                    if (buffer.Length > 0)
                        waitValue = true;
                }
            }
        }
    }
}
