using Engine.Asset.Font;
using Engine.Graphics.Buffer;
using Engine.Graphics.Shader;
using Engine.Graphics.Texture;
using OpenTK.Graphics.OpenGL;
using OpenTK.Mathematics;

namespace Engine.Graphics.Render.Text;

public class TextRenderer
{
  private readonly IndexBuffer _indexBuffer;
  private readonly VertexArray _vertexArray;

  private readonly VertexBuffer<GlyphVertex> _glyphVertexBuffer;
  // private readonly VertexBuffer<GlyphCommonVertex> _glyphCommonVertexBuffer;

  private readonly Program _program;
  private readonly int _characterCount;
  private int _queuedCharacterCount;

  private readonly TextureUnitMap _textureUnitMap = new(16);
  private readonly int[] _textureUnitIndices = new int[16];

  private readonly GlyphVertex[] _glyphVertices;
  // private readonly GlyphCommonVertex[] _glyphCommonVertices;

  public TextRenderer(Engine parEngine, int parCharacterCount)
  {
    _characterCount = parCharacterCount;
    _glyphVertices = new GlyphVertex[parCharacterCount * 4];
    // _glyphCommonVertices = new GlyphCommonVertex[parCharacterCount];

    _program = parEngine.EngineResourceManager.Load<Program>("shader/text");

    _indexBuffer = new IndexBuffer(CreateIndices(_characterCount));
    _glyphVertexBuffer = new VertexBuffer<GlyphVertex>(_characterCount * 4,
      BufferStorageFlags.DynamicStorageBit);
    // _glyphCommonVertexBuffer = new VertexBuffer<GlyphCommonVertex>(_characterCount,
    //   BufferStorageFlags.DynamicStorageBit);

    _vertexArray = new VertexArray();

    _vertexArray.BindIndexBuffer(_indexBuffer);
    _vertexArray.BindVertexBuffer(_glyphVertexBuffer, 0, 0);
    // _vertexArray.BindVertexBuffer(_glyphCommonVertexBuffer, 1, 1);
  }

  public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix)
  {
    if (_queuedCharacterCount >= _characterCount)
    {
      throw new InvalidOperationException("Character count exceeded");
    }

    var textureId = _textureUnitMap.GetUnit(parFont.AtlasTexture);

    var fontIterator = parFont.Iterator(parText);

    foreach (var glyphData in fontIterator)
    {
      for (var i = 0; i < 4; i++)
      {
        _glyphVertices[_queuedCharacterCount * 4 + i]._position = glyphData.Positions[i] + glyphData.Offset;
        _glyphVertices[_queuedCharacterCount * 4 + i]._uv = glyphData.UVs[i];
        _glyphVertices[_queuedCharacterCount * 4 + i]._color = parColor;
        _glyphVertices[_queuedCharacterCount * 4 + i]._atlasId = textureId;
        _glyphVertices[_queuedCharacterCount * 4 + i]._unitRange = parFont.UnitRange;
        _glyphVertices[_queuedCharacterCount * 4 + i]._modelMatrix = parModelMatrix;
      }

      _queuedCharacterCount++;
    }
  }

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

    _glyphVertexBuffer.UploadData(_glyphVertices, _queuedCharacterCount * 4);
    // _glyphCommonVertexBuffer.UploadData(_glyphCommonVertices, _queuedCharacterCount);
    _vertexArray.Bind();

    _program.Bind();
    _program.SetUniform("u_Projection", in parProjectionMatrix);
    _program.SetUniform("u_View", in parViewMatrix);
    foreach (var (texture, unit) in _textureUnitMap.Textures)
    {
      texture.BindUnit(unit);
      _textureUnitIndices[unit] = unit;
    }

    _program.SetUniform("u_Atlas", _textureUnitIndices);

    GL.DrawElements(PrimitiveType.Triangles, _queuedCharacterCount * 6, DrawElementsType.UnsignedInt, 0);
  }

  public void Reset()
  {
    _textureUnitMap.Reset();
    _queuedCharacterCount = 0;
  }

  private static uint[] CreateIndices(int parCharacterCount)
  {
    var indices = new uint[parCharacterCount * 6];

    for (var i = 0; i < parCharacterCount; i++)
    {
      var index = i * 6;
      var offset = i * 4;
      indices[index + 0] = (uint)offset + 0;
      indices[index + 1] = (uint)offset + 2;
      indices[index + 2] = (uint)offset + 1;
      indices[index + 3] = (uint)offset + 2;
      indices[index + 4] = (uint)offset + 3;
      indices[index + 5] = (uint)offset + 1;
    }

    return indices;
  }
}