using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using System.Drawing;
using System.Windows.Forms;

namespace Object_Editor
{
    public class GLControl : OpenTK.GLControl
    {
        public GLControl()
        {
            this.ResizeRedraw = true;
            this.Camera = new Camera();
        }

        #region Types

        struct Byte4
        {
            public byte R, G, B, A;

            public Byte4(byte[] input)
            {
                R = input[0];
                G = input[1];
                B = input[2];
                A = input[3];
            }

            public uint ToUInt32()
            {
                byte[] temp = new byte[] { this.R, this.G, this.B, this.A };
                return BitConverter.ToUInt32(temp, 0);
            }

            //public override string ToString()
            //{
            //    return this.R + ", " + this.G + ", " + this.B + ", " + this.A;
            //}
        }

        #endregion

        #region Variables

        private static GLControl currentControl;

        private MouseButtons currentMouseButton = System.Windows.Forms.MouseButtons.None;
        private Point mouseDownLocation = Point.Empty;
        private Vector3d originalVector;

        #endregion

        #region Properties

        public Camera Camera { get; set; }
        public bool Loaded { get; set; }
        public ObjectManager.Mesh[] RenderMesh { get; set; }

        public ObjectManager.Mesh HighlightedMesh { get; set; }
        public ObjectManager.Face HighlightedFace { get; set; }

        public static ObjectManager.Mesh SelectedMesh { get; set; }
        public static ObjectManager.Face SelectedFace { get; set; }

        public override Color BackColor
        {
            get
            {
                return base.BackColor;
            }
            set
            {
                base.BackColor = value;
                MakeCurrentChecked();
                GL.ClearColor(this.BackColor);
            }
        }

        #endregion

        #region Methods

        private static void Rotate(ref double pX, ref double pY, ref double pZ, double dx, double dy, double dz)
        {
            double cosax = Math.Cos(dx), sinax = Math.Sin(dx);
            double cosay = Math.Cos(dy), sinay = Math.Sin(dy);
            double cosaz = Math.Cos(dz), sinaz = Math.Sin(dz);
            double x, y, z;
            // Axis.X:
            y = cosax * pY - sinax * pZ;
            z = cosax * pZ + sinax * pY;
            pY = y; pZ = z;
            // Axis.Y:
            x = cosay * pX + sinay * pZ;
            z = cosay * pZ - sinay * pX;
            pX = x; pZ = z;
            // Axis.Z:
            x = cosaz * pX - sinaz * pY;
            y = cosaz * pY + sinaz * pX;
            pX = x; pY = y;
        }

        private void MakeCurrentChecked()
        {
            if (currentControl != this)
            {
                this.MakeCurrent();
                currentControl = this;
            }
        }

        public ObjectManager.Face SelectFace(ObjectManager.Mesh mesh, int X, int Y)
        {
            if (mesh != null)
            {
                GL.Disable(EnableCap.Lighting);
                GL.ClearColor(255, 255, 255, 255);
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                GL.Normal3(Vector3.UnitY);
                for (int i = 0; i < mesh.Faces.Count; i++)
                {
                    GL.Color3((byte)(i % 256), (byte)((i >> 8) % 256), (byte)((i >> 16) % 256));
                    GL.Begin(BeginMode.Polygon);
                    {
                        foreach (ObjectManager.FaceVertex vertex in mesh.Faces[i].Vertices)
                        {
                            GL.Vertex3(vertex.TransformedVertex.Coordinates);
                        }
                    } GL.End();
                }
                Byte4 Pixel = new Byte4();
                GL.ReadPixels(X, this.ClientSize.Height - Y, 1, 1, PixelFormat.Rgb, PixelType.UnsignedByte, ref Pixel);
                GL.ClearColor(this.BackColor);
                GL.Enable(EnableCap.Lighting);
                if (Pixel.ToUInt32() != 16777215)
                {
                    return mesh.Faces[(int)Pixel.ToUInt32()];
                }
            }
            return null;
        }

        public ObjectManager.Face SelectFace(ObjectManager.Mesh mesh, Point Position)
        {
            return SelectFace(mesh, Position.X, Position.Y);
        }

