using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VisimBox.Renderer;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using VisiMath;
using System.Drawing;
using System.Drawing.Imaging;
using VisimBox.Renderer.RenderObjects;
using VisiMath.Geometry;

namespace VisimRenderGL
{
    // rendering and drawing calls
    // Render**** - expects that material etc is already set, requires primitives or buffers
    // Draw*** - expects rendering object

    
    partial class GLRenderContext : RenderContext
    {
        /// <summary>
        /// Sets main rendering color (especially for 2d rendering)
        /// </summary>
        /// <param name="col">Color</param>
        public override void SetColor(ref ColorByte col)
        {
            GL.Color4(ref col.R);
        }

        /// <summary>
        /// Sets model transformation (without camera transformation)
        /// </summary>
        /// <param name="mat"></param>
        public override void SetTransformation(ref Matrix4 mat)
        {
            if (MainCamera != null)
            {
                Matrix4 cammat = MainCamera.GetViewTransformation();
                LoadMatrix(ref cammat);
                MultMatrix(ref mat);
            }
            else
            {
                LoadMatrix(ref mat);
            }
            
        }

        public void SetSimpleMaterial()
        {
            //Disable lighting
            GL.Disable(EnableCap.Lighting);
            //disable textures
            GL.Disable(EnableCap.Texture2D);
            GL.Disable(EnableCap.ColorMaterial);
        }



        #region special objects

        /// <summary>
        /// Draws simple skybox
        /// </summary>
        /// <param name="skybox">Skybox data (note, textures in skybox must be initialized!)</param>
        public override void DrawSkybox(ref SkyboxData skybox)
        {
            //OPTIMIZE!!! - prestore skybox quads in vertex buffers
            
            //GL.Disable(EnableCap.DepthTest);
            bool fog = Fog;
            Fog = false;

            GL.Disable(EnableCap.CullFace);
            
            GL.Disable(EnableCap.Lighting);

            GL.Enable(EnableCap.Texture2D);
            GL.Color3(Color.White);

            GL.PushMatrix();
            //GL.LoadIdentity();

            Matrix4 mat = MainCamera.GetViewTransformation();
            LoadMatrix(ref mat);


            Vector3 position = MainCamera.Position;

            //float[] baka = new float[64];
            //GL.GetFloat(GetPName.ModelviewMatrix, baka);

            // Move to camera position
            GL.Translate(position.X, position.Y, position.Z);
            //GL.GetFloat(GetPName.ModelviewMatrix, baka);
            // Scaling
            GL.Scale(skybox.Scale.X, skybox.Scale.Y, skybox.Scale.Z);

            //GL.GetFloat(GetPName.ModelviewMatrix, baka);

            float cx = -0.0f, cz = 1.0f;
            float r = 1.005f; // border

            // Right side
            Texture tex = skybox.GetTexture(1);

            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(-cx, cz); GL.Vertex3(-r, 1.0f, -r);
                GL.TexCoord2(-cx, cx); GL.Vertex3(-r, 1.0f, r);
                GL.TexCoord2(-cz, cx); GL.Vertex3(r, 1.0f, r);
                GL.TexCoord2(-cz, cz); GL.Vertex3(r, 1.0f, -r);
                GL.End();
            }


            tex = skybox.GetTexture(0);
            // Left side
            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(cx, cz); GL.Vertex3(-r, -1.0f, -r);
                GL.TexCoord2(cx, cx); GL.Vertex3(-r, -1.0f, r);
                GL.TexCoord2(cz, cx); GL.Vertex3(r, -1.0f, r);
                GL.TexCoord2(cz, cz); GL.Vertex3(r, -1.0f, -r);
                GL.End();
            }

