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