using Engine.Graphics.Buffer;
using Engine.Graphics.Buffer.Vertex;
using Engine.Graphics.Shader;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;

namespace Engine.Graphics.Render;

public abstract class InstancedRenderer<C, I>
  where C : struct, IVertex
  where I : struct, IVertex
{
  private readonly IndexBuffer _indexBuffer;
  private readonly VertexBuffer<C> _commonVertexBuffer;
  private readonly VertexBuffer<I> _instanceVertexBuffer;
  private readonly VertexArray _vertexArray;

  protected readonly int _instanceCount;
  protected int _queuedInstanceCount;
  protected readonly I[] _instanceVertices;

  private readonly PrimitiveType _primitiveType;
  private readonly Program _program;

  protected InstancedRenderer(PrimitiveType parPrimitiveType, int parInstanceCount,
    uint[] parIndexBuffer, C[] parInstanceBuffer,
    Program parProgram)
  {
    _program = parProgram;
    _instanceCount = parInstanceCount;
    _queuedInstanceCount = 0;
    _instanceVertices = new I[parInstanceCount];
    _primitiveType = parPrimitiveType;

    _indexBuffer = new IndexBuffer(parIndexBuffer);
    _commonVertexBuffer = new VertexBuffer<C>(parInstanceBuffer);
    _instanceVertexBuffer = new VertexBuffer<I>(parInstanceCount,
      BufferStorageFlags.DynamicStorageBit);
    _vertexArray = new VertexArray();

    _vertexArray.BindIndexBuffer(_indexBuffer);
    _vertexArray.BindVertexBuffer(_commonVertexBuffer, 0, 0);
    _vertexArray.BindVertexBuffer(_instanceVertexBuffer, 1, 1);
  }

  public void Render(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
  {
    if (_queuedInstanceCount <= 0)
    {
      return;
    }

    if (DataChanged())
      _instanceVertexBuffer.UploadData(_instanceVertices, _queuedInstanceCount);
    _vertexArray.Bind();

    _program.Bind();
    _program.SetUniform("uProjectionMatrix", in parProjectionMatrix);
    _program.SetUniform("uViewMatrix", in parViewMatrix);
    SetAdditionalUniforms(_program);

    GL.DrawElementsInstanced(_primitiveType, _indexBuffer.Count * _queuedInstanceCount,
      DrawElementsType.UnsignedInt, 0,
      _queuedInstanceCount);
  }

  public virtual void Reset()
  {
    _queuedInstanceCount = 0;
  }

  protected virtual void SetAdditionalUniforms(Program parProgram)
  {
  }

  protected virtual bool DataChanged()
  {
    return true;
  }
}