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

#region --- Using Directives ---

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;
using OpenTK.Graphics;

#endregion

namespace OpenTK.Platform
{
    namespace MacOS
    {
        /// <summary>
        /// This delegate represents any method that takes no arguments and returns an int.
        /// I would have used Func but that requires .NET 4
        /// </summary>
        /// <returns>The int value that your method returns</returns>
        public delegate int GetInt();
    }

    /// <summary>
    /// Provides cross-platform utilities to help interact with the underlying platform.
    /// </summary>
    public static class Utilities
    {
        #region internal static bool ThrowOnX11Error

        static bool throw_on_error;
        internal static bool ThrowOnX11Error
        {
            get { return throw_on_error; }
            set
            {
                if (value && !throw_on_error)
                {
                    Type.GetType("System.Windows.Forms.XplatUIX11, System.Windows.Forms")
                        .GetField("ErrorExceptions", System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.NonPublic)
                        .SetValue(null, true);
                    throw_on_error = true;
                }
                else if (!value && throw_on_error)
                {
                    Type.GetType("System.Windows.Forms.XplatUIX11, System.Windows.Forms")
                        .GetField("ErrorExceptions", System.Reflection.BindingFlags.Static |
                            System.Reflection.BindingFlags.NonPublic)
                        .SetValue(null, false);
                    throw_on_error = false;
                }
            }
        }

        #endregion

        #region internal static void LoadExtensions(Type type)

        delegate Delegate LoadDelegateFunction(string name, Type signature);