            // Back
            tex = skybox.GetTexture(3);
            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(-cx, cx); GL.Vertex3(-1.0f, -r, r);
                GL.TexCoord2(-cz, cx); GL.Vertex3(-1.0f, r, r);
                GL.TexCoord2(-cz, cz); GL.Vertex3(-1.0f, r, -r);
                GL.TexCoord2(-cx, cz); GL.Vertex3(-1.0f, -r, -r);
                GL.End();
            }


            // Front
            tex = skybox.GetTexture(2);
            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(cx, cx); GL.Vertex3(1.0f, -r, r);
                GL.TexCoord2(cz, cx); GL.Vertex3(1.0f, r, r);
                GL.TexCoord2(cz, cz); GL.Vertex3(1.0f, r, -r);
                GL.TexCoord2(cx, cz); GL.Vertex3(1.0f, -r, -r);
                GL.End();
            }


            // Top
            tex = skybox.GetTexture(4);
            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(-cz, cz); GL.Vertex3(r, -r, 1.0f);
                GL.TexCoord2(-cx, cz); GL.Vertex3(r, r, 1.0f);
                GL.TexCoord2(-cx, cx); GL.Vertex3(-r, r, 1.0f);
                GL.TexCoord2(-cz, cx); GL.Vertex3(-r, -r, 1.0f);
                GL.End();
            }


            // Down side
            tex = skybox.GetTexture(5);
            if (tex != null)
            {
                tex.Activate(this);
                GL.Begin(BeginMode.Quads);
                GL.TexCoord2(cz, cz); GL.Vertex3(r, -r, -1.0f);
                GL.TexCoord2(cx, cz); GL.Vertex3(r, r, -1.0f);
                GL.TexCoord2(cx, cx); GL.Vertex3(-r, r, -1.0f);
                GL.TexCoord2(cz, cx); GL.Vertex3(-r, -r, -1.0f);
                GL.End();
            }


            GL.PopMatrix();
            GL.Disable(EnableCap.Texture2D);
            GL.Enable(EnableCap.CullFace);
            //GL.Enable(EnableCap.DepthTest);

            Fog = fog;

        }


        #endregion

        #region immediate mode rendering


        protected void DrawImmediateVertex(ref IVertex point, ref VertexDescriptor vd)
        {
            if (vd.HasColor())
            {
                ColorByte cb = point.Color;
                GL.Color4(ref cb.R);
            }
            if (vd.HasNormal())
            {
                Vector3 norm = point.Normal;
                GL.Normal3(ref norm.X);
            }
            if (vd.HasTexCoord())
            {
                Vector2 tx = point.TexCoord2;
                GL.TexCoord2(ref tx.X);
            }
            Vector3 pos = point.Position;
            GL.Vertex3(ref pos.X);
        }

        protected void DrawImmediateVertex(ref Vector3 point)
        {
            GL.Vertex3(ref point.X);
        }

        public override void DrawPoint3D(IVertex point)
        {
            GL.Begin(BeginMode.Points);
            VertexDescriptor vd = VertexFactory.GetDescriptor(point.GetType());
            DrawImmediateVertex(ref point, ref vd);
            GL.End();
        }

        public override void DrawPoint3D(ref Vector3 vec)
        {
            GL.Begin(BeginMode.Points);
            DrawImmediateVertex(ref vec);
            GL.End();
        }

        public override void DrawLine3D(IVertex first, IVertex second)
        {
            GL.Begin(BeginMode.Lines);
            VertexDescriptor vd = VertexFactory.GetDescriptor(first.GetType());
            DrawImmediateVertex(ref first, ref vd);
            DrawImmediateVertex(ref second, ref vd);
            GL.End();
        }

        public override void DrawLine3D(ref Vector3 first, ref Vector3 second)
        {
            GL.Begin(BeginMode.Lines);
            DrawImmediateVertex(ref first);
            DrawImmediateVertex(ref second);
            GL.End();
        }

        public override void DrawQuad3D(ref Vector3 first, ref Vector3 second, ref Vector3 third, ref Vector3 fourth)
        {
            GL.Begin(BeginMode.Quads);
            DrawImmediateVertex(ref first);
            DrawImmediateVertex(ref second);
            DrawImmediateVertex(ref third);
            DrawImmediateVertex(ref fourth);
            GL.End();
        }

        public override void DrawTriangle3D(ref Vector3 first, ref Vector3 second, ref Vector3 third)
        {
            GL.Begin(BeginMode.Triangles);
            DrawImmediateVertex(ref first);
            DrawImmediateVertex(ref second);
            DrawImmediateVertex(ref third);
            GL.End();
        }

        public override void DrawTriangle3D(IVertex first, IVertex second, IVertex third)
        {
            GL.Begin(BeginMode.Triangles);
            VertexDescriptor vd = VertexFactory.GetDescriptor(first.GetType());
            DrawImmediateVertex(ref first, ref vd);
            DrawImmediateVertex(ref second, ref vd);
            DrawImmediateVertex(ref third, ref vd);
            GL.End();
        }

        public override void DrawQuad3D(IVertex first, IVertex second, IVertex third, IVertex fourth)
        {
            GL.Begin(BeginMode.Triangles);
            VertexDescriptor vd = VertexFactory.GetDescriptor(first.GetType());
            DrawImmediateVertex(ref first, ref vd);
            DrawImmediateVertex(ref second, ref vd);
            DrawImmediateVertex(ref third, ref vd);
            DrawImmediateVertex(ref fourth, ref vd);
            GL.End();
        }


        protected void RenderArrayImmediate(IVertexArray varray, int startindex = 0, int count = 0)
        {
            VertexDescriptor vd = varray.Descriptor;

            bool docolors = vd.HasColor();
            bool donormals = vd.HasNormal();
            bool dotexcoor = vd.HasTexCoord();
            RenderArrayImmediate(varray, docolors, donormals, dotexcoor, startindex, count);        
        }

        protected void RenderArrayImmediate(IVertexArray varray, bool docolors, bool donormals, bool dotexcoor, int startindex = 0, int count = 0)
        {
            if (startindex >= varray.Count)
                throw new ArgumentException("RenderArrayImmediate: starting index too high!");

            if (count == 0)
                count = varray.Count - startindex;
            if (count < 0)
                throw new ArgumentException("RenderArrayImmediate: count is too high!");


            for (int i = startindex; i < count; i++)
            {
                IVertex iv = varray[i];

                Vector3 vec = iv.Position;

                if (docolors)
                {
                    //GL.Color4(Color.FromArgb(iv.Color));
                    SetColor(iv.Color);
                }

                if (donormals)
                {
                    Vector3 nor = iv.Normal;
                    GL.Normal3(ref nor.X);
                }

                if (dotexcoor)
                {
                    Vector2 tx = iv.TexCoord2;
                    GL.TexCoord2(ref tx.X);
                }

                GL.Vertex3(ref vec.X);
            }
        }

        #endregion


        #region Vertex Array rendering

        /// <summary>
        /// Sets pointers for array drawing. Note, fixes varray.pointer, free it after using!
        /// </summary>
        /// <param name="varray"></param>
        protected void SetArrayPointers(VertexDescriptor vd, IntPtr basePointer, uint vertexbuffer = 0, uint indexbuffer = 0 )
        {

            bool docolors = vd.HasColor();
            bool donormals = vd.HasNormal();
            bool dotexcoor = vd.HasTexCoord();

            IntPtr pos;
            IntPtr norm;
            IntPtr colors;
            IntPtr texCoords;

            int size = vd.SizeInBytes;

            GL.EnableClientState(ArrayCap.VertexArray);
            if (indexbuffer !=0)
                GL.EnableClientState(ArrayCap.IndexArray);
            else
                GL.DisableClientState(ArrayCap.IndexArray);
            GL.DisableClientState(ArrayCap.EdgeFlagArray);
            GL.DisableClientState(ArrayCap.SecondaryColorArray);
            GL.DisableClientState(ArrayCap.FogCoordArray);
            GL.BindBuffer(BufferTarget.ArrayBuffer, vertexbuffer);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, indexbuffer);

            pos = (IntPtr)((int)basePointer + vd.GetOffset(VertexElementUsage.Position));

            GL.VertexPointer(3, VertexPointerType.Float, size, pos);

            if (docolors)
            {
                colors = (IntPtr)((int)basePointer + vd.GetOffset(VertexElementUsage.Color));
                GL.EnableClientState(ArrayCap.ColorArray);
                GL.ColorPointer(4, ColorPointerType.UnsignedByte, size, colors);
            }
            else
            {
                GL.DisableClientState(ArrayCap.ColorArray);
            }

            if (donormals)
            {
                norm = (IntPtr)((int)basePointer + vd.GetOffset(VertexElementUsage.Normal));
                GL.EnableClientState(ArrayCap.NormalArray);
                GL.NormalPointer(NormalPointerType.Float, size, norm);
            }
            else
            {
                GL.DisableClientState(ArrayCap.NormalArray);
            }

            if (dotexcoor)
            {
                texCoords = (IntPtr)((int)basePointer + vd.GetOffset(VertexElementUsage.TexCoord));
                GL.EnableClientState(ArrayCap.TextureCoordArray);
                GL.TexCoordPointer(2, TexCoordPointerType.Float, size, texCoords);
            }
            else
            {
                GL.DisableClientState(ArrayCap.TextureCoordArray);
            }
        }

        protected void ResetArrayPointers()
        {
            GL.DisableClientState(ArrayCap.TextureCoordArray);
            GL.DisableClientState(ArrayCap.VertexArray);
            GL.DisableClientState(ArrayCap.NormalArray);
            GL.DisableClientState(ArrayCap.ColorArray);
        }


        protected void RenderArray(BeginMode bm, IVertexArray varray, int vertperelement, int startindex = 0, int count = 0)
        {
            if (startindex >= varray.Count)
                throw new ArgumentException("RenderArray: starting index too high!");

            if (count == 0)
                count = varray.Count - startindex;
            if (count < 0)
                throw new ArgumentException("RenderArray: count is too high!");

            if (count % vertperelement != 0)
                throw new ArgumentException("RenderArray - vertex array doesn't contain correct number of vertices");

            if (varray.IsPinnable)
            {
                IntPtr pointer = varray.GetPointer();
                SetArrayPointers(varray.Descriptor, pointer, 0, 0);
                GL.DrawArrays(bm, startindex, count);
                varray.FreePointer();
                ResetArrayPointers();
                GL.Finish();
            }
            else
            {
                GL.Begin(bm);
                RenderArrayImmediate(varray, startindex, count);
                GL.End();
            }
        }

        #region Shortcuts

        public override void RenderQuads(IVertexArray varray, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.Quads, varray, 4, startindex, count);
        }

        public override void RenderTriangles(IVertexArray varray, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.Triangles, varray, 3, startindex, count);
        }

        public override void RenderPoints(IVertexArray points, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.Points, points, 1, startindex, count);
        }

        public override void RenderLines(IVertexArray varray, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.Lines, varray, 2, startindex, count);
        }


        public override void RenderLine(IVertexArray varray, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.LineStrip, varray, 1, startindex, count);
        }

        public override void RenderPoly(IVertexArray varray, int startindex = 0, int count = 0)
        {
            RenderArray(BeginMode.Polygon, varray, 1, startindex, count);
        }

        #endregion

        public void GetBeginMode(PrimitiveTypes type, out BeginMode bm, out int vertperelement)
        {
            switch (type)
            {
                case PrimitiveTypes.Triangle:       //!!
                    bm = BeginMode.Triangles;
                    vertperelement = 3;
                    break;
                case PrimitiveTypes.TriangleList:   //!!
                    bm = BeginMode.Triangles;
                    vertperelement = 3;
                    break;
                case PrimitiveTypes.IndexedTriangles:
                    bm = BeginMode.Triangles;
                    vertperelement = 3;
                    break;
                case PrimitiveTypes.IndexedQuads:
                    bm = BeginMode.Quads;
                    vertperelement = 4;
                    break;
                case PrimitiveTypes.QuadList:
                    bm = BeginMode.Quads;
                    vertperelement = 4;
                    break;
                case PrimitiveTypes.Quad:
                    bm = BeginMode.Quads;
                    vertperelement = 4;
                    break;
                case PrimitiveTypes.IndexedLines:
                    bm = BeginMode.Lines;
                    vertperelement = 2;
                    break;
                case PrimitiveTypes.LineSegment:
                    bm = BeginMode.Lines;
                    vertperelement = 2;
                    break;
                case PrimitiveTypes.LineList:
                    bm = BeginMode.Lines;
                    vertperelement = 2;
                    break;
                case PrimitiveTypes.PolyLine:
                    bm = BeginMode.LineStrip;
                    vertperelement = 1;
                    break;
                case PrimitiveTypes.PointList:
                    bm = BeginMode.Points;
                    vertperelement = 1;
                    break;
                default:
                    throw new ArgumentException("Unsupported type of mesh");
            }
        }

        public override void RenderIndexedMesh(IVertexArray varray, int[] indices, PrimitiveTypes type, int startindex = 0, int count = 0)
        {
            BeginMode bm;
            int vertperelement;

            GetBeginMode(type, out bm, out vertperelement);


            if (startindex >= varray.Count)
                throw new ArgumentException("RenderIndexedMesh: starting index too high!");

            if (count == 0)
                count = indices.Length - startindex;
            if (count < 0)
                throw new ArgumentException("RenderIndexedMesh: count is too high!");

            if (count % vertperelement != 0)
                throw new ArgumentException("RenderIndexedMesh - vertex array doesn't contain correct number of vertices");


            if (varray.IsPinnable)
            {
                if ((startindex != 0) || (count != indices.Length))
                {
                    if (count == 0)
                        count = indices.Length - startindex;

                    int []newindices = new int[count];
                    for (int i = startindex; i < count; i++)
                    {
                        newindices[i - startindex] = indices[i];
                    }

                    indices = newindices;
                }
                IntPtr pointer = varray.GetPointer();
                SetArrayPointers(varray.Descriptor, pointer, 0, 0);
                GL.DrawElements(bm, indices.Length, DrawElementsType.UnsignedInt, indices);
                varray.FreePointer();
                ResetArrayPointers();
                GL.Finish();
            }

            else
            {
                VertexDescriptor vd = varray.Descriptor;
                 GL.Begin(bm);
                 for (int i = startindex; i < count; i++)
                 {
                     IVertex iv = varray[indices[i]];
                     DrawImmediateVertex(ref iv, ref vd);
                 }
                 GL.End();
            }
        }

        #endregion


        #region VertexBuffer rendering


        public override void RenderIndexedMesh(IVertexBuffer vertices, int[] indices, PrimitiveTypes type, int startindex = 0, int count = 0)
        {
            BeginMode bm;
            int vertperelement;

            GetBeginMode(type, out bm, out vertperelement);

            if (startindex >= indices.Length)
                throw new ArgumentException("RenderIndexedMesh: starting index too high!");

            if (count == 0)
                count = indices.Length - startindex;
            if (count < 0)
                throw new ArgumentException("RenderIndexedMesh: count is too high!");

            if (count % vertperelement != 0)
                throw new ArgumentException("RenderIndexedMesh - vertex array doesn't contain correct number of vertices");
            
            SetArrayPointers(vertices.Descriptor, IntPtr.Zero, vertices.InternalID, 0);
            GL.DrawElements(bm, count, DrawElementsType.UnsignedInt, indices);
            ResetArrayPointers();
        }


        public override void RenderIndexedMesh(IVertexBuffer vertices, IIndexBuffer indices, PrimitiveTypes type, int startindex = 0, int count = 0)
        {
            BeginMode bm;
            int vertperelement;

            GetBeginMode(type, out bm, out vertperelement);

            if (startindex >= indices.Count)
                throw new ArgumentException("RenderIndexedMesh: starting index too high!");

            if (count == 0)
                count = indices.Count - startindex;
            if (count < 0)
                throw new ArgumentException("RenderIndexedMesh: count is too high!");

            if (count % vertperelement != 0)
                throw new ArgumentException("RenderIndexedMesh - vertex array doesn't contain correct number of vertices");

            SetArrayPointers(vertices.Descriptor, IntPtr.Zero, vertices.InternalID, indices.InternalID);
            GL.DrawElements(bm, count, DrawElementsType.UnsignedInt, startindex);
            ResetArrayPointers();
        }


        /// <summary>
        /// Render vertex buffer as specified type of primitive (can't be used for indexed meshes!)
        /// </summary>
        /// <param name="vertices">Vertex buffer</param>
        /// <param name="type">Primitive type</param>
        /// <param name="startindex">starting index</param>
        /// <param name="count">Number of elements</param>
        public override void RenderVertexBuffer(IVertexBuffer vertices, PrimitiveTypes type, int startindex = 0, int count = 0)
        {
            BeginMode bm;
            int vertperelement;

            GetBeginMode(type, out bm, out vertperelement);

            if (count == 0)
                count = vertices.Count - startindex;

            if (startindex + count> vertices.Count)
                throw new ArgumentException("RenderVertexBuffer: starting index too high!");

            if (count % vertperelement != 0)
                throw new ArgumentException("RenderVertexBuffer - vertex array doesn't contain correct number of vertices");

            SetArrayPointers(vertices.Descriptor, IntPtr.Zero, vertices.InternalID, 0);

            GL.DrawArrays(bm, startindex, count);

            ResetArrayPointers();
        }



        #endregion


    }
}
