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

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;

namespace OpenTK.Platform.X11
{
    /// <summary>
    /// Provides methods to create and control an opengl context on the X11 platform.
    /// This class supports OpenTK, and is not intended for use by OpenTK programs.
    /// </summary>
    internal sealed class X11GLContext : IGraphicsContext, IGraphicsContextInternal
    {
        IntPtr context;
        //X11WindowInfo window;
        X11WindowInfo currentWindow;
        //IntPtr visual;
        bool vsync_supported;
        int vsync_interval;

        bool disposed;

        #region --- Constructors ---

        static X11GLContext()
        {
            // Set the GetCurrentContext implementation.
            if (GraphicsContext.GetCurrentContext == null)
                GraphicsContext.GetCurrentContext = X11GLContext.GetCurrentContext;
        }

        internal X11GLContext(GraphicsMode mode, IWindowInfo info, IGraphicsContext shared, bool directRendering)
        {
            //if (mode == null) mode = GraphicsMode.Default;
            if (info == null) throw new ArgumentNullException("info", "Should point to a valid window.");

            currentWindow = (X11WindowInfo)info;
            currentWindow.VisualInfo = SelectVisual(mode, currentWindow);

            Debug.Print("Chose visual: {0}", currentWindow.VisualInfo);

            CreateContext(shared, directRendering, currentWindow);
        }

        #endregion

        #region --- Private Methods ---

        #region XVisualInfo SelectVisual(GraphicsMode mode, X11WindowInfo currentWindow)

        XVisualInfo SelectVisual(GraphicsMode mode, X11WindowInfo currentWindow)
        {
            XVisualInfo info = new XVisualInfo();
            info.visualid = (IntPtr)mode.Index;
            info.screen = currentWindow.Screen;
            int items;
            
            lock (API.Lock)
            {
                IntPtr vs = Functions.XGetVisualInfo(currentWindow.Display, XVisualInfoMask.ID | XVisualInfoMask.Screen, ref info, out items);
                if (items == 0)
                    throw new GraphicsModeException(String.Format("Invalid GraphicsMode specified ({0}).", mode));

                info = (XVisualInfo)Marshal.PtrToStructure(vs, typeof(XVisualInfo));
                Functions.XFree(vs);
            }

            return info;
        }

        #endregion

        #region void CreateContext(IGraphicsContext shareContext, bool direct)

        void CreateContext(IGraphicsContext shareContext, bool direct, X11WindowInfo window)
        {
            try
            {
                ContextHandle shareHandle = shareContext != null ? (shareContext as IGraphicsContextInternal).Context :
                                                                   (ContextHandle)IntPtr.Zero;

                Debug.Write("Creating OpenGL context: ");
                Debug.Write(direct ? "direct, " : "indirect, ");
                Debug.Write(shareHandle.Handle == IntPtr.Zero ? "not shared... " :
                    String.Format("shared with ({0})... ", shareHandle));

                lock (API.Lock)
                {
                    XVisualInfo info = window.VisualInfo;   // Cannot pass a Property by reference.
                    context = Glx.CreateContext(window.Display, ref info, shareHandle.Handle, direct);

                    // Context creation succeeded, return.
                    if (context != IntPtr.Zero)
                    {
                        Debug.Print("done! (id: {0})", context);
                        return;
                    }

                    // Context creation failed. Retry with a non-shared context with the direct/indirect bit flipped.
                    Debug.Print("failed.");
                    Debug.Write(String.Format("Creating OpenGL context: {0}, not shared... ", !direct ? "direct" : "indirect"));
                    context = Glx.CreateContext(window.Display, ref info, IntPtr.Zero, !direct);
                    if (context != IntPtr.Zero)
                    {
                        Debug.Print("done! (id: {0})", context);
                        return;
                    }
                }

                Debug.Print("failed.");
                throw new GraphicsModeException("Failed to create OpenGL context. Glx.CreateContext call returned 0.");
            }
            finally
            {
                Debug.Unindent();
            }
        }

        #endregion

        bool SupportsExtension(X11WindowInfo window, string e)
        {
            string extensions = Glx.QueryExtensionsString(window.Display, window.Screen);
            return !String.IsNullOrEmpty(extensions) && extensions.Contains(e);
        }

        #endregion

        #region --- IGraphicsContext Members ---

        #region public void SwapBuffers()

