// SPDX-License-Identifier: Unlicense

#include "font.h"
#include "opengl.h"
#include "data.h"
#include "global.h"
#include "shader.h"
#include "string_.h"
#include "geometry.h"

#include <codecvt>
#include <string.h>
#include <vector>

//#define NOTE_NAMES_FLAT
#define NOTE_NAMES_SHARP

#ifdef NOTE_NAMES_FLAT
inline constexpr const char8_t noteNames[][3] =
{
  u8"C", u8"D\b", u8"D", u8"E\b", u8"E", u8"F", u8"G\b", u8"G", u8"A\b", u8"A", u8"B\b", u8"B"
};
#endif // NOTE_NAMES_FLAT
#ifdef NOTE_NAMES_SHARP
inline constexpr const char8_t noteNames[][3] =
{
  u8"C", u8"C#", u8"D", u8"D#", u8"E", u8"F", u8"F#", u8"G", u8"G#", u8"A", u8"A#", u8"B"
};
#endif // NOTE_NAMES_SHARP
#ifdef NOTE_NAMES_SHARP_FULL
inline constexpr const char8_t noteNames[][5] =
{
  u8"C-1", u8"C#-1", u8"D-1", u8"D#-1", u8"E-1", u8"F-1", u8"F#-1", u8"G-1", u8"G#-1", u8"A-1", u8"A#-1", u8"B-1",
  u8"C0",  u8"C#0",  u8"D0",  u8"D#0",  u8"E0",  u8"F0",  u8"F#0",  u8"G0",  u8"G#0",  u8"A0",  u8"A#0",  u8"B0",
  u8"C1",  u8"C#1",  u8"D1",  u8"D#1",  u8"E1",  u8"F1",  u8"F#1",  u8"G1",  u8"G#1",  u8"A1",  u8"A#1",  u8"B1",
  u8"C2",  u8"C#2",  u8"D2",  u8"D#2",  u8"E2",  u8"F2",  u8"F#2",  u8"G2",  u8"G#2",  u8"A2",  u8"A#2",  u8"B2",
  u8"C3",  u8"C#3",  u8"D3",  u8"D#3",  u8"E3",  u8"F3",  u8"F#3",  u8"G3",  u8"G#3",  u8"A3",  u8"A#3",  u8"B3",
  u8"C4",  u8"C#4",  u8"D4",  u8"D#4",  u8"E4",  u8"F4",  u8"F#4",  u8"G4",  u8"G#4",  u8"A4",  u8"A#4",  u8"B4",
  u8"C5",  u8"C#5",  u8"D5",  u8"D#5",  u8"E5",  u8"F5",  u8"F#5",  u8"G5",  u8"G#5",  u8"A5",  u8"A#5",  u8"B5",
  u8"C6",  u8"C#6",  u8"D6",  u8"D#6",  u8"E6",  u8"F6",  u8"F#6",  u8"G6",  u8"G#6",  u8"A6",  u8"A#6",  u8"B6",
  u8"C7",  u8"C#7",  u8"D7",  u8"D#7",  u8"E7",  u8"F7",  u8"F#7",  u8"G7",  u8"G#7",  u8"A7",  u8"A#7",  u8"B7",
  u8"C8",  u8"C#8",  u8"D8",  u8"D#8",  u8"E8",  u8"F8",  u8"F#8",  u8"G8",  u8"G#8",  u8"A8",  u8"A#8",  u8"B8",
  u8"C9",  u8"C#9",  u8"D9",  u8"D#9",  u8"E9",  u8"F9",  u8"F#9",  u8"G9"
};
#endif // NOTE_NAMES_SHARP_FULL

GLuint Font1::fretNumberTextureCache[25]; // cache for numbers 1-24
GLuint Font1::noteNamesTextureCache[ARRAY_SIZE(noteNames)]; // cache for flat notes
static struct MiscTextTextureCache
{
  std::u8string text;
  GLuint texture;
  GLuint textureWidth;
} miscTextTextureCache[256];
static i32 miscTextTextureCacheIndex = 0;

#ifdef SHR3D_FONT_MSDF
static std::unordered_map<char32_t, const Data::Texture::GlyphInfo*> glyphInfoMap;
#endif // SHR3D_FONT_MSDF

