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

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using System.Xml.XPath;

namespace Bind.Structures
{
    /// <summary>
    /// Represents a single parameter of an opengl function.
    /// </summary>
    public class Parameter : Type
    {
        string cache;
        bool rebuild;
        bool unsafe_allowed;        // True if the cache may contain unsafe types, false otherwise.

        #region Constructors

        /// <summary>
        /// Creates a new Parameter without type and name.
        /// </summary>
        public Parameter()
            :base()
        {
        }

        /// <summary>
        /// Creates a new parameter from the parameters passed (deep copy).
        /// </summary>
        /// <param name="p">The parameter to copy from.</param>
        public Parameter(Parameter p)
            : base(p)
        {
            if (p == null)
                return;

            Name = p.Name;
            Unchecked = p.Unchecked;
            UnmanagedType = p.UnmanagedType;
            Generic = p.Generic;
            Flow = p.Flow;
            cache = p.cache;
            //this.rebuild = false;
        }

        #endregion

        #region public string Name

        string _name = String.Empty;
        /// <summary>
        /// Gets or sets the name of the parameter.
        /// </summary>
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    while (value.StartsWith("*"))
                    {
                        Pointer++;
                        value = value.Substring(1);
                    }
                    _name = value;
                    rebuild = true;
                }
            }
        }

        #endregion

        #region UnmanagedType

        UnmanagedType _unmanaged_type;
        /// <summary>
        /// Gets or sets the name of the parameter.
        /// </summary>
        private UnmanagedType UnmanagedType
        {
            get { return _unmanaged_type; }
            set
            {
                if (_unmanaged_type != value)
                {
                    _unmanaged_type = value;
                    rebuild = true;
                }
            }
        }

        #endregion

        #region public FlowDirection Flow

        FlowDirection _flow;

        /// <summary>
        /// Gets or sets the flow of the parameter.
        /// </summary>
        public FlowDirection Flow
        {
            get { return _flow; }
            set
            {
                if (_flow != value)
                {
                    _flow = value;
                    rebuild = true;
                }
            }
        }

        #endregion

        #region public bool NeedsPin

        public bool NeedsPin
        {
            get
            {
                return (Array > 0 || Reference || CurrentType == "object") &&
                        !CurrentType.ToLower().Contains("string");
            }
        }

        #endregion

        #region public bool Unchecked

        private bool _unchecked;

        public bool Unchecked
        {
            get { return _unchecked; }
            set
            {
                if (_unchecked != value)
                {
                    _unchecked = value;
                    rebuild = true;
                }
            }
        }

        #endregion

        #region public bool Generic
        
        bool generic;
        public bool Generic
        {
            get { return generic; }
            set { generic = value; }
        }

        #endregion

        #region public override string CurrentType

        public override string CurrentType
        {
            get
            {
                return base.CurrentType;
            }
            set
            {
                base.CurrentType = value;
                rebuild = true;
            }
        }

        #endregion

        #region public bool DiffersOnlyOnReference

        // Returns true if this parameter differs only on reference compared to another parameter, i.e:
        // returns true for 'int' & 'ref int'
        // returns true for 'ref float' & 'float'
        // returns false 'int' & 'int*'
        // returns false 'int' & 'int[]'
        // returns false 'int' & 'float'
        public bool DiffersOnlyOnReference(Parameter other)
        {
            return
                CurrentType == other.CurrentType &&
                (Reference && !(other.Reference || other.Array > 0 || other.Pointer != 0) ||
                other.Reference && !(Reference || Array > 0 || Pointer != 0));
        }

        #endregion

        #region public override string ToString()

        public override string ToString()
        {
            return ToString(false);
        }

        #endregion

        #region public string ToString(bool override_unsafe_setting)

        public string ToString(bool override_unsafe_setting)
        {
            rebuild |= unsafe_allowed |= override_unsafe_setting;
            unsafe_allowed = override_unsafe_setting;

            if (!String.IsNullOrEmpty(cache) && !rebuild)
            {
               return cache;
            }
            else
            {
                StringBuilder sb = new StringBuilder();

                if (Flow == FlowDirection.Out)
                    sb.Append("[OutAttribute] ");
                else if (Flow == FlowDirection.Undefined)
                    sb.Append("[InAttribute, OutAttribute] ");

                if (Reference)
                {
                    if (Flow == FlowDirection.Out)
                        sb.Append("out ");
                    else
                        sb.Append("ref ");
                }

                if (!override_unsafe_setting && ((Settings.Compatibility & Settings.Legacy.NoPublicUnsafeFunctions) != Settings.Legacy.None))
                {
                    if (Pointer != 0)
                    {
                        sb.Append("IntPtr");
                    }
                    else
                    {
                        sb.Append(CurrentType);
                        if (Array > 0)
                        {
                            sb.Append("[");
                            for (int i = 1; i < Array; i++)
                                sb.Append(",");
                            sb.Append("]");
                        }
                    }
                }
                else
                {
                    sb.Append(CurrentType);
                    for (int i = 0; i < Pointer; i++)
                        sb.Append("*");
                    if (Array > 0)
                    {
                        sb.Append("[");
                        for (int i = 1; i < Array; i++)
                            sb.Append(",");
                        sb.Append("]");
                    }
                }
                if (!String.IsNullOrEmpty(Name))
                {
                    sb.Append(" ");
                    sb.Append(Utilities.Keywords.Contains(Name) ? "@" + Name : Name);
                }

                rebuild = false;
                cache = sb.ToString();
                return cache;
            }
        }

        #endregion

        #region override public void Translate(XPathNavigator overrides, string category)

        override public void Translate(XPathNavigator overrides, string category)
        {
            base.Translate(overrides, category);

            // Find out the necessary wrapper types.
            if (Pointer != 0)/* || CurrentType == "IntPtr")*/
            {
                if (CurrentType.ToLower().Contains("string"))
                {
                    // string* -> [In] String[] or [Out] StringBuilder[]
                    CurrentType =
                        Flow == FlowDirection.Out ?
                        "System.Text.StringBuilder[]" :
                        "String[]";

                    Pointer = 0;
                    WrapperType = WrapperTypes.None;
                }
                else if (CurrentType.ToLower().Contains("char"))
                {
                    // char* -> [In] String or [Out] StringBuilder
                    CurrentType =
                        Flow == FlowDirection.Out ?
                        "System.Text.StringBuilder" :
                        "String";

                    Pointer = 0;
                    WrapperType = WrapperTypes.None;
                }
                else if (CurrentType.ToLower().Contains("void") ||
                    (!String.IsNullOrEmpty(PreviousType) && PreviousType.ToLower().Contains("void"))) /*|| CurrentType.Contains("IntPtr"))*/
                {
                    CurrentType = "IntPtr";
                    Pointer = 0;
                    WrapperType = WrapperTypes.GenericParameter;
                }
                else
                {
                    WrapperType = WrapperTypes.ArrayParameter;
                }
            }

            if (Reference)
                WrapperType |= WrapperTypes.ReferenceParameter;

            if (Name == "params")
                Name = "@params";
            if (Name == "event")
                Name = "@event";

            // This causes problems with bool arrays
            //if (CurrentType.ToLower().Contains("bool"))
            //    WrapperType = WrapperTypes.BoolParameter;
        }

        #endregion

        #region Static Members

        // Returns the FlowDirection that matches the specified string
        // ("out" or "in", otherwise undefined).
        public static FlowDirection GetFlowDirection(string direction)
        {
            return direction == "out" ? FlowDirection.Out : direction == "in" ? FlowDirection.In : FlowDirection.Undefined;
        }
        
        #endregion
    }

    /// <summary>
    /// Holds the parameter list of an opengl function.
    /// </summary>
    public class ParameterCollection : List<Parameter>
    {
        string cache = String.Empty;
        string callStringCache = String.Empty;
        private bool rebuild = true;
        bool hasPointerParameters;
        bool hasReferenceParameters;
        bool hasUnsignedParameters;
        bool hasGenericParameters;
        bool unsafe_types_allowed;
        public bool Rebuild
        {
            private get { return rebuild; }
            set { rebuild = true;/*value;*/ }
        }

        #region Constructors

        public ParameterCollection()
        {
        }

        public ParameterCollection(ParameterCollection pc)
        {
            foreach (Parameter p in pc)
            {
                Add(new Parameter(p));
            }
        }

        #endregion

        #region void BuildCache()

        void BuildCache()
        {
            BuildReferenceAndPointerParametersCache();
            BuildCallStringCache();
            BuildToStringCache(unsafe_types_allowed);
            Rebuild = false;
        }

        #endregion

        #region public bool HasPointerParameters

        public bool HasPointerParameters
        {
            get
            {
                if (Rebuild)
                    BuildCache();

                return hasPointerParameters;
            }
        }

        #endregion

        #region public bool HasReferenceParameters

        public bool HasReferenceParameters
        {
            get
            {
                if (Rebuild)
                    BuildCache();
                
                return hasReferenceParameters;
            }
        }

        #endregion

        #region public bool HasUnsignedParameters

        public bool HasUnsignedParameters
        {
            get
            {
                if (Rebuild)
                    BuildCache();

                return hasUnsignedParameters;
            }
        }

        #endregion

        #region public bool HasGenericParameters

        public bool HasGenericParameters
        {
            get
            {
                if (Rebuild)
                    BuildCache();

                return hasGenericParameters;
            }
        }
        
        #endregion

        #region void BuildReferenceAndPointerParametersCache()

        void BuildReferenceAndPointerParametersCache()
        {
            foreach (Parameter p in this)
            {
                if (p.Pointer != 0 || p.CurrentType.Contains("IntPtr"))
                    hasPointerParameters = true;

                if (p.Reference)
                    hasReferenceParameters = true;

                if (p.Unsigned)
                    hasUnsignedParameters = true;

                if (p.Generic)
                    hasGenericParameters = true;
            }
        }

        #endregion

        #region new public void Add(Parameter p)

        new public void Add(Parameter p)
        {
            Rebuild = true;
            base.Add(p);
        }

        #endregion

        public override string ToString()
        {
            return ToString(false);
        }

        #region public string ToString(bool override_unsafe_setting)

        /// <summary>
        /// Gets the parameter declaration string.
        /// </summary>
        /// <param name="override_unsafe_setting">
        /// If true, unsafe types will be used even if the Settings.Compatibility.NoPublicUnsafeFunctions flag is set.
        /// </param>
        /// <returns>The parameter list of an opengl function in the form ( [parameters] )</returns>
        public string ToString(bool override_unsafe_setting)
        {
            Rebuild |= unsafe_types_allowed != override_unsafe_setting;
            unsafe_types_allowed = override_unsafe_setting;

            if (!Rebuild)
            {
                return cache;
            }
            else
            {
                BuildCache();
                return cache;
            }
        }

        #endregion

        #region void BuildToStringCache(bool override_unsafe_setting)

        void BuildToStringCache(bool override_unsafe_setting)
        {
            unsafe_types_allowed = override_unsafe_setting;

            StringBuilder sb = new StringBuilder();
            sb.Append("(");
            if (Count > 0)
            {
                foreach (Parameter p in this)
                {
                    sb.Append(p.ToString(override_unsafe_setting));
                    sb.Append(", ");
                }
                sb.Replace(", ", ")", sb.Length - 2, 2);
            }
            else
                sb.Append(")");

            cache = sb.ToString();
        }

        #endregion

        #region public string CallString()

        public string CallString()
        {
            if (!Rebuild)
            {
                return callStringCache;
            }
            else
            {
                BuildCache();
                return callStringCache;
            }
        }

        #endregion

        #region private void BuildCallStringCache()

        /// <summary>
        /// Builds a call string instance and caches it.
        /// </summary>
        private void BuildCallStringCache()
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("(");

            if (Count > 0)
            {
                foreach (Parameter p in this)
                {
                    if (p.Unchecked)
                        sb.Append("unchecked((" + p.CurrentType + ")");

                    if (!p.Generic && p.CurrentType != "object")
                    {
                        if (p.CurrentType.ToLower().Contains("string"))
                        {
                            sb.Append(String.Format("({0}{1})",
                                p.CurrentType, (p.Array > 0) ? "[]" : ""));
                        }
                        else if (p.IndirectionLevel != 0)
                        {
                            if (((Settings.Compatibility & Settings.Legacy.TurnVoidPointersToIntPtr) != Settings.Legacy.None) &&
                                p.Pointer != 0 && p.CurrentType.Contains("void"))
                                sb.Append("(IntPtr)");
                            else
                            {
                                sb.Append("(");
                                sb.Append(p.CurrentType);
                                for (int i = 0; i < p.IndirectionLevel; i++)
                                    sb.Append("*");
                                sb.Append(")");
                            }
                        }
                        else
                        {
                            sb.Append(String.Format("({0})", p.CurrentType));
                        }
                    }

                    sb.Append(Utilities.Keywords.Contains(p.Name) ? "@" + p.Name : p.Name);

                    if (p.Unchecked)
                        sb.Append(")");

                    sb.Append(", ");
                }
                sb.Replace(", ", ")", sb.Length - 2, 2);
            }
            else
            {
                sb.Append(")");
            }

            callStringCache = sb.ToString();
        }

        #endregion

        public bool ContainsType(string type)
        {
            foreach (Parameter p in this)
                if (p.CurrentType == type)
                    return true;
            return false;
        }
    }
}
