#region --- License ---
/* Licensed under the MIT/X11 license.
 * Copyright (c) 2006-2008 the OpenTK Team.
 * This notice may not be removed from any source distribution.
 * See license.txt for licensing details.
 */
#endregion

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;

namespace Examples
{
    [Example("GLU Tesselation Functions Test", ExampleCategory.OpenGL, "GLU", Visible = false)]
    public class Test : GameWindow
    {
        int startList;
        IntPtr tess;

        // Define the signatures for the callback functions, and declare the callbacks.
        delegate void BeginCallbackDelegate(BeginMode mode);
        delegate void EndCallbackDelegate();
        delegate void VertexCallbackDelegate(IntPtr v);
        delegate void ErrorCallbackDelegate(GluErrorCode code);
        unsafe delegate void CombineCallbackDelegate(
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 3)]double[] coordinates,
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]double*[] vertexData,
            [MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]float[] weight,
            double** dataOut);

        BeginCallbackDelegate tessBegin;
        EndCallbackDelegate tessEnd;
        ErrorCallbackDelegate tessError;
        VertexCallbackDelegate tessVertex;
        CombineCallbackDelegate tessCombine;

        public Test() : base()
        {
            Keyboard.KeyDown += KeyDownHandler;
        }

        #region --- GLU Tessellation callbacks ---

        #region BeginHandler

        void BeginHandler(BeginMode mode)
        {
            GL.Begin(mode);
        }

        #endregion

        #region EndHandler

        void EndHandler()
        {
            GL.End();
        }

        #endregion

        #region VertexHandler

        void VertexHandler(IntPtr v)
        {
            unsafe { GL.Vertex3((double*)v); }
        }

        #endregion

        #region ErrorHandler

        void ErrorHandler(GluErrorCode code)
        {
            System.Windows.Forms.MessageBox.Show(
                String.Format("GLU Error {0}: {1}", code.ToString(), Glu.ErrorString(code)),
                "An error occured while tesselating.");
            this.Exit();
        }

        #endregion

        #region CombineHandler

        unsafe double*[] combineData;
        int data_index = 0;
        unsafe void CombineHandler(double[] coordinates, double*[] data, float[] weight, double** dataOut)
        {
            // Workaround Mono 1.2.6 bug with unsafe inline initializers
            if (combineData == null)
                combineData = new double*[16];

            double* out_data = combineData[data_index] = (double*)Marshal.AllocHGlobal(6 * sizeof(double));
            int i;

            out_data[0] = coordinates[0];
            out_data[1] = coordinates[1];
            out_data[2] = coordinates[2];

            for (i = 3; i < 6; i++)
            {
                double* real_data = (double*)data[i-3];
                out_data[i] = weight[0] * real_data[0] +
                              weight[1] * real_data[1] +
                              weight[2] * real_data[2] +
                              weight[3] * real_data[3];
            }
            data_index++;

            *dataOut = out_data;
        }

        #endregion

        #endregion

        #region KeyDownHandler

        public void KeyDownHandler(KeyboardDevice sender, Key key)
        {
            switch (key)
            {
                case Key.Escape:
                    this.Exit();
                    return;
            }
        }

        #endregion

        #region OnResize

        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(0, 0, Width, Height);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();
            Glu.Ortho2D(0.0, (double)Width, 0.0, (double)Height);
        }

        #endregion

        #region OnLoad

        public override void OnLoad(EventArgs e)
        {
            double[][] rect = new double[4][] {
                new double[] {50.0, 50.0, 0.0},
                new double[] {200.0, 50.0, 0.0},
                new double[] {200.0, 200.0, 0.0},
                new double[] {50.0, 200.0, 0.0}
            };
            double[][] tri = new double[3][] {
                new double[] {75.0, 75.0, 0.0},
                new double[] {125.0, 175.0, 0.0},
                new double[] {175.0, 75.0, 0.0}
            };
            double[][] star = new double[5][] {
                new double[] {250.0, 50.0, 0.0, 1.0, 0.0, 1.0},
                new double[] {325.0, 200.0, 0.0, 1.0, 1.0, 0.0},
                new double[] {400.0, 50.0, 0.0, 0.0, 1.0, 1.0},
                new double[] {250.0, 150.0, 0.0, 1.0, 0.0, 0.0},
                new double[] {400.0, 150.0, 0.0, 0.0, 1.0, 0.0}
            };

            GL.ClearColor(System.Drawing.Color.MidnightBlue);

            tess = Glu.NewTess();
            startList = GL.GenLists(3);

            tessVertex = this.VertexHandler;
            tessBegin = this.BeginHandler;
            tessEnd = this.EndHandler;
            tessError = this.ErrorHandler;
            unsafe { tessCombine = this.CombineHandler; }
            Trace.Assert(tessVertex != null, "Failed to load tesselator callback function.");
            Trace.Assert(tessBegin != null, "Failed to load tesselator begin callback function.");
            Trace.Assert(tessEnd != null, "Failed to load tesselator end callback function.");
            Trace.Assert(tessError != null, "Failed to load tesselator error callback function.");
            Trace.Assert(tessCombine != null, "Failed to load tesselator combine callback function.");

            Glu.TessCallback(tess, TessCallback.TessVertex, tessVertex);
            Glu.TessCallback(tess, TessCallback.TessBegin, tessBegin);
            Glu.TessCallback(tess, TessCallback.TessEnd, tessEnd);
            Glu.TessCallback(tess, TessCallback.TessError, tessError);

            // rectangle with triangular hole inside
            GL.NewList(startList, ListMode.Compile);
            GL.ShadeModel(ShadingModel.Flat);
            Glu.TessBeginPolygon(tess, IntPtr.Zero);
            Glu.TessBeginContour(tess);
            Glu.TessVertex(tess, rect[0], rect[0]);
            Glu.TessVertex(tess, rect[1], rect[1]);
            Glu.TessVertex(tess, rect[2], rect[2]);
            Glu.TessVertex(tess, rect[3], rect[3]);
            Glu.TessEndContour(tess);
            Glu.TessBeginContour(tess);
            Glu.TessVertex(tess, tri[0], tri[0]);
            Glu.TessVertex(tess, tri[1], tri[1]);
            Glu.TessVertex(tess, tri[2], tri[2]);
            Glu.TessEndContour(tess);
            Glu.TessEndPolygon(tess);
            GL.EndList();
            
            Glu.TessCallback(tess, TessCallback.TessVertex, tessVertex);
            Glu.TessCallback(tess, TessCallback.TessBegin, tessBegin);
            Glu.TessCallback(tess, TessCallback.TessEnd, tessEnd);
            Glu.TessCallback(tess, TessCallback.TessError, tessError);
            Glu.TessCallback(tess, TessCallback.TessCombine, tessCombine);

            // smooth shaded, self-intersecting star
            GL.NewList(startList + 1, ListMode.Compile);
            GL.ShadeModel(ShadingModel.Smooth);
            Glu.TessWindingRuleProperty(tess, TessWinding.TessWindingPositive);
            Glu.TessBeginPolygon(tess, IntPtr.Zero);
            Glu.TessBeginContour(tess);
            Glu.TessVertex(tess, star[0], star[0]);
            Glu.TessVertex(tess, star[1], star[1]);
            Glu.TessVertex(tess, star[2], star[2]);
            Glu.TessVertex(tess, star[3], star[3]);
            Glu.TessVertex(tess, star[4], star[4]);
            Glu.TessEndContour(tess);
            Glu.TessEndPolygon(tess);
            GL.EndList();
        }

        #endregion

        #region OnUnload

        public override void OnUnload(EventArgs e)
        {
            if (tess != IntPtr.Zero)
                Glu.DeleteTess(tess);
            GL.DeleteLists(startList, 3);
            while (data_index != 0)
                unsafe { Marshal.FreeHGlobal((IntPtr)combineData[data_index--]); }
        }

        #endregion

        #region OnRenderFrame

        protected override void OnRenderFrame(FrameEventArgs e)
        {
            GL.Clear(ClearBufferMask.ColorBufferBit);

            GL.Color3(1.0f, 1.0f, 1.0f);
            GL.CallList(startList);
            GL.CallList(startList + 1);
            GL.Flush();

            this.SwapBuffers();
        }

        #endregion

        public static void Main()
        {
            using (Test test = new Test())
            {
                Utilities.SetWindowTitle(test);
                test.Run(30.0, 0.0);
            }
        }
    }
}