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

namespace Engine.Graphics.Render.Quad;

public class QuadRenderer : InstancedRenderer<QuadCommonVertex, QuadInstanceVertex>
{
  private readonly TextureUnitMap _textureUnitMap = new(16);

  private readonly int[] _textureUnitIndices = new int[16];

  private int _frameHash;
  private int _previousHash;

  public QuadRenderer(Engine parEngine, int parInstanceCount)
    : base(PrimitiveType.Triangles, parInstanceCount, [0, 2, 1, 2, 3, 1], [
        new QuadCommonVertex { _position = new Vector3(-0.5f, -0.5f, 0), _uv = new Vector2(0, 0) },
        new QuadCommonVertex { _position = new Vector3(-0.5f, 0.5f, 0), _uv = new Vector2(0, 1) },
        new QuadCommonVertex { _position = new Vector3(0.5f, -0.5f, 0), _uv = new Vector2(1, 0) },
        new QuadCommonVertex { _position = new Vector3(0.5f, 0.5f, 0), _uv = new Vector2(1, 1) },
      ],
      parEngine.EngineResourceManager.Load<Program>("shader/quad"))
  {
  }

  public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, 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]._modelMatrix = parModelMatrix;
    _instanceVertices[_queuedInstanceCount]._color = parColor;
    _instanceVertices[_queuedInstanceCount]._textureId = textureId + 0.01f;

    _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;
  }
}