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

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Reflection.Emit;

using OpenTK.Platform;

namespace OpenTK.Graphics
{
    /// <summary>
    /// Provides access to the OpenGL Utilities library.
    /// Methods i this library are considered deprecated and should be avoided.
    /// </summary>
    [Obsolete("Use OpenTK math functions instead.")]
    public static partial class Glu
    {
        private const string Library = "glu32.dll";

        private static Dictionary<string, bool> AvailableExtensions = new Dictionary<string, bool>();
        private static bool rebuildExtensionList = true;

         private static Type importsClass = typeof(Imports);

        static Glu()
        {
            // Glu doesn't have any extensions, so this is safe to call once and be done with it.
            LoadAll();
        }

        #region private static Delegate LoadDelegate(string name, Type signature)

        /// <summary>
        /// Creates a System.Delegate that can be used to call a GLU function, core or extension.
        /// </summary>
        /// <param name="name">The name of the GLU function (eg. "gluBuild2DMipmaps")</param>
        /// <param name="signature">The signature of the GLU function.</param>
        /// <returns>
        /// A System.Delegate that can be used to call this GLU function, or null if the specified
        /// function name did not correspond to an GLU function.
        /// </returns>
        private static Delegate LoadDelegate(string name, Type signature)
        {
            MethodInfo m = importsClass.GetMethod(name.Substring(3), BindingFlags.Static | BindingFlags.NonPublic);
            return
                GL.GetExtensionDelegate(name, signature) ??
                (m != null ? Delegate.CreateDelegate(signature, m) : null);
        }

        #endregion

        #region public static void LoadAll()