#ifdef SHR3D_FONT_BITMAP
static std::vector<u8> createTextBitmap(const char8_t* text, i32 letters)
{
  const i32 rowOffset = (letters - 1) * Font1::charWidth;

  std::vector<u8> textBitmap(i64(letters) * Font1::charWidth * Font1::charHeight);

  for (i32 c = 0; c < letters; ++c)
  {
    char letter = text[c];

    if (letter < u8' ' || letter > u8'~')
      letter = u8'~' + 1;

    const i32 offsetY = ((letter - ' ') / 16) * Font1::charHeight;
    const i32 offsetX = (letter % 16) * Font1::charWidth;

    i32 i = Font1::charWidth * c;
    for (i32 y = 0; y < Font1::charHeight; ++y)
    {
      for (i32 x = 0; x < Font1::charWidth; ++x)
      {
        // only the alpha channel is needed
        textBitmap[i] = Data::Texture::fontBitmap[(y + offsetY) * Data::Texture::fontBitmapWidth + (x + offsetX)];

        ++i;

        if (i % Font1::charWidth == 0)
          i += rowOffset;
      }
    }
  }

  return textBitmap;
}
#endif // SHR3D_FONT_BITMAP
#ifdef SHR3D_FONT_MSDF

static std::vector<u8> createTextBitmap(const char8_t* text, i32& textureWidth)
{
  ASSERT(text != nullptr);
  ASSERT(text[0] != '\0');

  textureWidth = Font1::textureWidth(text);

  std::vector<u8> rgbTextBitmap(textureWidth * Font1::charHeight * 3);

  i32 i = 0;
  i32 xOffset = 0;
  while (text[i] != u8'\0')
  {
    i8 utf8CharBytes = 0;
    const char32_t utf32Char = String::utf32Char(&text[i], utf8CharBytes);

    const auto found = glyphInfoMap.find(utf32Char);

    const Data::Texture::GlyphInfo& glyphInfo = found != glyphInfoMap.end() ? *found->second : *glyphInfoMap.find(U' ')->second;

    {
      const u8* rgb = &Data::Texture::fontMSDF[glyphInfo.byteOffset];

      for (u32 y = 0; y < Font1::charHeight; ++y)
      {
        auto a = y * textureWidth * 3 + xOffset * 3;
        auto b = y * glyphInfo.width * 3;
        auto c = glyphInfo.width * 3;
        memcpy(&rgbTextBitmap[a], &rgb[b], c);
      }

      xOffset += glyphInfo.width;
    }

    i += utf8CharBytes;
  }

  return rgbTextBitmap;
}
#endif // SHR3D_FONT_MSDF

void Font1::init()
{
  ASSERT(glGetError() == GL_NO_ERROR);
#ifdef SHR3D_FONT_MSDF
  for (i32 i = 0; i < ARRAY_SIZE(Data::Texture::codepointGlyphInfos); ++i)
  {
    const Data::Texture::CodepointGlyphInfo& codepointGlyphInfo = Data::Texture::codepointGlyphInfos[i];
    glyphInfoMap[codepointGlyphInfo.codepoint] = &codepointGlyphInfo.glyphInfo;
  }
#endif // SHR3D_FONT_MSDF

  for (i32 fretNumber = 0; fretNumber < ARRAY_SIZE(fretNumberTextureCache); ++fretNumber)
  {
    char8_t fretNumberStr[3];
    sprintf(reinterpret_cast<char*>(fretNumberStr), "%d", fretNumber);

    GL(glGenTextures(1, &fretNumberTextureCache[fretNumber]));
    GL(glBindTexture(GL_TEXTURE_2D, fretNumberTextureCache[fretNumber]));
#ifdef SHR3D_FONT_BITMAP
    const i32 letters = i32(strlen(reinterpret_cast<const char*>(fretNumberStr)));
    GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
    GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
    const std::vector<u8> textBitmap = createTextBitmap(fretNumberStr, letters);
    GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, u64(Font1::charWidth * letters), Font1::charHeight, 0, GL_RED, GL_UNSIGNED_BYTE, textBitmap.data()));
    ASSERT(glGetError() == GL_NO_ERROR);
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
#endif // SHR3D_FONT_BITMAP
#ifdef SHR3D_FONT_MSDF
    i32 textureWidth;
    const std::vector<u8> textBitmap = createTextBitmap(fretNumberStr, textureWidth);
    GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
    GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
    GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, Font1::charHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, textBitmap.data()));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