        /// <internal />
        /// <summary>Loads all extensions for the specified class. This function is intended
        /// for OpenGL, Wgl, Glx, OpenAL etc.</summary>
        /// <param name="type">The class to load extensions for.</param>
        /// <remarks>
        /// <para>The Type must contain a nested class called "Delegates".</para>
        /// <para>
        /// The Type must also implement a static function called LoadDelegate with the
        /// following signature:
        /// <code>static Delegate LoadDelegate(string name, Type signature)</code>
        /// </para>
        /// <para>This function allocates memory.</para>
        /// </remarks>
        internal static void LoadExtensions(Type type)
        {
            // Using reflection is more than 3 times faster than directly loading delegates on the first
            // run, probably due to code generation overhead. Subsequent runs are faster with direct loading
            // than with reflection, but the first time is more significant.

            int supported = 0;
            Type extensions_class = type.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.");

            MethodInfo load_delegate_method_info = type.GetMethod("LoadDelegate", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (load_delegate_method_info == null)
                throw new InvalidOperationException(type.ToString() + " does not contain a static LoadDelegate method.");
            LoadDelegateFunction LoadDelegate = (LoadDelegateFunction)Delegate.CreateDelegate(
                typeof(LoadDelegateFunction), load_delegate_method_info);

            Debug.Write("Load extensions for " + type.ToString() + "... ");

            System.Diagnostics.Stopwatch time = new System.Diagnostics.Stopwatch();
            time.Reset();
            time.Start();

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

                f.SetValue(null, d);
            }

            FieldInfo rebuildExtensionList = type.GetField("rebuildExtensionList", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (rebuildExtensionList != null)
                rebuildExtensionList.SetValue(null, true);

            time.Stop();
            Debug.Print("{0} extensions loaded in {1} ms.", supported, time.ElapsedMilliseconds);
            time.Reset();
        }

        #endregion

        #region internal static bool TryLoadExtension(Type type, string extension)

        /// <internal />
        /// <summary>Loads the specified extension for the specified class. This function is intended
        /// for OpenGL, Wgl, Glx, OpenAL etc.</summary>
        /// <param name="type">The class to load extensions for.</param>
        /// <param name="extension">The extension to load.</param>
        /// <remarks>
        /// <para>The Type must contain a nested class called "Delegates".</para>
        /// <para>
        /// The Type must also implement a static function called LoadDelegate with the
        /// following signature:
        /// <code>static Delegate LoadDelegate(string name, Type signature)</code>
        /// </para>
        /// <para>This function allocates memory.</para>
        /// </remarks>
        internal static bool TryLoadExtension(Type type, string extension)
        {
            Type extensions_class = type.GetNestedType("Delegates", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (extensions_class == null)
            {
                Debug.Print(type.ToString(), " does not contain extensions.");
                return false;
            }

            LoadDelegateFunction LoadDelegate = (LoadDelegateFunction)Delegate.CreateDelegate(typeof(LoadDelegateFunction),
                type.GetMethod("LoadDelegate", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
            if (LoadDelegate == null)
            {
                Debug.Print(type.ToString(), " does not contain a static LoadDelegate method.");
                return false;
            }

            FieldInfo f = extensions_class.GetField(extension, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            if (f == null)
            {
                Debug.Print("Extension \"", extension, "\" not found in ", type.ToString());
                return false;
            }

            Delegate old = f.GetValue(null) as Delegate;
            Delegate @new = LoadDelegate(f.Name, f.FieldType);
            if ((old != null ? old.Target : null) != (@new != null ? @new.Target : null))
            {
                f.SetValue(null, @new);
                FieldInfo rebuildExtensionList = type.GetField("rebuildExtensionList", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
                if (rebuildExtensionList != null)
                    rebuildExtensionList.SetValue(null, true);
            }
            return @new != null;
        }

        #endregion

        #region CreateGetAddress

        internal static GraphicsContext.GetAddressDelegate CreateGetAddress()
        {
            GraphicsContext.GetAddressDelegate loader = null;
            if (Configuration.RunningOnWindows)
            {
                loader = Platform.Windows.Wgl.GetProcAddress;
            }
            else if (Configuration.RunningOnX11)
            {
                loader = Platform.X11.Glx.GetProcAddress;
            }
            else if (Configuration.RunningOnMacOS)
            {
                loader = Platform.MacOS.NS.GetAddress;
            }
            else
            {
                throw new PlatformNotSupportedException();
            }
            return loader;
        }

        #endregion

        #region --- Creating a Graphics Context ---

        /// <summary>
        /// Creates an IGraphicsContext instance for the specified window.
        /// </summary>
        /// <param name="mode">The GraphicsMode for the GraphicsContext.</param>
        /// <param name="window">An IWindowInfo instance describing the parent window for this IGraphicsContext.</param>
        /// <param name="major">The major OpenGL version number for this IGraphicsContext.</param>
        /// <param name="minor">The minor OpenGL version number for this IGraphicsContext.</param>
        /// <param name="flags">A bitwise collection of GraphicsContextFlags with specific options for this IGraphicsContext.</param>
        /// <returns>A new IGraphicsContext instance.</returns>
        [Obsolete("Call new OpenTK.Graphics.GraphicsContext() directly, instead.")]
        public static IGraphicsContext CreateGraphicsContext(
            GraphicsMode mode, IWindowInfo window,
            int major, int minor, GraphicsContextFlags flags)
        {
            GraphicsContext context = new GraphicsContext(mode, window, major, minor, flags);
            context.MakeCurrent(window);

            (context as IGraphicsContextInternal).LoadAll();

            return context;
        }

        #region CreateX11WindowInfo

        /// <summary>
        /// Constructs a new IWindowInfo instance for the X11 platform.
        /// </summary>
        /// <param name="display">The display connection.</param>
        /// <param name="screen">The screen.</param>
        /// <param name="windowHandle">The handle for the window.</param>
        /// <param name="rootWindow">The root window for screen.</param>
        /// <param name="visualInfo">A pointer to a XVisualInfo structure obtained through XGetVisualInfo.</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateX11WindowInfo(IntPtr display, int screen, IntPtr windowHandle, IntPtr rootWindow, IntPtr visualInfo)
        {
            Platform.X11.X11WindowInfo window = new OpenTK.Platform.X11.X11WindowInfo();
            window.Display = display;
            window.Screen = screen;
            window.Handle = windowHandle;
            window.RootWindow = rootWindow;
            window.Visual = visualInfo;
            return window;
        }

        #endregion

        #region CreateWindowsWindowInfo

        /// <summary>
        /// Creates an IWindowInfo instance for the windows platform.
        /// </summary>
        /// <param name="windowHandle">The handle of the window.</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateWindowsWindowInfo(IntPtr windowHandle)
        {
            return new OpenTK.Platform.Windows.WinWindowInfo(windowHandle, null);
        }

        #endregion

        #region CreateMacOSCarbonWindowInfo

        /// <summary>
        /// Creates an IWindowInfo instance for the Mac OS X platform.
        /// </summary>
        /// <param name="windowHandle">The handle of the window.</param>
        /// <param name="ownHandle">Ignored. This is reserved for future use.</param>
        /// <param name="isControl">Set to true if windowHandle corresponds to a System.Windows.Forms control.</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateMacOSCarbonWindowInfo(IntPtr windowHandle, bool ownHandle, bool isControl)
        {
            return CreateMacOSCarbonWindowInfo(windowHandle, ownHandle, isControl, null, null);
        }

        /// <summary>
        /// Creates an IWindowInfo instance for the Mac OS X platform with an X and Y offset for the GL viewport location.
        /// </summary>
        /// <param name="windowHandle">The handle of the window.</param>
        /// <param name="ownHandle">Ignored. This is reserved for future use.</param>
        /// <param name="isControl">Set to true if windowHandle corresponds to a System.Windows.Forms control.</param>
        /// <param name="xOffset">The X offset for the GL viewport</param>
        /// <param name="yOffset">The Y offset for the GL viewport</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateMacOSCarbonWindowInfo(IntPtr windowHandle, bool ownHandle, bool isControl, 
            OpenTK.Platform.MacOS.GetInt xOffset, OpenTK.Platform.MacOS.GetInt yOffset)
        {
            return new OpenTK.Platform.MacOS.CarbonWindowInfo(windowHandle, false, isControl, xOffset, yOffset);
        }

        #endregion

        #region CreateMacOSWindowInfo

        /// <summary>
        /// Creates an IWindowInfo instance for the Mac OS X platform.
        /// </summary>
        /// <param name="windowHandle">The handle of the NSWindow.</param>
        /// <remarks>Assumes that the NSWindow's contentView is the NSView we want to attach to our context.</remarks>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateMacOSWindowInfo(IntPtr windowHandle)
        {
            return new OpenTK.Platform.MacOS.CocoaWindowInfo(windowHandle);
        }

        /// <summary>
        /// Creates an IWindowInfo instance for the Mac OS X platform.
        /// </summary>
        /// <param name="windowHandle">The handle of the NSWindow.</param>
        /// <param name="viewHandle">The handle of the NSView.</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateMacOSWindowInfo(IntPtr windowHandle, IntPtr viewHandle)
        {
            return new OpenTK.Platform.MacOS.CocoaWindowInfo(windowHandle, viewHandle);
        }

        #endregion

        #region CreateDummyWindowInfo

        /// <summary>
        /// Creates an IWindowInfo instance for the dummy platform.
        /// </summary>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateDummyWindowInfo()
        {
            return new Dummy.DummyWindowInfo();
        }

        #endregion

        #region CreateSdl2WindowInfo

        /// <summary>
        /// Creates an IWindowInfo instance for the windows platform.
        /// </summary>
        /// <param name="windowHandle">The handle of the window.</param>
        /// <returns>A new IWindowInfo instance.</returns>
        public static IWindowInfo CreateSdl2WindowInfo(IntPtr windowHandle)
        {
            return new OpenTK.Platform.SDL2.Sdl2WindowInfo(
                windowHandle, null);
        }

        #endregion

        #endregion

        #region RelaxGraphicsMode

        internal static bool RelaxGraphicsMode(ref GraphicsMode mode)
        {
            ColorFormat color = mode.ColorFormat;
            int depth = mode.Depth;
            int stencil = mode.Stencil;
            int samples = mode.Samples;
            ColorFormat accum = mode.AccumulatorFormat;
            int buffers = mode.Buffers;
            bool stereo = mode.Stereo;

            bool success = RelaxGraphicsMode(
                ref color, ref depth, ref stencil, ref samples,
                ref accum, ref buffers, ref stereo);

            mode = new GraphicsMode(
                color, depth, stencil, samples,
                accum, buffers, stereo);

            return success;
        }

        /// \internal
        /// <summary>
        /// Relaxes graphics mode parameters. Use this function to increase compatibility
        /// on systems that do not directly support a requested GraphicsMode. For example:
        /// - user requested stereoscopic rendering, but GPU does not support stereo
        /// - user requseted 16x antialiasing, but GPU only supports 4x
        /// </summary>
        /// <returns><c>true</c>, if a graphics mode parameter was relaxed, <c>false</c> otherwise.</returns>
        /// <param name="color">Color bits.</param>
        /// <param name="depth">Depth bits.</param>
        /// <param name="stencil">Stencil bits.</param>
        /// <param name="samples">Number of antialiasing samples.</param>
        /// <param name="accum">Accumulator buffer bits.</param>
        /// <param name="buffers">Number of rendering buffers (1 for single buffering, 2+ for double buffering, 0 for don't care).</param>
        /// <param name="stereo">Stereoscopic rendering enabled/disabled.</param>
        internal static bool RelaxGraphicsMode(ref ColorFormat color, ref int depth, ref int stencil, ref int samples, ref ColorFormat accum, ref int buffers, ref bool stereo)
        {
            // Parameters are relaxed in order of importance.
            // - Accumulator buffers are way outdated as a concept,
            // so they go first.
            // - Triple+ buffering is generally not supported by the
            // core WGL/GLX/AGL/CGL/EGL specs, so we clamp
            // to double-buffering as a second step. (If this doesn't help
            // we will also fall back to undefined single/double buffering
            // as a last resort).
            // - AA samples are an easy way to increase compatibility
            // so they go next.
            // - Stereoscopic is only supported on very few GPUs
            // (Quadro/FirePro series) so it goes next.
            // - The rest of the parameters then follow.

            if (accum != 0)
            {
                accum = 0;
                return true;
            }

            if (buffers > 2)
            {
                buffers = 2;
                return true;
            }

            if (samples > 0)
            {
                samples = Math.Max(samples - 1, 0);
                return true;
            }

            if (stereo)
            {
                stereo = false;
                return true;
            }

            if (stencil != 0)
            {
                stencil = 0;
                return true;
            }

            if (depth != 0)
            {
                depth = 0;
                return true;
            }

            if (color != 24)
            {
                color = 24;
                return true;
            }

            if (buffers != 0)
            {
                buffers = 0;
                return true;
            }

            // no parameters left to relax, fail
            return false;
        }

        #endregion
    }
}