        /// <summary>
        /// Loads all GLU functions (core and extensions).
        /// </summary>
        /// <remarks>
        /// <para>
        /// Call this function manually whenever you need to update GLU entry points.
        /// This need will never arise under normal usage patterns.
        /// </para>
        /// </remarks>
        public static void LoadAll()
        {
            int supported = 0;
            Type extensions_class = typeof(Glu).GetNestedType("Delegates", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (extensions_class == null)
                throw new InvalidOperationException("The specified type does not have any loadable extensions.");

            FieldInfo[] delegates = extensions_class.GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (delegates == null)
                throw new InvalidOperationException("The specified type does not have any loadable extensions.");

            foreach (FieldInfo f in delegates)
            {
                Delegate d = LoadDelegate(f.Name, f.FieldType);
                if (d != null)
                    ++supported;

                f.SetValue(null, d);
            }

            rebuildExtensionList = true;
        }

        #endregion

        #region public static bool Load(string function)

        /// <summary>
        /// Tries to reload the given GLU function (core or extension).
        /// </summary>
        /// <param name="function">The name of the GLU function.</param>
        /// <returns>True if the function was found and reloaded, false otherwise.</returns>
        /// <remarks>
        /// <para>
        /// While the automatic initialisation will load all GLU entry points, in some cases
        /// the initialization can take place before a render context has been established.
        /// In this case, use this function to load the entry points for the GLU functions
        /// you will need, or use LoadAll() to load all available entry points.
        /// </para>
        /// <para>
        /// This function returns true if the given GLU function is supported, false otherwise.
        /// </para>
        /// <para>
        /// To query for supported extensions use the IsExtensionSupported() function instead.
        /// </para>
        /// </remarks>
        public static bool Load(string function)
        {
            // Glu does not contain any extensions - this method does nothing.
            return true;
        }

        #endregion

        #region public static bool SupportsExtension(string name)

        /// <summary>
        /// Determines whether the specified GLU extension is available in
        /// the current GLU context.
        /// </summary>
        /// <param name="name">The string for the GLU extension.</param>
        /// <returns>True if the specified extension is available, false otherwise.</returns>
        public static bool SupportsExtension(string name)
        {
            if (rebuildExtensionList)
            {
                BuildExtensionList();
            }

            // Search the cache for the string. Note that the cache substitutes
            // strings "1.0" to "2.1" with "GL_VERSION_1_0" to "GL_VERSION_2_1"
            if (AvailableExtensions.ContainsKey(name))
            {
                return AvailableExtensions[name];
            }

            return false;
        }

        #endregion

        #region private static void BuildExtensionList()

        /// <summary>
        /// Builds a cache of the supported extensions to speed up searches.
        /// </summary>
        private static void BuildExtensionList()
        {
            // Assumes there is an opengl context current.

            AvailableExtensions.Clear();

            string version_string = Glu.GetString(GluStringName.Version);
            if (String.IsNullOrEmpty(version_string))
            {
                throw new ApplicationException("Failed to build extension list. Is there an opengl context current?");
            }

            string version = version_string.Trim(' ');
            if (version.StartsWith("1.0"))
            {
                AvailableExtensions.Add("VERSION_1_0", true);
            }
            else if (version.StartsWith("1.1"))
            {
                AvailableExtensions.Add("VERSION_1_0", true);
                AvailableExtensions.Add("VERSION_1_1", true);
            }
            else if (version.StartsWith("1.2"))
            {
                AvailableExtensions.Add("VERSION_1_0", true);
                AvailableExtensions.Add("VERSION_1_1", true);
                AvailableExtensions.Add("VERSION_1_2", true);
            }
            else if (version.StartsWith("1.3"))
            {
                AvailableExtensions.Add("VERSION_1_0", true);
                AvailableExtensions.Add("VERSION_1_1", true);
                AvailableExtensions.Add("VERSION_1_2", true);
                AvailableExtensions.Add("VERSION_1_3", true);
            }

            string extension_string = Glu.GetString(GluStringName.Extensions);
            if (String.IsNullOrEmpty(extension_string))
            {   // no extensions are available
                return;
            }

            string[] extensions = extension_string.Split(' ');
            foreach (string ext in extensions)
            {
                AvailableExtensions.Add(ext, true);
            }

            rebuildExtensionList = false;
        }

        #endregion

        #region Overloads

        public static void LookAt(Vector3 eye, Vector3 center, Vector3 up)
        {
            Delegates.gluLookAt((double)eye.X, (double)eye.Y, (double)eye.Z, (double)center.X, (double)center.Y, (double)center.Z, (double)up.X, (double)up.Y, (double)up.Z);
        }

        // One token Project overload, I picked this one because it's CLS compliant, and it
        // makes reasonably clear which args are inputs and which are outputs.
        public static Int32 Project(Vector3 obj, double[] model, double[] proj, Int32[] view, out Vector3 win)
        {
            unsafe
            {
                double winX, winY, winZ;
                double* winX_ptr = &winX;
                double* winY_ptr = &winY;
                double* winZ_ptr = &winZ;
                fixed (double* model_ptr = model)
                fixed (double* proj_ptr = proj)
                fixed (Int32* view_ptr = view)
                {
                    Int32 retval = Delegates.gluProject((double)obj.X, (double)obj.Y, (double)obj.Z, (double*)model_ptr, (double*)proj_ptr, (Int32*)view_ptr, (double*)winX_ptr, (double*)winY_ptr, (double*)winZ_ptr);
                    win = new Vector3((float)*winX_ptr, (float)*winY_ptr, (float)*winZ_ptr);
                    return retval;
                }
            }
        }

        public static void TessNormal(IntPtr tess, Vector3 normal)
        {
            Delegates.gluTessNormal(tess, (double)normal.X, (double)normal.Y, (double)normal.Z);
        }

        public static Int32 UnProject(Vector3 win, double[] model, double[] proj, Int32[] view, out Vector3 obj)
        {
            unsafe
            {
                double objX, objY, objZ;
                double* objX_ptr = &objX;
                double* objY_ptr = &objY;
                double* objZ_ptr = &objZ;
                fixed (double* model_ptr = model)
                fixed (double* proj_ptr = proj)
                fixed (Int32* view_ptr = view)
                {
                    Int32 retval = Delegates.gluUnProject((double)win.X, (double)win.Y, (double)win.Z, (double*)model_ptr, (double*)proj_ptr, (Int32*)view_ptr, (double*)objX_ptr, (double*)objY_ptr, (double*)objZ_ptr);
                    obj = new Vector3((float)*objX_ptr, (float)*objY_ptr, (float)*objZ_ptr);
                    return retval;
                }
            }
        }

        public static Int32 UnProject4(Vector4 win, double[] model, double[] proj, Int32[] view, double near, double far, out Vector4 obj)
        {
            unsafe
            {
                double objX, objY, objZ, objW;
                double* objX_ptr = &objX;
                double* objY_ptr = &objY;
                double* objZ_ptr = &objZ;
                double* objW_ptr = &objW;
                fixed (double* model_ptr = model)
                fixed (double* proj_ptr = proj)
                fixed (Int32* view_ptr = view)
                {
                    Int32 retval = Delegates.gluUnProject4((double)win.X, (double)win.Y, (double)win.Z, (double)win.W, (double*)model_ptr, (double*)proj_ptr, (Int32*)view_ptr, (double)near, (double)far, (double*)objX_ptr, (double*)objY_ptr, (double*)objZ_ptr, (double*)objW_ptr);
                    obj = new Vector4((float)*objX_ptr, (float)*objY_ptr, (float)*objZ_ptr, (float)*objW_ptr);
                    return retval;
                }
            }
        }

        public static string ErrorString(ErrorCode error)
        {
            return ErrorString((GluErrorCode)error);
        }

        public static void TessWindingRuleProperty(IntPtr tess, TessWinding property)
        {
            Glu.TessProperty(tess, TessParameter.TessWindingRule, (double)property);
        }

        #endregion

    }

#if false