#endif // SHR3D_FONT_MSDF
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  }

  for (i32 note = 0; note < ARRAY_SIZE(noteNamesTextureCache); ++note)
  {
    GL(glGenTextures(1, &noteNamesTextureCache[note]));
    GL(glBindTexture(GL_TEXTURE_2D, noteNamesTextureCache[note]));
#ifdef SHR3D_FONT_BITMAP
    const i32 letters = i32(strlen(reinterpret_cast<const char*>(noteNames[note])));
    const std::vector<u8> textBitmap = createTextBitmap(noteNames[note], letters);
    GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
    GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
    GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, u32(Font1::charWidth * letters), Font1::charHeight, 0, GL_RED, GL_UNSIGNED_BYTE, textBitmap.data()));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
#endif // SHR3D_FONT_BITMAP
#ifdef SHR3D_FONT_MSDF
    i32 textureWidth;
    const std::vector<u8> textBitmap = createTextBitmap(noteNames[note], textureWidth);
    GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
    GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
    GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
    GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, Font1::charHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, textBitmap.data()));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
#endif // SHR3D_FONT_MSDF
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
    GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  }
}

i32 Font1::textureWidth(const char8_t* text)
{
  ASSERT(text != nullptr);
  ASSERT(text[0] != u8'\0');

  i32 width = 0;

#ifdef SHR3D_FONT_BITMAP
  return strlen(reinterpret_cast<const char*>(text)) * Font1::charWidth;
#endif // SHR3D_FONT_BITMAP
#ifdef SHR3D_FONT_MSDF
  i32 i = 0;
  while (text[i] != u8'\0')
  {
    i8 utf8CharBytes = 0;
    const char32_t utf32Char = String::utf32Char(&text[i], utf8CharBytes);

    const auto found = glyphInfoMap.find(utf32Char);

    const Data::Texture::GlyphInfo& glyphInfo = found != glyphInfoMap.end() ? *found->second : *glyphInfoMap.find(U' ')->second;
    width += glyphInfo.width;
    i += utf8CharBytes;
  }

  return width;
#endif // SHR3D_FONT_MSDF
}

void Font1::draw(const char8_t* text, f32 posX, f32 posY, f32 posZ, f32 scaleX, f32 scaleY)
{
  const f32 left = posX - scaleX;
  const f32 top = posY - scaleY;
  const f32 right = posX + scaleX;
  const f32 bottom = posY + scaleY;

  // for sprites triangleStrip: 4 Verts + UV. Format: x,y,z,u,v
  const GLfloat v[] = {
    left , top, posZ, 0.0f, 1.0f,
    right, top, posZ, 1.0f, 1.0f,
    left, bottom, posZ, 0.0f, 0.0f,
    right, bottom, posZ, 1.0f, 0.0f,
  };

  GL(glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW));

  GL(glBindTexture(GL_TEXTURE_2D, textTexture(text)));
  GL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
  GL(glBindTexture(GL_TEXTURE_2D, Global::texture));
}

//void Font1::drawFretNumber(const i32 fretNumber, const f32 posX, const f32 posY, const f32 posZ, const f32 scaleX, const f32 scaleY)
//{
//  ASSERT(fretNumber >= 0);
//  ASSERT(fretNumber < 25);
//
//  const f32 left = posX - scaleX;
//  const f32 top = posY - scaleY;
//  const f32 right = posX + scaleX;
//  const f32 bottom = posY + scaleY;
//
//  const GLfloat v[] = {
//    left , top, posZ, 0.0f, 1.0f,
//    right, top, posZ, 1.0f, 1.0f,
//    left, bottom, posZ, 0.0f, 0.0f,
//    right, bottom, posZ, 1.0f, 0.0f,
//  };
//
//  GL(glBindTexture(GL_TEXTURE_2D, fretNumberTextureCache[fretNumber]));
//  GL(glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW));
//  GL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
//}