        public ObjectManager.Mesh SelectMesh(int X, int Y)
        {
            if (RenderMesh != null)
            {
                GL.Disable(EnableCap.Lighting);
                GL.ClearColor(255, 255, 255, 255);
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                GL.Normal3(Vector3.UnitY);
                for (int i = 0; i < RenderMesh.Length; i++)
                {
                    GL.Color3((byte)(i % 256), (byte)((i >> 8) % 256), (byte)((i >> 16) % 256));
                    foreach (ObjectManager.Face face in RenderMesh[i].Faces)
                    {
                        GL.Begin(BeginMode.Polygon);
                        {
                            foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                            {
                                GL.Vertex3(vertex.TransformedVertex.Coordinates);
                            }
                        } GL.End();
                    }
                }
                Byte4 Pixel = new Byte4();
                GL.ReadPixels(X, this.ClientSize.Height - Y, 1, 1, PixelFormat.Rgb, PixelType.UnsignedByte, ref Pixel);
                GL.ClearColor(this.BackColor);
                GL.Enable(EnableCap.Lighting);
                if (Pixel.ToUInt32() != 16777215)
                {
                    return RenderMesh[Pixel.ToUInt32()];
                }
            }
            return null;
        }

        public ObjectManager.Mesh SelectMesh(Point Position)
        {
            return SelectMesh(Position.X, Position.Y);
        }

        public void SetupViewport()
        {
            MakeCurrentChecked();

            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadIdentity();

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, (float)this.ClientSize.Width / (float)this.ClientSize.Height, 0.2f, 1000f);
            GL.LoadMatrix(ref projection);
            GL.Viewport(0, 0, this.ClientSize.Width, this.ClientSize.Height);

            GL.MatrixMode(MatrixMode.Modelview);
        }