    //public delegate object

    public delegate void FastVoidInvokeHandler(object target, object[] paramters);
    public delegate object FastInvokeHandler(object target, object[] paramters);
    public static class FastInvoker
    {
        /// <summary>
        /// Use this one instead of MethodInfo.Invoke, this way it is 50 times quicker.
        /// 
        /// <example>
        /// string Filter = "FirstName = 'Ton'"
        /// MethodInfo mi = typeof(Person).GetMethod("GetAll");
        /// snoei.net.Reflection.FastInvoker.FastInvokeHandler fi = snoei.net.Reflection.FastInvoker.GetMethodInvoker( mi );
        //    return fi.Invoke( Person, new object[]{Filter} );
        /// //Calls Person.GetAll(string Filter);
        /// </example>
        /// </summary>
        /// <param name="methodInfo"></param>
        /// <returns></returns>
        public static Delegate GetMethodInvoker(MethodInfo methodInfo)
        {
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, methodInfo.ReturnType, new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);
            ILGenerator il = dynamicMethod.GetILGenerator();
            ParameterInfo[] ps = methodInfo.GetParameters();
            Type[] paramTypes = new Type[ps.Length];

            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                    paramTypes[i] = ps[i].ParameterType.GetElementType();
                else
                    paramTypes[i] = ps[i].ParameterType;
            }

            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];

            for (int i = 0; i < paramTypes.Length; i++)
                locals[i] = il.DeclareLocal(paramTypes[i], true);

            for (int i = 0; i < paramTypes.Length; i++)
            {
                il.Emit(OpCodes.Ldarg_1);
                EmitFastInt(il, i);
                il.Emit(OpCodes.Ldelem_Ref);
                EmitCastToReference(il, paramTypes[i]);
                il.Emit(OpCodes.Stloc, locals[i]);
            }

            if (!methodInfo.IsStatic)
                il.Emit(OpCodes.Ldarg_0);

            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                    il.Emit(OpCodes.Ldloca_S, locals[i]);
                else
                    il.Emit(OpCodes.Ldloc, locals[i]);
            }

            if (methodInfo.IsStatic)
                il.EmitCall(OpCodes.Call, methodInfo, null);
            else
                il.EmitCall(OpCodes.Callvirt, methodInfo, null);

            if (methodInfo.ReturnType == typeof(void))
                il.Emit(OpCodes.Ldnull);
            else
                EmitBoxIfNeeded(il, methodInfo.ReturnType);

            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (ps[i].ParameterType.IsByRef)
                {
                    il.Emit(OpCodes.Ldarg_1);
                    EmitFastInt(il, i);
                    il.Emit(OpCodes.Ldloc, locals[i]);
                    if (locals[i].LocalType.IsValueType)
                        il.Emit(OpCodes.Box, locals[i].LocalType);
                    il.Emit(OpCodes.Stelem_Ref);
                }
            }

            il.Emit(OpCodes.Ret);

            if (methodInfo.ReturnType == typeof(void))
                return dynamicMethod.CreateDelegate(typeof(FastVoidInvokeHandler));
            else
                return dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
        }

        private static void EmitCastToReference(ILGenerator il, System.Type type)
        {
            if (type.IsValueType)
                il.Emit(OpCodes.Unbox_Any, type);
            else
                il.Emit(OpCodes.Castclass, type);
        }

        private static void EmitBoxIfNeeded(ILGenerator il, System.Type type)
        {
            if (type.IsValueType)
                il.Emit(OpCodes.Box, type);
        }

        private static void EmitFastInt(ILGenerator il, int value)
        {
            switch (value)
            {
                case -1:
                    il.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    il.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    il.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    il.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    il.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    il.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    il.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    il.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    il.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    il.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            if (value > -129 && value < 128)
                il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            else
                il.Emit(OpCodes.Ldc_I4, value);
        }
    }

#endif
}
