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

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.XPath;

namespace Bind.Structures
{
    /// <summary>
    /// Represents an opengl function.
    /// The return value, function name, function parameters and opengl version can be retrieved or set.
    /// </summary>
    public class Delegate : IComparable<Delegate>
    {
        internal static DelegateCollection Delegates;

        private static bool delegatesLoaded;
        bool? cls_compliance_overriden;

        protected static Regex endings = new Regex(@"((((d|f|fi)|u?[isb])_?v?)|v)", RegexOptions.Compiled | RegexOptions.RightToLeft);
        protected static Regex endingsNotToTrim = new Regex("(ib|[tdrey]s|[eE]n[vd]|bled|Flag|Tess|Status|Pixels|Instanced|Indexed|Varyings|Boolean|IDs)", RegexOptions.Compiled | RegexOptions.RightToLeft);

        // Add a trailing v to functions matching this regex. Used to differntiate between overloads taking both
        // a 'type' and a 'ref type' (such overloads are not CLS Compliant).
        // The default Regex matches no functions. Create a new Regex in Bind.Generator classes to override the default behavior. 
        internal static Regex endingsAddV = new Regex("^0", RegexOptions.Compiled);

        
        #region internal static void Initialize(string glSpec, string glSpecExt)
        
        internal static void Initialize(string glSpec, string glSpecExt)
        {
            if (!delegatesLoaded)
            {
                using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, glSpec))
                {
                    Delegates = MainClass.Generator.ReadDelegates(sr);
                }

