#include "render/TextRenderer.h"
#include <ft2build.h>
#include FT_FREETYPE_H

#include "Environment.h"
#include "render/OpenGlExtensions.h"
#include <iostream>
#include <array>
#include <algorithm>
#include <cmath>
#include <unordered_set>

namespace ZL {

// Decode one UTF-8 codepoint from str at byte position i; advances i past the sequence.
static uint32_t nextUtf8Codepoint(const std::string& str, size_t& i)
{
    auto b = [&](size_t offset) { return static_cast<unsigned char>(str[i + offset]); };
    unsigned char c = b(0);
    uint32_t cp;
    size_t bytes;
    if      (c < 0x80) { cp = c;        bytes = 1; }
    else if (c < 0xE0) { cp = c & 0x1F; bytes = 2; }
    else if (c < 0xF0) { cp = c & 0x0F; bytes = 3; }
    else               { cp = c & 0x07; bytes = 4; }
    for (size_t j = 1; j < bytes && i + j < str.size(); ++j)
        cp = (cp << 6) | (b(j) & 0x3F);
    i += bytes;
    return cp;
}

TextRenderer::~TextRenderer()
{
    glyphs.clear();
	atlasTexture.reset();
    textMesh.positionVBO.reset();
}

bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename)
{
    
    r = &renderer;

#ifdef EMSCRIPTEN
    r->shaderManager.AddShaderFromFiles(shaderName,
        "resources/shaders/text2d.vertex",
        "resources/shaders/text2d_web.fragment",
        zipfilename
    );
#else
    r->shaderManager.AddShaderFromFiles(shaderName,
        "resources/shaders/text2d.vertex",
        "resources/shaders/text2d_desktop.fragment",
        zipfilename
    );
#endif
    ZL::CheckGlError(__FILE__, __LINE__);
    if (!loadGlyphs(ttfPath, pixelSize, zipfilename)) return false;
    ZL::CheckGlError(__FILE__, __LINE__);

    textMesh.data.PositionData.resize(6, Eigen::Vector3f(0, 0, 0));
    textMesh.RefreshVBO();
    ZL::CheckGlError(__FILE__, __LINE__);
    return true;
}

void TextRenderer::ClearCache()
{
    cache.clear();
}

bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename)
{
    // 1. Загружаем сырые данные из ZIP
    std::vector<char> fontData;
    try {
        fontData = !zipfilename.empty()
            ? readFileFromZIP(ttfPath, zipfilename)
            : readFile(ttfPath); // Предполагаем наличие функции readFile
    }
    catch (const std::exception& e) {
        std::cerr << "TextRenderer: failed to read font data: " << e.what() << "\n";
        return false;
    }

    if (fontData.empty()) return false;

    FT_Library ft;
    if (FT_Init_FreeType(&ft)) {
        std::cerr << "FreeType: FT_Init_FreeType failed\n";
        return false;
    }

    // 2. Инициализируем Face из памяти
    FT_Face face;
    // Важно: передаем указатель на буфер и его размер
    if (FT_New_Memory_Face(ft,
        reinterpret_cast<const FT_Byte*>(fontData.data()),
        static_cast<FT_Long>(fontData.size()),
        0,
        &face))
    {
        std::cerr << "FreeType: failed to load font from memory: " << ttfPath << "\n";
        FT_Done_FreeType(ft);
        return false;
    }

    // Ensure we use the Unicode charmap so Cyrillic codepoints resolve correctly.
    FT_Select_Charmap(face, FT_ENCODING_UNICODE);
    FT_Set_Pixel_Sizes(face, 0, pixelSize);
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // glyphs.clear();
    
    // Сначала собираем все глифы в память
    struct GlyphBitmap {
        int width = 0;
        int height = 0;
        std::vector<char> data; // R8 байты (ширина*height)
        Eigen::Vector2f bearing;
        unsigned int advance = 0;
    };

    std::vector<std::pair<uint32_t, GlyphBitmap>> glyphList;
    glyphList.reserve(128 - 32);

    int maxGlyphHeight = 0;

    for (unsigned char c = 32; c < 128; ++c) {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER)) {
            // пропускаем если не удалось загрузить, но сохраняем пустой запись с advance
            GlyphBitmap gb;
            gb.width = 0;
            gb.height = 0;
            gb.bearing = { 0.f, 0.f };
            gb.advance = 0;
            glyphList.emplace_back((uint32_t)c, std::move(gb));
            continue;
        }

        GlyphBitmap gb;
        gb.width = face->glyph->bitmap.width;
        gb.height = face->glyph->bitmap.rows;
        gb.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top);
        gb.advance = static_cast<unsigned int>(face->glyph->advance.x);

        size_t dataSize = static_cast<size_t>(gb.width) * static_cast<size_t>(gb.height);
        if (dataSize > 0) {
            gb.data.assign(face->glyph->bitmap.buffer, face->glyph->bitmap.buffer + dataSize);
            maxGlyphHeight = max(maxGlyphHeight, gb.height);
        }

        glyphList.emplace_back((uint32_t)c, std::move(gb));
    }

    // Load extra codepoints from resources/symbols.txt (UTF-8 encoded).
    {
        std::vector<char> symbolsRaw;
        try {
            symbolsRaw = !zipfilename.empty()
                ? readFileFromZIP("resources/symbols.txt", zipfilename)
                : readFile("resources/symbols.txt");
        }
        catch (...) {} // optional file — silently skip if missing

        if (!symbolsRaw.empty()) {
            // Collect codepoints already loaded (ASCII 32-127) to skip duplicates.
            std::unordered_set<uint32_t> seen;
            for (auto& p : glyphList) seen.insert(p.first);

            std::string symbolsStr(symbolsRaw.begin(), symbolsRaw.end());
            size_t pos = 0;
            while (pos < symbolsStr.size()) {
                uint32_t cp = nextUtf8Codepoint(symbolsStr, pos);
                if (cp < 32 || !seen.insert(cp).second) continue; // skip controls and duplicates

                GlyphBitmap gb;
                if (FT_Load_Char(face, cp, FT_LOAD_RENDER)) {
                    gb.width = 0; gb.height = 0;
                    gb.bearing = { 0.f, 0.f };
                    gb.advance = 0;
                } else {
                    gb.width   = face->glyph->bitmap.width;
                    gb.height  = face->glyph->bitmap.rows;
                    gb.bearing = Eigen::Vector2f((float)face->glyph->bitmap_left,
                                                 (float)face->glyph->bitmap_top);
                    gb.advance = static_cast<unsigned int>(face->glyph->advance.x);
                    size_t dataSize = static_cast<size_t>(gb.width) * static_cast<size_t>(gb.height);
                    if (dataSize > 0) {
                        gb.data.assign(face->glyph->bitmap.buffer,
                                       face->glyph->bitmap.buffer + dataSize);
                        maxGlyphHeight = max(maxGlyphHeight, gb.height);
                    }
                }
                glyphList.emplace_back(cp, std::move(gb));
            }
        }
    }

    lineHeight = std::max<float>(static_cast<float>(pixelSize) * 1.3f, static_cast<float>(maxGlyphHeight) * 1.25f);

    // Пакуем глифы в один атлас (упрощённый алгоритм строковой укладки)
    const int padding = 1;
    const int maxAtlasWidth = 1024; // безопасное значение для большинства устройств
    int curX = padding;
    int curY = padding;
    int rowHeight = 0;
    int neededWidth = 0;
    int neededHeight = 0;

    // Предварительно вычислим требуемый размер, укладывая в maxAtlasWidth
    for (auto& p : glyphList) {
        const GlyphBitmap& gb = p.second;
        int w = gb.width;
        int h = gb.height;
        if (curX + w + padding > maxAtlasWidth) {
            // новая строка
            neededWidth = max(neededWidth, curX);
            curX = padding;
            curY += rowHeight + padding;
            rowHeight = 0;
        }
        curX += w + padding;
        rowHeight = max(rowHeight, h);
    }
    neededWidth = max(neededWidth, curX);
    neededHeight = curY + rowHeight + padding;

    // Подгоняем к степеням двух (необязательно, но часто удобно)
    auto nextPow2 = [](int v) {
        int p = 1;
        while (p < v) p <<= 1;
        return p;
        };

    atlasWidth = static_cast<size_t>(nextPow2(max(16, neededWidth)));
    atlasHeight = static_cast<size_t>(nextPow2(max(16, neededHeight)));

    // Ограничение - если получилось слишком большое, попробуем без power-of-two
    if (atlasWidth > 4096) atlasWidth = static_cast<size_t>(neededWidth);
    if (atlasHeight > 4096) atlasHeight = static_cast<size_t>(neededHeight);

    // Создаём буфер атласа, инициализируем нулями (прозрачность)
    std::vector<char> atlasData(atlasWidth * atlasHeight, 0);

    // Второй проход - размещаем глифы и заполняем atlasData
    curX = padding;
    curY = padding;
    rowHeight = 0;

    for (auto &p : glyphList) {
        uint32_t ch = p.first;
        GlyphBitmap &gb = p.second;

        if (gb.width == 0 || gb.height == 0) {
            // пустой глиф — записываем UV с нулевым размером и метрики
            GlyphInfo gi;
            gi.size = Eigen::Vector2f(0.f, 0.f);
            gi.bearing = gb.bearing;
            gi.advance = gb.advance;
            gi.uv = Eigen::Vector2f(0.f, 0.f);
            gi.uvSize = Eigen::Vector2f(0.f, 0.f);
            glyphs.emplace(ch, gi);
            continue;
        }

        if (curX + gb.width + padding > static_cast<int>(atlasWidth)) {
            // новая строка
            curX = padding;
            curY += rowHeight + padding;
            rowHeight = 0;
        }

        // Копируем строки глифа в atlasData
        for (int row = 0; row < gb.height; ++row) {
            // FreeType буфер, как мы ранее использовали, хранит строки подряд.
            // Копируем gb.width байт из gb.data на позицию (curX, curY + row)
            int destY = curY + row;
            int destX = curX;
            char* destPtr = atlasData.data() + destY * atlasWidth + destX;
            const char* srcPtr = gb.data.data() + row * gb.width;
            std::memcpy(destPtr, srcPtr, static_cast<size_t>(gb.width));
        }

        // Сохраняем информацию о глифе (в пикселях и UV)
        GlyphInfo gi;
        gi.size = Eigen::Vector2f((float)gb.width, (float)gb.height);
        gi.bearing = gb.bearing;
        gi.advance = gb.advance;

        // UV: нормализуем относительно размера атласа. Здесь uv указывает на верх-лево.
        gi.uv = Eigen::Vector2f((float)curX / (float)atlasWidth, (float)curY / (float)atlasHeight);
        gi.uvSize = Eigen::Vector2f((float)gb.width / (float)atlasWidth, (float)gb.height / (float)atlasHeight);

        glyphs.emplace(ch, gi);

        curX += gb.width + padding;
        rowHeight = max(rowHeight, gb.height);
    }

    // Создаём Texture из atlasData (R8)
    TextureDataStruct atlasTex;
    atlasTex.width = atlasWidth;
    atlasTex.height = atlasHeight;
    atlasTex.format = TextureDataStruct::R8;
    atlasTex.mipmap = TextureDataStruct::NONE;
    atlasTex.data = std::move(atlasData);

    atlasTexture = std::make_shared<Texture>(atlasTex);

    // Очистка
    FT_Done_Face(face);
    FT_Done_FreeType(ft);

    // После FT_Done_Face данные из fontData больше не нужны, 
    // вектор сам очистится при выходе из функции.

    glBindTexture(GL_TEXTURE_2D, 0);
    return true;
}

