#region --- License ---
/* Copyright (c) 2006, 2007 Stefanos Apostolopoulos
 * See license.txt for license info
 */
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.XPath;

namespace Bind.Structures
{
    #region class Enum

    public class Enum
    {
        internal static EnumCollection GLEnums = new EnumCollection();
        internal static EnumCollection AuxEnums = new EnumCollection();

        static StringBuilder translator = new StringBuilder();
        string _name;
        static bool enumsLoaded;

        #region Initialize

        internal static void Initialize(string enumFile, string enumextFile, string auxFile)
        {
            Initialize(enumFile, enumextFile);

            if (!String.IsNullOrEmpty(auxFile))
                using (StreamReader sr = new StreamReader(Path.Combine(Settings.InputPath, auxFile)))
                {
                    AuxEnums = MainClass.Generator.ReadEnums(sr);
                }
        }

        internal static void Initialize(string enumFile, string enumextFile)
        {
            if (!enumsLoaded)
            {
                if (!String.IsNullOrEmpty(enumFile))
                {
                    using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, enumFile))
                    {
                        GLEnums = MainClass.Generator.ReadEnums(sr);
                    }
                }

                if (!String.IsNullOrEmpty(enumextFile))
                {
                    using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, enumextFile))
                    {
                        foreach (Enum e in MainClass.Generator.ReadEnums(sr).Values)
                        {
                            //enums.Add(e.Name, e);
                            Utilities.Merge(GLEnums, e);
                        }
                    }
                }

                enumsLoaded = true;
            }
        }

        #endregion

        #region Constructors

        public Enum()
        { }

        public Enum(string name)
        {
            Name = name;
        }

        #endregion

        #region Public Members

        // Returns true if the enum contains a collection of flags, i.e. 1, 2, 4, 8, ...
        public bool IsFlagCollection
        {
            get
            {
                // It seems that all flag collections contain "Mask" in their names.
                // This looks like a heuristic, but it holds 100% in practice
                // (checked all enums to make sure).
                return Name.Contains("Mask");
            }
        }

        #region public string Name

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        #endregion

        #region ConstantCollection

        Dictionary<string, Constant> _constant_collection = new Dictionary<string, Constant>();

        public IDictionary<string, Constant> ConstantCollection
        {
            get { return _constant_collection; }
        }

        #endregion

        #region TranslateName

        public static string TranslateName(string name)
        {
            if (String.IsNullOrEmpty(name))
                return name;

            if (Utilities.Keywords.Contains(name))
                return name;

            translator.Remove(0, translator.Length);    // Trick to avoid allocating a new StringBuilder.

            // Translate the constant's name to match .Net naming conventions
            if ((Settings.Compatibility & Settings.Legacy.NoAdvancedEnumProcessing) == Settings.Legacy.None)
            {
                bool is_after_underscore_or_number = true;    // Detect if we just passed a '_' or a number and make the next char
                                                    // uppercase.
                bool is_previous_uppercase = false; // Detect if previous character was uppercase, and turn
                                                    // the current one to lowercase.

                foreach (char c in name)
                {
                    char char_to_add;
                    if (c == '_' || c == '-')
                        is_after_underscore_or_number = true;
                    else 
                    {
                        if (Char.IsDigit(c))
                            is_after_underscore_or_number = true;
                        char_to_add = is_after_underscore_or_number ? Char.ToUpper(c) :
                            is_previous_uppercase ? Char.ToLower(c) : c;
                        is_previous_uppercase = Char.IsUpper(c);
                        translator.Append(char_to_add);
                        is_after_underscore_or_number = false;
                    }
                }

                translator[0] = Char.ToUpper(translator[0]);
            }
            else
                translator.Append(name);

            translator.Replace("Pname", "PName");
            translator.Replace("SRgb", "Srgb");

            string ret = translator.ToString();
            if (ret.StartsWith(Settings.EnumPrefix))
                return ret.Substring(Settings.EnumPrefix.Length);
            return ret;
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            List<Constant> constants = new List<Constant>(ConstantCollection.Values);
            constants.Sort(delegate(Constant c1, Constant c2)
            {
                int ret = String.Compare(c1.Value, c2.Value);
                if (ret == 0)
                    return String.Compare(c1.Name, c2.Name);
                return ret;
            });

            if (IsFlagCollection)
                sb.AppendLine("[Flags]");
            sb.AppendLine("public enum " + Name);
            sb.AppendLine("{");

            foreach (Constant c in constants)
            {
                sb.Append("    ");
                sb.Append(c.ToString());
                if (!String.IsNullOrEmpty(c.ToString()))
                    sb.AppendLine(",");
            }
            sb.Append("}");

            return sb.ToString();
        }

        #endregion

        #endregion
    }

    #endregion

    #region class EnumCollection

    public class EnumCollection : SortedDictionary<string, Enum>
    {
        internal void AddRange(EnumCollection enums)
        {
            foreach (Enum e in enums.Values)
                Utilities.Merge(this, e);
        }

        internal void Translate(XPathDocument overrides)
        {
            if (overrides == null)
                throw new ArgumentNullException("overrides");

            string path = "/overrides/enum[@name='{0}']";

            // Translate enum names.
            {
                List<string> keys_to_update = new List<string>();
                foreach (Enum e in Values)
                {
                    string name = e.Name;

                    XPathNavigator enum_override = overrides.CreateNavigator().SelectSingleNode(String.Format(path, name));
                    if (enum_override != null)
                    {
                        XPathNavigator name_override = enum_override.SelectSingleNode("name");
                        if (name_override != null)
                        {
                            name = name_override.Value;
                        }
                    }

                    name = Enum.TranslateName(name);
                    if (name != e.Name)
                    {
                        keys_to_update.Add(e.Name);
                        e.Name = name;
                    }
                }

                foreach (string name in keys_to_update)
                {
                    Enum e = this[name];
                    Remove(name);
                    Add(e.Name, e);
                }

                keys_to_update = null;
            }

            foreach (Enum e in Values)
            {
                XPathNavigator enum_override = overrides.CreateNavigator().SelectSingleNode(String.Format(path, e.Name));
                foreach (Constant c in e.ConstantCollection.Values)
                {
                    if (enum_override != null)
                    {
                        XPathNavigator constant_override = enum_override.SelectSingleNode(String.Format("token[@name='{0}']", c.PreviousName)) ??
                            enum_override.SelectSingleNode(String.Format("token[@name={0}]", c.Name));
                        if (constant_override != null)
                        {
                            foreach (XPathNavigator node in constant_override.SelectChildren(XPathNodeType.Element))
                            {
                                switch (node.Name)
                                {
                                    case "name": c.Name = (string)node.TypedValue; break;
                                    case "value": c.Value = (string)node.TypedValue; break;
                                }
                            }
                        }
                    }

                    // There are cases when a value is an aliased constant, with no enum specified.
                    // (e.g. FOG_COORD_ARRAY_TYPE = GL_FOG_COORDINATE_ARRAY_TYPE)
                    // In this case try searching all enums for the correct constant to alias (stupid opengl specs).
                    if (String.IsNullOrEmpty(c.Reference) && !Char.IsDigit(c.Value[0]))
                    {
                        foreach (Enum @enum in Values)
                        {
                            // Skip generic GLenum
                            if (@enum.Name == "GLenum")
                                continue;

                            if (@enum.ConstantCollection.ContainsKey(c.Value))
                            {
                                c.Reference = @enum.Name;
                                break;
                            }
                        }
                    }
                }
            }

            foreach (Enum e in Values)
            {
            restart:
                foreach (Constant c in e.ConstantCollection.Values)
                {
                    bool result = Constant.TranslateConstantWithReference(c, Enum.GLEnums, Enum.AuxEnums);
                    if (!result)
                    {
                        e.ConstantCollection.Remove(c.Name);
                        goto restart;
                    }
                }
            }

            if (Settings.DropMultipleTokens)
            {
                // When there are multiple tokens with the same value but different extension
                // drop the duplicates. Order of preference: core > ARB > EXT > vendor specific

                List<Constant> removed_tokens = new List<Constant>();

                foreach (Enum e in Values)
                {
                    if (e.Name == "All")
                        continue;

                    // This implementation is a not very bright O(n^2).
                    foreach (Constant c in e.ConstantCollection.Values)
                    {
                        foreach (Constant c2 in e.ConstantCollection.Values)
                        {
                            if (c.Name != c2.Name && c.Value == c2.Value)
                            {
                                int prefer = OrderOfPreference(Utilities.GetGL2Extension(c.Name), Utilities.GetGL2Extension(c2.Name));
                                if (prefer == -1)
                                    removed_tokens.Add(c2);
                                else if (prefer == 1)
                                    removed_tokens.Add(c);
                            }
                        }
                    }

                    foreach (Constant c in removed_tokens)
                        e.ConstantCollection.Remove(c.Name);
                    removed_tokens.Clear();
                }
            }
        }

        // Return -1 for ext1, 1 for ext2 or 0 if no preference.
        int OrderOfPreference(string ext1, string ext2)
        {
            // If one is empty and the other not, prefer the empty one (empty == core)
            // Otherwise check for Arb and Ext. To reuse the logic for the
            // empty check, let's try to remove first Arb, then Ext from the strings.
            int ret = PreferEmpty(ext1, ext2);
            if (ret != 0)
                return ret;
            
            ext1 = ext1.Replace("Arb", ""); ext2 = ext2.Replace("Arb", "");
            ret = PreferEmpty(ext1, ext2);
            if (ret != 0)
                return ret;
            
            ext1 = ext1.Replace("Ext", ""); ext2 = ext2.Replace("Ext", "");
            return PreferEmpty(ext1, ext2);
        }

        // Prefer the empty string over the non-empty.
        int PreferEmpty(string ext1, string ext2)
        {
            if (String.IsNullOrEmpty(ext1) && !String.IsNullOrEmpty(ext2))
                return -1;
            else if (String.IsNullOrEmpty(ext2) && !String.IsNullOrEmpty(ext1))
                return 1;
            else
                return 0;
        }

        new bool TryGetValue(string key, out Enum value)
        {
            return base.TryGetValue(key, out value);
        }
    }

    #endregion
}
