using Engine.Graphics.Shader;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;

namespace Engine.Graphics.Render.Mesh;

public class MeshRenderer(Asset.Mesh.Mesh parMesh, int parInstanceCount, Program parProgram)
  : InstancedRenderer<Asset.Mesh.Mesh.Vertex, MeshInstanceVertex>(
    PrimitiveType.Triangles,
    parInstanceCount,
    parMesh.Indices.ToArray(),
    parMesh.Vertices.ToArray(),
    parProgram
  )
{
  private readonly TextureUnitMap _textureUnitMap = new(16);
  private readonly int[] _textureUnitIndices = new int[16];

  private int _frameHash;
  private int _previousHash;

  public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null)
  {
    if (_queuedInstanceCount >= _instanceCount)
    {
      throw new InvalidOperationException("Instance count exceeded");
    }

    var textureId = -1;
    if (parTexture != null)
    {
      textureId = _textureUnitMap.GetUnit(parTexture);
    }

    _instanceVertices[_queuedInstanceCount]._textureId = textureId;
    _instanceVertices[_queuedInstanceCount]._modelMatrix = parModelMatrix;

    _frameHash = HashCode.Combine(_frameHash, _instanceVertices[_queuedInstanceCount]);

    _queuedInstanceCount++;
  }

  protected override void SetAdditionalUniforms(Program parProgram)
  {
    foreach (var (texture, unit) in _textureUnitMap.Textures)
    {
      texture.BindUnit(unit);
      _textureUnitIndices[unit] = unit;
    }

    parProgram.SetUniform("uTexture", _textureUnitIndices);
  }

  protected override bool DataChanged()
  {
    return _frameHash != _previousHash;
  }

  public override void Reset()
  {
    base.Reset();
    _textureUnitMap.Reset();
    _previousHash = _frameHash;
    _frameHash = 0;
  }
}