        public void Render()
        {
            MakeCurrentChecked();

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // Load identity matrix
            GL.LoadIdentity();
            // Obtain left-handed coordinates
            GL.Scale(1, 1, -1);

            #region Camera Translations

            // Apply camera rotation
            GL.Rotate(this.Camera.Rotation.X, Vector3d.UnitX);
            GL.Rotate(-this.Camera.Rotation.Y, Vector3d.UnitY);
            // TODO: Add Z-rotation if wanted
            // Apply camera translation
            GL.Translate(-this.Camera.Location.X, -this.Camera.Location.Y, -this.Camera.Location.Z);

            #endregion

            #region Lighting position
            GL.Light(LightName.Light0, LightParameter.Position, new float[] { 2f, 20f, -5f, 0f });
            #endregion

            GL.Disable(EnableCap.Lighting);

            #region Render Floor Grid

            GL.LineWidth(0.5f);
            GL.Color3(.25, .25, .25);

            GL.Begin(BeginMode.Lines);
            {
                for (int i = -10; i <= 10; i++)
                {
                    // TODO: Grid lines passing through Origin
                    if (i == 0) continue;
                    GL.Vertex3(i, 0, -10);
                    GL.Vertex3(i, 0, 10);
                    GL.Vertex3(-10, 0, i);
                    GL.Vertex3(10, 0, i);
                }
            }
            GL.End();

            #endregion

            #region Render Axis (Centre)

            GL.LineWidth(2);
            GL.Begin(BeginMode.Lines);
            {
                GL.Color3(1f, 0, 0);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(100, 0, 0);
                GL.Color3(0, 1f, 0);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(0, 100, 0);
                GL.Color3(0, 0, 1f);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(0, 0, 100);

                GL.Color3(0.5f, 0, 0);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(-100, 0, 0);
                GL.Color3(0, 0.5f, 0);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(0, -100, 0);
                GL.Color3(0, 0, 0.5f);
                GL.Vertex3(Vector3.Zero);
                GL.Vertex3(0, 0, -100);
            }
            GL.End();

            #endregion

            GL.Enable(EnableCap.Lighting);

            #region Render Graphics

            if (SelectedMesh == null)
            {
                // Mesh isn't selected
                #region Render Meshes
                if (RenderMesh != null)
                {
                    foreach (ObjectManager.Mesh mesh in RenderMesh)
                    {
                        GL.Color3(mesh.Material.Color);
                        foreach (ObjectManager.Face face in mesh.Faces)
                        {
                            GL.Begin(BeginMode.Polygon);
                            {
                                foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                                {
                                    GL.Normal3(vertex.Normals);
                                    GL.Vertex3(vertex.TransformedVertex.Coordinates);
                                }
                            } GL.End();
                        }
                    }
                }
                #endregion

                #region Render Highlighted Mesh (Wireframe)
                if (HighlightedMesh != null)
                {
                    GL.Disable(EnableCap.DepthTest);
                    GL.Disable(EnableCap.Lighting);
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(4.0f);
                    //GL.Color3(HighlightedMesh.Material.Color.GetBrightness()>0.5?Color.Black:Color.White);
                    GL.Color3(1.0f, 1.0f, 1.0f);
                    foreach (ObjectManager.Face face in HighlightedMesh.Faces)
                    {
                        GL.Begin(BeginMode.Polygon);
                        {
                            foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                            {
                                GL.Normal3(vertex.Normals);
                                GL.Vertex3(vertex.TransformedVertex.Coordinates);
                            }
                        } GL.End();
                    }
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                    GL.Enable(EnableCap.Lighting);
                    GL.Enable(EnableCap.DepthTest);
                }
                #endregion
            }
            else if (SelectedFace == null)
            {
                // Mesh is selected but not Face
                #region Render Non-selected Meshes (Wireframe)
                if (RenderMesh != null)
                {
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(2.0f);
                    foreach (ObjectManager.Mesh mesh in RenderMesh)
                    {
                        if (mesh != SelectedMesh)
                        {
                            //GL.Color4(mesh.Material.Color.R, mesh.Material.Color.G, mesh.Material.Color.B, (byte)128);
                            GL.Color3(mesh.Material.Color);
                            //GL.Color3(0.7f, 0.7f, 0.7f);
                            foreach (ObjectManager.Face face in mesh.Faces)
                            {
                                GL.Begin(BeginMode.Polygon);
                                {
                                    foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                                    {
                                        GL.Normal3(vertex.Normals);
                                        GL.Vertex3(vertex.TransformedVertex.Coordinates);
                                    }
                                } GL.End();
                            }
                        }
                    }
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                }
                #endregion

                #region Render Selected Mesh
                if (RenderMesh != null)
                {
                    //GL.Disable(EnableCap.DepthTest);
                    GL.Color3(SelectedMesh.Material.Color);
                    foreach (ObjectManager.Face face in SelectedMesh.Faces)
                    {
                        GL.Begin(BeginMode.Polygon);
                        {
                            foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                            {
                                GL.Normal3(vertex.Normals);
                                GL.Vertex3(vertex.TransformedVertex.Coordinates);
                            }
                        } GL.End();
                    }
                    //GL.Enable(EnableCap.DepthTest);
                }
                #endregion

                #region Render Highlighted Face (Wireframe)
                if (HighlightedFace != null)
                {
                    GL.Disable(EnableCap.Lighting);
                    GL.Disable(EnableCap.DepthTest);
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(4.0f);
                    //GL.Color3(HighlightedMesh.Material.Color.GetBrightness()>0.5?Color.Black:Color.White);
                    GL.Color3(1.0f, 1.0f, 1.0f);
                    GL.Begin(BeginMode.Polygon);
                    {
                        foreach (ObjectManager.FaceVertex vertex in HighlightedFace.Vertices)
                        {
                            GL.Normal3(vertex.Normals);
                            GL.Vertex3(vertex.TransformedVertex.Coordinates);
                        }
                    } GL.End();
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                    GL.Enable(EnableCap.Lighting);
                    GL.Enable(EnableCap.DepthTest);
                }
                #endregion
            }
            else
            {
                // Face is selected

                #region Render Non-selected Meshes (Wireframe)
                if (RenderMesh != null)
                {
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(2.0f);
                    foreach (ObjectManager.Mesh mesh in RenderMesh)
                    {
                        if (mesh != SelectedMesh)
                        {
                            GL.Color3(mesh.Material.Color);
                            //GL.Color3(0.7f, 0.7f, 0.7f);
                            foreach (ObjectManager.Face face in mesh.Faces)
                            {
                                GL.Begin(BeginMode.Polygon);
                                {
                                    foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                                    {
                                        GL.Normal3(vertex.Normals);
                                        GL.Vertex3(vertex.TransformedVertex.Coordinates);
                                    }
                                } GL.End();
                            }
                        }
                    }
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                }
                #endregion

                #region Render Selected Mesh
                if (RenderMesh != null)
                {
                    GL.Color3(SelectedMesh.Material.Color);
                    foreach (ObjectManager.Face face in SelectedMesh.Faces)
                    {
                        GL.Begin(BeginMode.Polygon);
                        {
                            foreach (ObjectManager.FaceVertex vertex in face.Vertices)
                            {
                                GL.Normal3(vertex.Normals);
                                GL.Vertex3(vertex.TransformedVertex.Coordinates);
                            }
                        } GL.End();
                    }
                }
                #endregion

                #region Render Selected Face (Wireframe)
                if (SelectedFace != null)
                {
                    GL.Disable(EnableCap.Lighting);
                    GL.Disable(EnableCap.DepthTest);
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(4.0f);
                    //GL.Color3(HighlightedMesh.Material.Color.GetBrightness()>0.5?Color.Black:Color.White);
                    GL.Color3(1.0f, 1.0f, 1.0f);
                    GL.Begin(BeginMode.Polygon);
                    {
                        foreach (ObjectManager.FaceVertex vertex in SelectedFace.Vertices)
                        {
                            GL.Normal3(vertex.Normals);
                            GL.Vertex3(vertex.TransformedVertex.Coordinates);
                        }
                    } GL.End();
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                    GL.Enable(EnableCap.Lighting);
                    GL.Enable(EnableCap.DepthTest);
                }
                #endregion

                #region Render Highlighted Face (Wireframe)
                if (HighlightedFace != null)
                {
                    GL.Disable(EnableCap.Lighting);
                    GL.Disable(EnableCap.DepthTest);
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);
                    GL.LineWidth(4.0f);
                    //GL.Color3(HighlightedMesh.Material.Color.GetBrightness()>0.5?Color.Black:Color.White);
                    GL.Color3(1.0f, 1.0f, 1.0f);
                    GL.Begin(BeginMode.Polygon);
                    {
                        foreach (ObjectManager.FaceVertex vertex in HighlightedFace.Vertices)
                        {
                            GL.Normal3(vertex.Normals);
                            GL.Vertex3(vertex.TransformedVertex.Coordinates);
                        }
                    } GL.End();
                    GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
                    GL.Enable(EnableCap.Lighting);
                    GL.Enable(EnableCap.DepthTest);
                }
                #endregion
            }