                if (!String.IsNullOrEmpty(glSpecExt))
                {
                    using (StreamReader sr = Utilities.OpenSpecFile(Settings.InputPath, glSpecExt))
                    {
                        foreach (Delegate d in MainClass.Generator.ReadDelegates(sr).Values)
                        {
                            Utilities.Merge(Delegates, d);
                        }
                    }
                }
                Console.WriteLine("Enforcing CLS compliance.");
                MarkCLSCompliance(Function.Wrappers);
                delegatesLoaded = true;
            }
        }

        #endregion
        
        #region --- Constructors ---

        public Delegate()
        {
            Parameters = new ParameterCollection();
        }

        public Delegate(Delegate d)
        {
            Category = d.Category;
            Name = d.Name;
            Parameters = new ParameterCollection(d.Parameters);
            ReturnType = new Type(d.ReturnType);
            Version = d.Version;
            //this.Version = !String.IsNullOrEmpty(d.Version) ? new string(d.Version.ToCharArray()) : "";
        }

        #endregion

        #region --- Properties ---

        #region public bool CLSCompliant

        /// <summary>
        ///  Gets the CLSCompliant property. True if the delegate is not CLSCompliant.
        /// </summary>
        public virtual bool CLSCompliant
        {
            get
            {
                if (cls_compliance_overriden != null)
                    return (bool)cls_compliance_overriden;

                if (Unsafe)
                    return false;

                if (!ReturnType.CLSCompliant)
                    return false;

                foreach (Parameter p in Parameters)
                {
                    if (!p.CLSCompliant)
                        return false;
                }
                return true;
            }
            set
            {
                cls_compliance_overriden = value;
            }
        }

        #endregion

        #region public string Category

        private string _category;

        public string Category
        {
            get { return _category; }
            set { _category = Enum.TranslateName(value); }
        }

        #endregion

        #region public bool NeedsWrapper

        /// <summary>
        /// Gets a value that indicates whether this function needs to be wrapped with a Marshaling function.
        /// This flag is set if a function contains an Array parameter, or returns
        /// an Array or string.
        /// </summary>
        public bool NeedsWrapper
        {
            get
            {
                // TODO: Add special cases for (Get)ShaderSource.

                if (ReturnType.WrapperType != WrapperTypes.None)
                    return true;

                foreach (Parameter p in Parameters)
                {
                    if (p.WrapperType != WrapperTypes.None)
                        return true;
                }

                return false;
            }
        }

        #endregion

        #region public virtual bool Unsafe

        /// <summary>
        /// True if the delegate must be declared as 'unsafe'.
        /// </summary>
        public virtual bool Unsafe
        {
            //get { return @unsafe; }
            //set { @unsafe = value; }
            get
            {
                //if ((Settings.Compatibility & Settings.Legacy.NoPublicUnsafeFunctions) != Settings.Legacy.None)
                //    return false;

                if (ReturnType.Pointer != 0)
                    return true;

                foreach (Parameter p in Parameters)
                {
                    if (p.Pointer != 0)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        #endregion

        #region public Parameter ReturnType

        Type _return_type = new Type();
        /// <summary>
        /// Gets or sets the return value of the opengl function.
        /// </summary>
        public Type ReturnType
        {
            get { return _return_type; }
            set
            {
                _return_type = value;
            }
        }

        #endregion

        #region public virtual string Name

        string _name;
        /// <summary>
        /// Gets or sets the name of the opengl function.
        /// </summary>
        public virtual string Name
        {
            get { return _name; }
            set
            {
                if (!String.IsNullOrEmpty(value))
                {
                    _name = value.Trim();
                }
            }
        }

        #endregion

        #region public ParameterCollection Parameters

        ParameterCollection _parameters;

        public ParameterCollection Parameters
        {
            get { return _parameters; }
            set { _parameters = value; }
        }

        #endregion

        #region public string Version

        string _version;

        /// <summary>
        /// Defines the opengl version that introduced this function.
        /// </summary>
        public string Version
        {
            get { return _version; }
            set { _version = value; }
        }

        #endregion

        #region public bool Extension

        string _extension;

        public string Extension
        {
            //get { return _extension; }
            //set { _extension = value; }
            get
            {
                if (!String.IsNullOrEmpty(Name))
                {
                    _extension = Utilities.GetGL2Extension(Name);
                    return String.IsNullOrEmpty(_extension) ? "Core" : _extension;
                }
                else
                {
                    return null;
                }
            }
        }

        #endregion

        #endregion

        #region --- Strings ---

        #region public string CallString()

        public string CallString()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(Settings.DelegatesClass);
            sb.Append(".");
            sb.Append(Settings.FunctionPrefix);
            sb.Append(Name);
            sb.Append(Parameters.CallString());

            return sb.ToString();
        }

        #endregion

        /// <summary>
        /// Returns a string representing the full non-delegate declaration without decorations.
        /// (ie "(unsafe) void glXxxYyy(int a, float b, IntPtr c)"
        /// </summary>
        #region public string DeclarationString()

        public string DeclarationString()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(Unsafe ? "unsafe " : "");
            sb.Append(ReturnType);
            sb.Append(" ");
            sb.Append(Name);
            sb.Append(Parameters.ToString(true));

            return sb.ToString();
        }

        #endregion

        #region override public string ToString()

        /// <summary>
        /// Returns a string representing the full delegate declaration without decorations.
        /// (ie "(unsafe) void delegate glXxxYyy(int a, float b, IntPtr c)"
        /// </summary>
        override public string ToString()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append(Unsafe ? "unsafe " : "");
            sb.Append("delegate ");
            sb.Append(ReturnType);
            sb.Append(" ");
            sb.Append(Name);
            sb.Append(Parameters.ToString(true));

            return sb.ToString();
        }

        #endregion

        public Delegate GetCLSCompliantDelegate()
        {
            Delegate f = new Delegate(this);

            for (int i = 0; i < f.Parameters.Count; i++)
            {
                f.Parameters[i].CurrentType = f.Parameters[i].GetCLSCompliantType();
            }

            f.ReturnType.CurrentType = f.ReturnType.GetCLSCompliantType();

            return f;
        }

        #endregion

        #region --- Wrapper Creation ---

        #region MarkCLSCompliance

        static void MarkCLSCompliance(FunctionCollection collection)
        {
            foreach (List<Function> wrappers in Function.Wrappers.Values)
            {
            restart:
                for (int i = 0; i < wrappers.Count; i++)
                {
                    for (int j = i + 1; j < wrappers.Count; j++)
                    {
                        if (wrappers[i].TrimmedName == wrappers[j].TrimmedName && wrappers[i].Parameters.Count == wrappers[j].Parameters.Count)
                        {
                            bool function_i_is_problematic = false;
                            bool function_j_is_problematic = false;

                            int k;
                            for (k = 0; k < wrappers[i].Parameters.Count; k++)
                            {
                                if (wrappers[i].Parameters[k].CurrentType != wrappers[j].Parameters[k].CurrentType)
                                    break;

                                if (wrappers[i].Parameters[k].DiffersOnlyOnReference(wrappers[j].Parameters[k]))
                                    if (wrappers[i].Parameters[k].Reference)
                                        function_i_is_problematic = true;
                                    else
                                        function_j_is_problematic = true;
                            }

                            if (k == wrappers[i].Parameters.Count)
                            {
                                if (function_i_is_problematic)
                                    wrappers.RemoveAt(i);
                                //wrappers[i].CLSCompliant = false;
                                if (function_j_is_problematic)
                                    wrappers.RemoveAt(j);
                                //wrappers[j].CLSCompliant = false;

                                if (function_i_is_problematic || function_j_is_problematic)
                                    goto restart;
                            }
                        }
                    }
                }
            }
        }

        #endregion

        #region CreateWrappers

        void CreateWrappers()
        {
            List<Function> wrappers = new List<Function>();
            CreateNormalWrappers(wrappers);

            // Generate wrappers using the untyped enum parameters, if necessary.
            if ((Settings.Compatibility & Settings.Legacy.KeepUntypedEnums) != 0)
            {
                CreateUntypedEnumWrappers(wrappers);
            }
            
            // Add CLS-compliant overloads for non-CLS compliant wrappers.
            CreateCLSCompliantWrappers(wrappers);
        }

        private static void CreateCLSCompliantWrappers(List<Function> wrappers)
        {
            // If the function is not CLS-compliant (e.g. it contains unsigned parameters)
            // we need to create a CLS-Compliant overload. However, we should only do this
            // iff the opengl function does not contain unsigned/signed overloads itself
            // to avoid redefinitions.
            foreach (Function f in wrappers)
            {
                Function.Wrappers.AddChecked(f);

                if (!f.CLSCompliant)
                {
                    Function cls = new Function(f);

                    cls.Body.Clear();
                    cls.CreateBody(true);

                    bool modified = false;
                    for (int i = 0; i < f.Parameters.Count; i++)
                    {
                        cls.Parameters[i].CurrentType = cls.Parameters[i].GetCLSCompliantType();
                        if (cls.Parameters[i].CurrentType != f.Parameters[i].CurrentType)
                            modified = true;
                    }

                    if (modified)
                        Function.Wrappers.AddChecked(cls);
                }
            }
        }

        private void CreateUntypedEnumWrappers(List<Function> wrappers)
        {
            Function f = new Function(this);
            var modified = false;
            f.Parameters = new ParameterCollection(f.Parameters.Select(p =>
            {
                if (p.IsEnum && p.CurrentType != Settings.CompleteEnumName)
                {
                    p.CurrentType = Settings.CompleteEnumName;
                    modified = true;
                }
                return p;
            }));
            if (modified)
            {
                f.WrapReturnType();
                f.WrapParameters(wrappers);
            }
        }

        void CreateNormalWrappers(List<Function> wrappers)
        {
            Function f = new Function(this);
            f.WrapReturnType();
            f.WrapParameters(wrappers);
        }

        #endregion

        #region TrimName

        // Trims unecessary suffices from the specified OpenGL function name.
        protected static string TrimName(string name, bool keep_extension)
        {
            string trimmed_name = Utilities.StripGL2Extension(name);
            string extension = Utilities.GetGL2Extension(name);

            // Note: some endings should not be trimmed, for example: 'b' from Attrib.
            // Check the endingsNotToTrim regex for details.
            Match m = endingsNotToTrim.Match(trimmed_name);
            if ((m.Index + m.Length) != trimmed_name.Length)
            {
                m = endings.Match(trimmed_name);

                if (m.Length > 0 && m.Index + m.Length == trimmed_name.Length)
                {
                    // Only trim endings, not internal matches.
                    if (m.Value[m.Length - 1] == 'v' && endingsAddV.IsMatch(name) &&
                        !name.StartsWith("Get") && !name.StartsWith("MatrixIndex"))
                    {
                        // Only trim ending 'v' when there is a number
                        trimmed_name = trimmed_name.Substring(0, m.Index) + "v";
                    }
                    else
                    {
                        if (!trimmed_name.EndsWith("xedv"))
                            trimmed_name = trimmed_name.Substring(0, m.Index);
                        else
                            trimmed_name = trimmed_name.Substring(0, m.Index + 1);
                    }
                }
            }

            if (keep_extension)
                return trimmed_name + extension;
            else
                return trimmed_name;
        }

        #endregion

        #region TranslateReturnType

        // Translates the opengl return type to the equivalent C# type.
        //
        // First, we use the official typemap (gl.tm) to get the correct type.
        // Then we override this, when it is:
        // 1) A string (we have to use Marshal.PtrToStringAnsi, to avoid heap corruption)
        // 2) An array (translates to IntPtr)
        // 3) A generic object or void* (translates to IntPtr)
        // 4) A GLenum (translates to int on Legacy.Tao or GL.Enums.GLenum otherwise).
        // Return types must always be CLS-compliant, because .Net does not support overloading on return types.
        void TranslateReturnType(XPathNavigator overrides, XPathNavigator function_override)
        {
            if (function_override != null)
            {
                XPathNavigator return_override = function_override.SelectSingleNode("returns");
                if (return_override != null)
                {
                    ReturnType.CurrentType = return_override.Value;
                }
            }

            ReturnType.Translate(overrides, Category);

            if (ReturnType.CurrentType.ToLower().Contains("void") && ReturnType.Pointer != 0)
            {
                ReturnType.QualifiedType = "System.IntPtr";
                ReturnType.WrapperType = WrapperTypes.GenericReturnType;
            }

            if (ReturnType.CurrentType.ToLower().Contains("string"))
            {
                ReturnType.QualifiedType = "System.IntPtr";
                ReturnType.WrapperType = WrapperTypes.StringReturnType;
            }

            if (ReturnType.CurrentType.ToLower().Contains("object"))
            {
                ReturnType.QualifiedType = "System.IntPtr";
                ReturnType.WrapperType |= WrapperTypes.GenericReturnType;
            }

            if (ReturnType.CurrentType.Contains("GLenum"))
            {
                if ((Settings.Compatibility & Settings.Legacy.ConstIntEnums) == Settings.Legacy.None)
                    ReturnType.QualifiedType = String.Format("{0}.{1}", Settings.EnumsOutput, Settings.CompleteEnumName);
                else
                    ReturnType.QualifiedType = "int";
            }

            ReturnType.CurrentType = ReturnType.GetCLSCompliantType();
        }

        #endregion

        #region TranslateParameters

        protected virtual void TranslateParameters(XPathNavigator overrides, XPathNavigator function_override)
        {
            for (int i = 0; i < Parameters.Count; i++)
            {
                if (function_override != null)
                {
                    XPathNavigator param_override = function_override.SelectSingleNode(String.Format("param[@name='{0}']", Parameters[i].Name));
                    if (param_override != null)
                    {
                        foreach (XPathNavigator node in param_override.SelectChildren(XPathNodeType.Element))
                        {
                            switch (node.Name)
                            {
                                case "type": Parameters[i].CurrentType = (string)node.TypedValue; break;
                                case "name": Parameters[i].Name = (string)node.TypedValue; break;
                                case "flow":  Parameters[i].Flow = Parameter.GetFlowDirection((string)node.TypedValue); break;
                            }
                        }
                    }
                }

                Parameters[i].Translate(overrides, Category);
                if (Parameters[i].CurrentType == "UInt16" && Name.Contains("LineStipple"))
                    Parameters[i].WrapperType = WrapperTypes.UncheckedParameter;

                // Special case: these functions take a string[] that should stay as is.
                // Todo: move to gloverrides.xml
                //if (Name.Contains("ShaderSource") && Parameters[i].CurrentType.ToLower().Contains("string"))
                //    Parameters[i].Array = 1;
            }
        }

        #endregion

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

            string path = "/overrides/replace/function[@name='{0}' and @extension='{1}']";
            string name = TrimName(Name, false);
            XPathNavigator function_override = overrides.CreateNavigator().SelectSingleNode(String.Format(path, name, Extension));

            TranslateReturnType(overrides.CreateNavigator(), function_override);
            TranslateParameters(overrides.CreateNavigator(), function_override);

            CreateWrappers();
        }

        #endregion

        #region IComparable<Delegate> Members

        public int CompareTo(Delegate other)
        {
            return Name.CompareTo(other.Name);
        }

        #endregion
    }

    #region class DelegateCollection : SortedDictionary<string, Delegate>

    class DelegateCollection : SortedDictionary<string, Delegate>
    {
        public void Add(Delegate d)
        {
            if (!ContainsKey(d.Name))
            {
                Add(d.Name, d);
            }
            else
            {
                Trace.WriteLine(String.Format(
                    "Spec error: function {0} redefined, ignoring second definition.",
                    d.Name));
            }
        }
    }

    #endregion
}
