using System.Collections;
using OpenTK.Mathematics;

namespace Engine.Asset.Font;

public class FontIterator : IEnumerable<FontIterator.NextGlyphData>
{
  public float MaxWidth => _maxWidth;
  public float MaxHeight => _maxHeight;

  private readonly Font _font;
  private readonly string _text;

  private int _currentIndex;
  private int _previousCodepoint = -1;
  private int _lineCharCount;
  private Vector2 _cursor = Vector2.Zero;
  private Vector2 _kerning = Vector2.Zero;

  private float _maxWidth;
  private float _maxHeight;

  public FontIterator(Font parFont, string parText)
  {
    _font = parFont;
    _text = parText;
  }

  private static bool IsLineBreak(int parCodepoint)
  {
    return parCodepoint == '\n';
  }

  private static bool IsTab(int parCodepoint)
  {
    return parCodepoint == '\t';
  }

  private void Tab()
  {
    var spaceGlyph = _font.GetGlyph(' ');
    if (spaceGlyph == null)
      return;

    var missingSpaces = 4 - _lineCharCount % 4;
    _cursor.X += missingSpaces * spaceGlyph.Advance;
  }

  private void LineBreak()
  {
    _kerning = Vector2.Zero;
    _cursor.X = 0;
    _cursor.Y += _font.Metadata.Metrics.LineHeight;
    _lineCharCount = 0;
    _previousCodepoint = -1;
  }

  public IEnumerator<NextGlyphData> GetEnumerator()
  {
    while (_currentIndex < _text.Length)
    {
      var codepoint = char.ConvertToUtf32(_text, _currentIndex);

      if (IsLineBreak(codepoint))
      {
        LineBreak();
        _currentIndex += 1;
        continue;
      }

      if (IsTab(codepoint))
      {
        Tab();
        _currentIndex += 1;
        continue;
      }

      var glyph = _font.GetGlyph(codepoint);
      if (glyph == null)
      {
        glyph = _font.GetGlyph('?');
        if (glyph == null)
        {
          _currentIndex += 1;
          continue;
        }
      }

      if (_previousCodepoint != -1)
      {
        var kerning = _font.GetKerning(_previousCodepoint, glyph.Unicode);
        if (kerning != null)
        {
          _kerning.X += kerning.Advance;
        }
      }

      var glyphData = _font.GetGlyphData(glyph.Unicode);

      _maxWidth = Math.Max(_maxWidth, _cursor.X + glyph.Advance);
      _maxHeight = Math.Max(_maxHeight, _cursor.Y + _font.Metadata.Metrics.Ascender);

      if (glyphData != null)
        yield return new NextGlyphData(glyphData.Positions, glyphData.UVs, (_cursor + _kerning) * new Vector2(1, -1));

      _cursor.X += glyph.Advance;
      _lineCharCount++;
      _previousCodepoint = glyph.Unicode;
      _currentIndex += 1;
    }
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }

  public record NextGlyphData(Vector2[] Positions, Vector2[] UVs, Vector2 Offset);
}