using System;
using SlatedGameToolkit.Framework.Graphics.OpenGL;
using SlatedGameToolkit.Framework.Graphics.Window;

namespace SlatedGameToolkit.Framework.Graphics.Render
{
    /// <summary>
    /// A set of two buffers, one for the vertices, and one for the indices. Also defines an vertex array that defines the attributes of the buffers.
    /// </summary>
    public class VertexArrayBuffers : IDisposable {
        private uint vertexBufferLength, indexBufferLength;
        private GLContext glContext;
        private bool disposed;
        private uint vertexBufferHandle, vertexArrayHandle, indexBufferHandle;

        /// <summary>
        /// Create a vertex array, a elements buffer, and an vertice array buffer.
        /// 
        /// Automatically checks if resizing the buffers are nessecary.
        /// </summary>
        /// <param name="context"></param>
        public VertexArrayBuffers(GLContext context) {
            this.glContext = context ?? WindowContextsManager.CurrentGL;

            uint[] vertexArrays = new uint[1];
            glContext.GenVertexArrays(1, vertexArrays);
            vertexArrayHandle = vertexArrays[0];

            uint[] vertexBuffers = new uint[2];
            glContext.GenBuffers(2, vertexBuffers);
            vertexBufferHandle = vertexBuffers[0];
            indexBufferHandle = vertexBuffers[1];
        }

        public void Use() {
            if (disposed) throw new ObjectDisposedException("VertexArrayBuffers");
            glContext.BindVertexArray(vertexArrayHandle);
            glContext.BindBuffer(BufferTargetARB.ElementArrayBuffer, indexBufferHandle);
            glContext.BindBuffer(BufferTargetARB.ArrayBuffer, vertexBufferHandle);
        }

        public unsafe void BufferVertices(float[] data, bool dynamic) {
            Use();
            uint requiredLength = (uint) (sizeof(float) * data.Length);
            fixed (void* pointer = &data[0]) {
                if (requiredLength > vertexBufferLength) {
                    glContext.BufferData(BufferTargetARB.ArrayBuffer, requiredLength, new IntPtr(pointer), dynamic ? OpenGL.BufferUsageARB.DynamicDraw : OpenGL.BufferUsageARB.StaticDraw);
                    vertexBufferLength = requiredLength;
                } else {
                    glContext.BufferSubData(BufferTargetARB.ArrayBuffer, IntPtr.Zero, new UIntPtr(requiredLength), new IntPtr(pointer));
                }
            }
        }

        public unsafe void BufferIndices(uint[] data, bool dynamic) {
            Use();
            uint requiredLength = (uint) (sizeof(uint) * data.Length);
            fixed (void* pointer = &data[0]) {
                if (requiredLength > indexBufferLength) {
                    glContext.BufferData(OpenGL.BufferTargetARB.ElementArrayBuffer, requiredLength, new IntPtr(pointer), dynamic ? OpenGL.BufferUsageARB.DynamicDraw : OpenGL.BufferUsageARB.StaticDraw);
                    indexBufferLength = requiredLength;
                } else {
                    glContext.BufferSubData(BufferTargetARB.ElementArrayBuffer, IntPtr.Zero, new UIntPtr(requiredLength), new IntPtr(pointer));
                }
            }
        }

        /// <summary>
        /// Defines the vertex attributes in an OpenGL vertex array object.
        /// Sends as floats.
        /// </summary>
        /// <param name="enableAttributes">Whether or not to automatically enable the attributes that are being defined. Defaults to true.</param>
        /// <param name="definitions">The definitions for the vertex array object.</param>
        public void defineVertexAttributes(bool enableAttributes = true, params VertexAttributeDefinition[] definitions) {
            Use();
            int offset = 0;
            uint stride = 0;
            foreach (VertexAttributeDefinition definition in definitions)
            {
                stride += (uint)definition.size * sizeof(float);
            }
            foreach (VertexAttributeDefinition definition in definitions)
            {
                glContext.VertexAttribPointer(definition.attributeIndex, definition.size, OpenGL.VertexAttribPointerType.Float, false, stride, new IntPtr(offset));
                offset += definition.size * sizeof(float);
                if (enableAttributes) glContext.EnableVertexAttribArray(definition.attributeIndex);
            }
        }

        /// <summary>
        /// Enaables the vertex array object's definitions at the given attribute indices.
        /// </summary>
        /// <param name="attributeIndices">The attribute indices to be enabled.</param>
        public void EnableAttributes(params uint[] attributeIndices) {
            Use();
            foreach (uint attrib in attributeIndices)
            {
                glContext.EnableVertexAttribArray(attrib);
            }
        }

        /// <summary>
        /// Disables the vertex array object's definitions at the given attribute indices.
        /// </summary>
        /// <param name="attributeIndices">The attribute indices to be disabled.</param>
        public void DisableAttributes(params uint[] attributeIndices) {
            Use();
            foreach (uint attrib in attributeIndices)
            {
                glContext.DisableVertexAttribArray(attrib);
            }
        }

        protected virtual void Dispose(bool disposing)
        {
            if (this.disposed) return;
            if (disposing) {
            }
            this.disposed = true;
            glContext.DeleteVertexArrays(1, new uint[] {vertexArrayHandle});
            glContext.DeleteBuffers(2, new uint[] {indexBufferHandle, vertexBufferHandle});
        }

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

        ~VertexArrayBuffers() {
            Dispose(false);
        }
    }

    public struct VertexAttributeDefinition {
        public uint attributeIndex;
        public int size;

        public VertexAttributeDefinition(uint attributeIndex, int size) {
            this.attributeIndex = attributeIndex;
            this.size = size;
        }
    }
}