//void Font1::drawFretNumber2(const GLint modelUniform, const i32 fretNumber, const f32 posX, const f32 posY, const f32 posZ, const f32 scaleX, const f32 scaleY)
//{
//  ASSERT(fretNumber >= 0);
//  ASSERT(fretNumber < 25);
//
//  const mat4 model = {
//    .m00 = scaleX,
//    .m11 = scaleY,
//    .m03 = posX,
//    .m13 = posY,
//    .m23 = posZ
//  };
//
//  GL(glUniformMatrix4fv(modelUniform, 1, GL_FALSE, &model.m00));
//  GL(glBindTexture(GL_TEXTURE_2D, fretNumberTextureCache[fretNumber]));
//
//  GL(glDrawArrays(GL_TRIANGLES, to_underlying_(Geometry::Offsets::planeZFlippedV), to_underlying_(Geometry::Vertecies::planeZFlippedV)));
//}

//void Font1::drawNoteNameFlat(const MidiNote midiNote, const f32 posX, const f32 posY, const f32 posZ, const f32 scaleX, const f32 scaleY)
//{
//  ASSERT(midiNote < MidiNote::COUNT);
//
//  const f32 left = posX - scaleX;
//  const f32 top = posY - scaleY;
//  const f32 right = posX + scaleX;
//  const f32 bottom = posY + scaleY;
//
//  const GLfloat v[] = {
//    left , top, posZ, 0.0f, 1.0f,
//    right, top, posZ, 1.0f, 1.0f,
//    left, bottom, posZ, 0.0f, 0.0f,
//    right, bottom, posZ, 1.0f, 0.0f,
//  };
//
//
//  GL(glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW));
//
//#ifdef NOTE_NAMES_SHARP_FULL
//  GL(glBindTexture(GL_TEXTURE_2D, noteNamesTextureCache[to_underlying_(midiNote)]));
//#else
//  const u8 noteNameIndex = to_underlying_(midiNote) % 12;
//  GL(glBindTexture(GL_TEXTURE_2D, noteNamesTextureCache[noteNameIndex]));
//#endif
//
//  GL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
//}

//mat4 Font1::createFontModelMatrix(const f32 posX, const f32 posY, const f32 posZ, const f32 scaleX, const f32 scaleY)
//{
//  const f32 left = posX - scaleX;
//  const f32 top = posY - scaleY;
//  const f32 right = posX + scaleX;
//  const f32 bottom = posY + scaleY;
//
//  const mat4 model = {
//    .m00 = 0.5f * (right - left),
//    .m11 = 0.5f * (bottom - top),
//    .m03 = left + 0.5f * (right - left),
//    .m13 = bottom + 0.5f * (top - bottom),
//    .m23 = posZ
//  };
//
//  return model;
//}

GLuint Font1::textTexture(const char8_t* text)
{
  for (i32 i = 0; i < ARRAY_SIZE(miscTextTextureCache); ++i)
  {
    if (miscTextTextureCache[i].text == text)
    {
      return miscTextTextureCache[i].texture;
    }
  }

  MiscTextTextureCache& mttc = miscTextTextureCache[miscTextTextureCacheIndex];

  if (mttc.texture == 0)
    GL(glGenTextures(1, &mttc.texture));

  GL(glBindTexture(GL_TEXTURE_2D, mttc.texture));
#ifdef SHR3D_FONT_BITMAP
  const i32 letters = i32(strlen(reinterpret_cast<const char*>(text)));
  const std::vector<u8> textBitmap = createTextBitmap(text, letters);
  mttc.text = text;
  mttc.textureWidth = letters * Font1::charWidth;
  GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
  GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
  GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
  GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
  GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, Font1::charWidth * letters, Font1::charHeight, 0, GL_RED, GL_UNSIGNED_BYTE, textBitmap.data()));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
#endif // SHR3D_FONT_BITMAP
#ifdef SHR3D_FONT_MSDF
  i32 textureWidth;
  const std::vector<u8> textBitmap = createTextBitmap(text, textureWidth);
  mttc.text = text;
  mttc.textureWidth = textureWidth;
  GL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
  GL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
  GL(glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0));
  GL(glPixelStorei(GL_UNPACK_SKIP_ROWS, 0));
  GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, textureWidth, Font1::charHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, textBitmap.data()));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
#endif // SHR3D_FONT_MSDF
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));

  miscTextTextureCacheIndex = (miscTextTextureCacheIndex + 1) % ARRAY_SIZE(miscTextTextureCache);

  return mttc.texture;
}