            #endregion

            this.SwapBuffers();
        }

        #endregion

        #region Overrided Methods

        protected override void OnLoad(EventArgs e)
        {
            Loaded = true;
            MakeCurrentChecked();
            GL.ClearColor(this.BackColor);
            GL.Enable(EnableCap.DepthTest);
            GL.Enable(EnableCap.CullFace);
            GL.CullFace(CullFaceMode.Front);

            //GL.Enable(EnableCap.Blend);
            //GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.One);

            //GL.Light(LightName.Light0, LightParameter.Position, new float[] { 1f, 1f, 0f, 0f });
            GL.Light(LightName.Light0, LightParameter.Ambient, new float[] { 0.4f, 0.4f, 0.4f, 1f });
            //GL.Light(LightName.Light0, LightParameter.Diffuse, new float[] { 0.67f, 0.67f, 0.67f, 1f });
            //GL.Light(LightName.Light0, LightParameter.Specular, new float[] { 1f, 1f, 1f, 1f });
            //GL.LightModel(LightModelParameter.LightModelAmbient, new float[] { 0.2f, 0.2f, 0.2f, 1 });
            GL.Enable(EnableCap.Lighting);
            GL.Enable(EnableCap.Light0);
            GL.Enable(EnableCap.ColorMaterial);
            GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse);
            //GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Specular, new float[] { 1f, 1f, 1f, 1f });
            //GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Emission, new float[] { 0f, 0f, 0f, 1f });
            //GL.ShadeModel(ShadingModel.Smooth);
            SetupViewport();
            base.OnLoad(e);
        }

        protected override void OnResize(EventArgs e)
        {
            SetupViewport();
            base.OnResize(e);
        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            Render();
            base.OnPaint(e);
        }

        // Handling Mouse Events

        protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left:
                    // TODO: Activate mouse selection (picking)
                    currentMouseButton = System.Windows.Forms.MouseButtons.Left;
                    mouseDownLocation = e.Location;
                    originalVector = this.Camera.Rotation;
                    break;
                case MouseButtons.Middle:
                    currentMouseButton = System.Windows.Forms.MouseButtons.Middle;
                    mouseDownLocation = e.Location;
                    //originalVector = this.Camera.Rotation;
                    break;
                case MouseButtons.Right:
                    currentMouseButton = System.Windows.Forms.MouseButtons.Right;
                    mouseDownLocation = e.Location;
                    originalVector = this.Camera.Location;
                    break;
                //default:
                //    break;
            }
            base.OnMouseDown(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            switch (currentMouseButton)
            {
                case MouseButtons.None:
                    // TODO: Handle highlighting (picking)
                    if (SelectedMesh == null)
                    {
                        HighlightedMesh = SelectMesh(this.PointToClient(Cursor.Position));
                    }
                    else //if (SelectedFace == null)
                    {
                        HighlightedFace = SelectFace(SelectedMesh, this.PointToClient(Cursor.Position));
                    }
                    Render();
                    break;
                case System.Windows.Forms.MouseButtons.Left:
                    // Rotation
                    this.Camera.Rotation.X = originalVector.X + ((double)e.Y - mouseDownLocation.Y) / 10;
                    this.Camera.Rotation.Y = originalVector.Y + ((double)mouseDownLocation.X - e.X) / 10;
                    Render();
                    break;
                case MouseButtons.Middle:
                    //// Rotation
                    //this.Camera.Rotation.X = originalVector.X + ((double)e.Y - mouseDownLocation.Y) / 5;
                    //this.Camera.Rotation.Y = originalVector.Y + ((double)mouseDownLocation.X - e.X) / 5;
                    //Render();
                    break;
                case MouseButtons.Right:
                    double x = mouseDownLocation.X - e.X, y = e.Y - mouseDownLocation.Y, z = 0;
                    Rotate(ref x, ref  y, ref z, -this.Camera.Rotation.X * 0.017453292519943, this.Camera.Rotation.Y * 0.017453292519943, this.Camera.Rotation.Z * 0.017453292519943);
                    this.Camera.Location.X = originalVector.X + x / 50;
                    this.Camera.Location.Y = originalVector.Y + y / 50;
                    this.Camera.Location.Z = originalVector.Z + z / 50;
                    Render();
                    break;
                //default:
                //    break;
            }
            base.OnMouseMove(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            switch (e.Button)
            {
                case MouseButtons.Left:
                    // -TODO: Activate mouse selection (picking)
                    //currentMouseButton = System.Windows.Forms.MouseButtons.Left;
                    //mouseDownLocation = e.Location;
                    if (e.Location == mouseDownLocation)
                    {
                        if (SelectedMesh == null)
                        {
                            SelectedMesh = SelectMesh(this.PointToClient(Cursor.Position));
                        }
                        else //if (SelectedFace == null)
                        {
                            if (SelectedFace == null & (SelectedFace = SelectFace(SelectedMesh, this.PointToClient(Cursor.Position))) == null)
                            {
                                SelectedMesh = null;
                            }
                        }
                    }
                    break;
                //case MouseButtons.Middle:
                //    currentMouseButton = System.Windows.Forms.MouseButtons.Middle;
                //    mouseDownLocation = e.Location;
                //    originalVector = this.Camera.Rotation;
                //    break;
                //case MouseButtons.Right:
                //    currentMouseButton = System.Windows.Forms.MouseButtons.Right;
                //    mouseDownLocation = e.Location;
                //    originalVector = this.Camera.Location;
                //    break;
                //default:
                //    break;
            }
            currentMouseButton = System.Windows.Forms.MouseButtons.None;
            base.OnMouseUp(e);
        }

        #endregion
    }

    public class Camera
    {
        public Vector3d Location = new Vector3d(-5, 2.5, -10);
        public Vector3d Rotation = new Vector3d(-10, 10, 0);
    }
}