float TextRenderer::measureTextWidth(const std::string& text, float scale) const
{
    if (text.empty()) {
        return 0.0f;
    }

    float penX = 0.0f;
    float maxLineWidth = 0.0f;

    size_t textPos = 0;
    while (textPos < text.size()) {
        uint32_t cp = nextUtf8Codepoint(text, textPos);

        if (cp == '\n') {
            maxLineWidth = max(maxLineWidth, penX);
            penX = 0.0f;
            continue;
        }

        auto it = glyphs.find(cp);
        if (it == glyphs.end()) {
            continue;
        }

        const GlyphInfo& g = it->second;
        penX += static_cast<float>(g.advance >> 6) * scale;
    }

    maxLineWidth = max(maxLineWidth, penX);
    return maxLineWidth;
}

void TextRenderer::drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color)
{
    if (!r || text.empty() || !atlasTexture) return;

    // формируем ключ кеша
    std::string key = text + "|" + std::to_string(scale) + "|" + (centered ? "1" : "0");
    auto itCache = cache.find(key);

    if (itCache == cache.end()) {
        VertexDataStruct textData;
        float penX = 0.0f;
        float penY = 0.0f;

        float totalW = 0.0f;
        float maxH = 0.0f;
        
        float maxLineWidth = 0.0f;
        size_t textPos = 0;
        while (textPos < text.size()) {
            uint32_t cp = nextUtf8Codepoint(text, textPos);
            if (cp == '\n') {
				maxLineWidth = max(maxLineWidth, penX);
                penX = 0.0f;
                penY -= lineHeight * scale;
                continue;
            }

            auto git = glyphs.find(cp);
            if (git == glyphs.end()) continue;
            const GlyphInfo& g = git->second;

            float xpos = penX + g.bearing.x() * scale;
            float ypos = penY - (g.size.y() - g.bearing.y()) * scale;
            float w = g.size.x() * scale;
            float h = g.size.y() * scale;

            // Добавляем 2 треугольника (6 вершин) для текущего символа
            textData.PositionData.push_back({ xpos,     ypos + h, 0.0f });
            textData.PositionData.push_back({ xpos,     ypos,     0.0f });
            textData.PositionData.push_back({ xpos + w, ypos,     0.0f });
            textData.PositionData.push_back({ xpos,     ypos + h, 0.0f });
            textData.PositionData.push_back({ xpos + w, ypos,     0.0f });
            textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f });

            // TexCoords — на основе UV позиции и размера в атласе (uv указывает на верх-лево)
            float u0 = g.uv.x();
            float v0 = g.uv.y();
            float u1 = u0 + g.uvSize.x();
            float v1 = v0 + g.uvSize.y();

            textData.TexCoordData.push_back({ u0, v0 });
            textData.TexCoordData.push_back({ u0, v1 });
            textData.TexCoordData.push_back({ u1, v1 });
            textData.TexCoordData.push_back({ u0, v0 });
            textData.TexCoordData.push_back({ u1, v1 });
            textData.TexCoordData.push_back({ u1, v0 });

            penX += (g.advance >> 6) * scale;
            totalW = penX;
            maxH = max(maxH, h);
        }
        totalW = max(maxLineWidth, penX);

        // Сохраняем в кеш
        CachedText ct;
        ct.width = totalW;
        ct.height = maxH;
        ct.mesh.AssignFrom(textData);

        auto res = cache.emplace(key, std::move(ct));
        itCache = res.first;
    }

    // Используем кешированный меш
    CachedText& cached = itCache->second;

    // Вычисляем смещение для проекции (оставляем Y как есть)
    float tx = x;
    if (centered) {
        tx = x - cached.width * 0.5f;
    }
    float ty = y;

    // 3. Обновляем VBO через наш стандартный механизм
    // Примечание: для текста лучше использовать GL_DYNAMIC_DRAW, 
    // но RefreshVBO сейчас жестко зашит на GL_STATIC_DRAW. 
    // Для UI это обычно не критично, если строк не тысячи.

    // 4. Рендеринг
    r->shaderManager.PushShader(shaderName);

    // Матрица проекции — используем виртуальные проекционные размеры,
    // чтобы координаты текста были независимы от физического разрешения экрана.
    float W = Environment::projectionWidth;
    float H = Environment::projectionHeight;
    Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
    proj(0, 0) = 2.0f / W;
    proj(1, 1) = 2.0f / H;
    // Сдвигаем проекцию так, чтобы локальные координаты меша (pen-origin=0,0) оказались в (tx,ty)
    proj(0, 3) = -1.0f + 2.0f * (tx) / W;
    proj(1, 3) = -1.0f + 2.0f * (ty) / H;

    //cached.mesh.RefreshVBO();

    r->RenderUniformMatrix4fv("uProjection", false, proj.data());
    r->RenderUniform1i("uText", 0);
    r->RenderUniform4fv("uColor", color.data());

    glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, atlasTexture->getTexID());

    // The R8 atlas is sampled as alpha; without blending the non-glyph area
    // of every quad writes opaque black and hides whatever is behind it.
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	r->DrawVertexRenderStruct(cached.mesh);

    glDisable(GL_BLEND);

    r->shaderManager.PopShader();

    // Сброс бинда текстуры не обязателен, но можно для чистоты
    glBindTexture(GL_TEXTURE_2D, 0);
}
} // namespace ZL