        public void SwapBuffers()
        {
            //if (window == null) throw new ArgumentNullException("window", "Must point to a valid window.");
            //X11WindowInfo w = (X11WindowInfo)window;
            if (currentWindow.Display == IntPtr.Zero || currentWindow.WindowHandle == IntPtr.Zero)
                throw new InvalidOperationException(
                    String.Format("Window is invalid. Display ({0}), Handle ({1}).", currentWindow.Display, currentWindow.WindowHandle));
            Glx.SwapBuffers(currentWindow.Display, currentWindow.WindowHandle);
        }

        #endregion

        #region public void MakeCurrent(IWindowInfo window)

        public void MakeCurrent(IWindowInfo window)
        {
            X11WindowInfo w = (X11WindowInfo)window;
            bool result;

            Debug.Write(String.Format("Making context {0} current on thread {1} (Display: {2}, Screen: {3}, Window: {4})... ",
                    context, System.Threading.Thread.CurrentThread.ManagedThreadId, w.Display, w.Screen, w.WindowHandle));

            if (w.Display == IntPtr.Zero || w.WindowHandle == IntPtr.Zero || context == IntPtr.Zero)
                throw new InvalidOperationException("Invalid display, window or context.");

            result = Glx.MakeCurrent(w.Display, w.WindowHandle, context);

            if (!result)
                throw new GraphicsContextException("Failed to make context current.");
            else
                Debug.WriteLine("done!");
        }

        #endregion

        #region public bool IsCurrent

        public bool IsCurrent
        {
            get { return Glx.GetCurrentContext() == this.context; }
            //set
            //{
            //    if (value)
            //        Glx.MakeCurrent(window.Display, window.Handle, context);
            //    else
            //        Glx.MakeCurrent(window.Handle, IntPtr.Zero, IntPtr.Zero);
            //}
        }

        #endregion

        #region public bool VSync

        public bool VSync
        {
            get
            {
                return vsync_supported && vsync_interval > 0;
            }
            set
            {
                if (vsync_supported)
                {
                    int error_code = Glx.Sgi.SwapInterval(value ? 1 : 0);
                    if (error_code != 0)
                        throw new GraphicsException(String.Format("Could not set vsync, error code: {0}", error_code));
                    vsync_interval = value ? 1 : 0;
                }
            }
        }

        #endregion

        public event DestroyEvent<IGraphicsContext> Destroy;

        #region public IntPtr GetAddress(string function)

        public IntPtr GetAddress(string function)
        {
            return Glx.GetProcAddress(function);
        }

        #endregion

        public void RegisterForDisposal(IDisposable resource)
        {
            throw new NotSupportedException("Use OpenTK.GraphicsContext instead.");
        }

        public void DisposeResources()
        {
            throw new NotSupportedException("Use OpenTK.GraphicsContext instead.");
        }

        public IEnumerable<DisplayMode> GetDisplayModes()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region --- IGLContextInternal Members ---

        #region void LoadAll()

        void IGraphicsContextInternal.LoadAll()
        {
            GL.LoadAll();
            Glu.LoadAll();
            Glx.LoadAll();
            vsync_supported = this.SupportsExtension(currentWindow, "SGI_swap_control") &&
                              this.GetAddress("glXSwapControlSGI") != IntPtr.Zero;
        }

        #endregion

        #region public DisplayMode Mode

        GraphicsMode IGraphicsContextInternal.GraphicsMode
        {
            get { return null; }
        }

        #endregion

        #region IntPtr IGLContextInternal.Context

        ContextHandle IGraphicsContextInternal.Context
        {
            get { return context; }
            /*private set { context = value; }*/
        }

        #endregion

        #region IWindowInfo IGLContextInternal.Info

        //IWindowInfo IGraphicsContextInternal.Info { get { return window; } }

        #endregion

        #endregion

        #region --- Methods ---

        void OnDestroy()
        {
            if (Destroy != null)
                Destroy(this, EventArgs.Empty);
        }

        #region static ContextHandle GetCurrentContext()

        static ContextHandle GetCurrentContext()
        {
            return (ContextHandle)Glx.GetCurrentContext();
        }

        #endregion

        #endregion

        #region --- IDisposable Members ---

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool manuallyCalled)
        {
            if (!disposed)
            {
                // Clean unmanaged resources:
                try
                {
                    Functions.XLockDisplay(currentWindow.Display);
                    Glx.MakeCurrent(currentWindow.Display, IntPtr.Zero, IntPtr.Zero);
                    Glx.DestroyContext(currentWindow.Display, context);
                    //Functions.XFree(visual);
                }
                finally
                {
                    Functions.XUnlockDisplay(currentWindow.Display);               
                }

                if (manuallyCalled)
                {
                }
            }
            disposed = true;
        }

        ~X11GLContext()
        {
            this.Dispose(false);
        }

        #endregion
    }
}