#include "dialogue/DialogueOverlay.h"
#include "dialogue/DialogueTypes.h"

#include "GameConstants.h"
#include "Environment.h"
#include <algorithm>

namespace ZL::Dialogue {

void DialogueOverlay::TexturedQuad::rebuild(const UiRect& newRect) {
	rect = newRect;
	mesh.data = CreateRect2D(
		{ rect.x + rect.w * 0.5f, rect.y + rect.h * 0.5f },
		{ rect.w * 0.5f, rect.h * 0.5f },
		0.0f
	);
	mesh.RefreshVBO();
	initialized = true;
}

bool DialogueOverlay::init(Renderer& renderer, const std::string& zipFile) {
    rendererRef = &renderer;
    zipFilename = zipFile;

    textboxTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/textbox_bg.png", zipFile));
    portraitFrameTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/portrait_frame.png", zipFile));
    choiceMainTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/choice_main.png", zipFile));
    choiceOptionalTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/choice_optional.png", zipFile));
    choiceSelectedTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/choice_selected.png", zipFile));
    cutsceneSubtitleTexture = std::make_shared<Texture>(CreateTextureDataFromPng("resources/dialogue/cutscene_subtitle_bg.png", zipFile));

    nameRenderer = std::make_unique<TextRenderer>();
    bodyRenderer = std::make_unique<TextRenderer>();
    choiceRenderer = std::make_unique<TextRenderer>();
    cutsceneRenderer = std::make_unique<TextRenderer>();

    const bool ok =
        nameRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 28, zipFile) &&
        bodyRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 24, zipFile) &&
        choiceRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 22, zipFile) &&
        cutsceneRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 24, zipFile);

    return ok;
}

void DialogueOverlay::draw(Renderer& renderer, const PresentationModel& model) {
    if (model.mode == PresentationMode::Hidden) {
        lastChoiceRects.clear();
        return;
    }

    if (model.mode == PresentationMode::Cutscene) {
        drawCutscene(renderer, model);
    }
    else {
        drawDialogue(renderer, model);
    }
}

void DialogueOverlay::drawDialogue(Renderer& renderer, const PresentationModel& model) {
    const float W = Environment::projectionWidth;
    const float H = Environment::projectionHeight;

    const UiRect portraitRect{ 24.0f, 24.0f, 182.0f, 182.0f };
    const UiRect textboxRect{ 220.0f, 24.0f, max(200.0f, W - 244.0f), 182.0f };

    if (!portraitQuad.initialized || portraitQuad.rect.w != portraitRect.w || portraitQuad.rect.h != portraitRect.h ||
        portraitQuad.rect.x != portraitRect.x || portraitQuad.rect.y != portraitRect.y) {
        portraitQuad.rebuild(portraitRect);
    }
    if (!textboxQuad.initialized || textboxQuad.rect.w != textboxRect.w || textboxQuad.rect.h != textboxRect.h ||
        textboxQuad.rect.x != textboxRect.x || textboxQuad.rect.y != textboxRect.y) {
        textboxQuad.rebuild(textboxRect);
    }

    glEnable(GL_BLEND);
    renderer.shaderManager.PushShader(defaultShaderName);
    renderer.RenderUniform1i(textureUniformName, 0);
    renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
    renderer.PushMatrix();
    renderer.LoadIdentity();

    drawQuad(renderer, textboxQuad, textboxTexture);
    drawQuad(renderer, portraitQuad, model.portraitPath.empty() ? portraitFrameTexture : loadTextureCached(model.portraitPath));
    drawQuad(renderer, portraitQuad, portraitFrameTexture);

    renderer.PopMatrix();
    renderer.PopProjectionMatrix();
    renderer.shaderManager.PopShader();

    const float nameX = textboxRect.x + 24.0f;
    const float nameY = textboxRect.y + textboxRect.h - 38.0f;
    const float bodyX = textboxRect.x + 24.0f;
    const float bodyY = textboxRect.y + textboxRect.h - 78.0f;

    if (!model.speaker.empty()) {
        nameRenderer->drawText(model.speaker, nameX, nameY, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
    }

    const std::string wrappedBody = wrapText(model.visibleText, 56);
    bodyRenderer->drawText(wrappedBody, bodyX, bodyY, 1.0f, false, { 1.0f, 1.0f, 1.0f, 1.0f });

    lastChoiceRects.clear();
    if (model.mode == PresentationMode::Choice) {
        const float choiceStartY = textboxRect.y + 56.0f;
        const float choiceHeight = 30.0f;
        const float choiceSpacing = 8.0f;
        const float choiceWidth = textboxRect.w - 48.0f;

        if (choiceQuads.size() < model.choices.size()) {
            choiceQuads.resize(model.choices.size());
        }

        renderer.shaderManager.PushShader(defaultShaderName);
        renderer.RenderUniform1i(textureUniformName, 0);
        renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
        renderer.PushMatrix();
        renderer.LoadIdentity();

        for (size_t i = 0; i < model.choices.size(); ++i) {
            const float y = choiceStartY + (choiceHeight + choiceSpacing) * static_cast<float>(model.choices.size() - 1 - i);
            UiRect rect{ textboxRect.x + 20.0f, y, choiceWidth, choiceHeight };
            lastChoiceRects.push_back(rect);
            choiceQuads[i].rebuild(rect);

            const bool isSelected = static_cast<int>(i) == model.selectedChoice;
            std::shared_ptr<Texture> choiceTexture = choiceMainTexture;
            if (model.choices[i].kind == ChoiceKind::Optional) {
                choiceTexture = choiceOptionalTexture;
            }
            if (isSelected) {
                choiceTexture = choiceSelectedTexture;
            }
            drawQuad(renderer, choiceQuads[i], choiceTexture);
        }

        renderer.PopMatrix();
        renderer.PopProjectionMatrix();
        renderer.shaderManager.PopShader();

        for (size_t i = 0; i < model.choices.size(); ++i) {
            const UiRect& rect = lastChoiceRects[i];
            const bool isSelected = static_cast<int>(i) == model.selectedChoice;
            const std::array<float, 4> color = (model.choices[i].kind == ChoiceKind::Optional)
                ? std::array<float, 4>{0.82f, 0.82f, 0.82f, 1.0f}
            : std::array<float, 4>{ 1.0f, 0.93f, 0.65f, 1.0f };

            choiceRenderer->drawText(
                wrapText(model.choices[i].text, 52),
                rect.x + 14.0f,
                rect.y + 9.0f,
                1.0f,
                false,
                isSelected ? std::array<float, 4>{1.0f, 1.0f, 1.0f, 1.0f} : color
            );
        }
    }

    glDisable(GL_BLEND);
}

void DialogueOverlay::drawCutscene(Renderer& renderer, const PresentationModel& model) {
    const float W = Environment::projectionWidth;
    const float H = Environment::projectionHeight;
    const UiRect fullscreenRect{ 0.0f, 0.0f, W, H };
    const UiRect subtitleRect{ W * 0.12f, 22.0f, W * 0.76f, 110.0f };

    backgroundQuad.rebuild(fullscreenRect);
    subtitleQuad.rebuild(subtitleRect);

    glEnable(GL_BLEND);
    renderer.shaderManager.PushShader(defaultShaderName);
    renderer.RenderUniform1i(textureUniformName, 0);
    renderer.PushProjectionMatrix(0.0f, W, 0.0f, H, -10.0f, 10.0f);
    renderer.PushMatrix();
    renderer.LoadIdentity();

    if (!model.backgroundPath.empty()) {
        drawQuad(renderer, backgroundQuad, loadTextureCached(model.backgroundPath));
    }
    drawQuad(renderer, subtitleQuad, cutsceneSubtitleTexture);

    renderer.PopMatrix();
    renderer.PopProjectionMatrix();
    renderer.shaderManager.PopShader();

    if (!model.speaker.empty()) {
        nameRenderer->drawText(model.speaker, subtitleRect.x + 24.0f, subtitleRect.y + subtitleRect.h - 32.0f, 1.0f, false, { 1.0f, 0.88f, 0.45f, 1.0f });
    }
    cutsceneRenderer->drawText(
        wrapText(model.visibleText, 62),
        subtitleRect.x + 24.0f,
        subtitleRect.y + 30.0f,
        1.0f,
        false,
        { 1.0f, 1.0f, 1.0f, 1.0f }
    );

    glDisable(GL_BLEND);
}

bool DialogueOverlay::handlePointerReleased(float x, float y, const PresentationModel& model, int& outChoiceIndex) const {
    outChoiceIndex = -1;
    if (model.mode != PresentationMode::Choice) {
        return false;
    }

    for (size_t i = 0; i < lastChoiceRects.size(); ++i) {
        if (lastChoiceRects[i].contains(x, y)) {
            outChoiceIndex = static_cast<int>(i);
            return true;
        }
    }
    return false;
}

std::shared_ptr<Texture> DialogueOverlay::loadTextureCached(const std::string& path) {
    if (path.empty()) {
        return nullptr;
    }

    auto it = textureCache.find(path);
    if (it != textureCache.end()) {
        return it->second;
    }

    auto texture = std::make_shared<Texture>(CreateTextureDataFromPng(path, zipFilename));
    textureCache[path] = texture;
    return texture;
}

void DialogueOverlay::drawQuad(Renderer& renderer, const TexturedQuad& quad, const std::shared_ptr<Texture>& texture) const {
    if (!texture) {
        return;
    }

    glBindTexture(GL_TEXTURE_2D, texture->getTexID());
    renderer.DrawVertexRenderStruct(quad.mesh);
}

std::string DialogueOverlay::wrapText(const std::string& input, size_t maxLineLength) {
    if (input.size() <= maxLineLength) {
        return input;
    }

    std::string output;
    size_t lineStart = 0;
    while (lineStart < input.size()) {
        size_t lineEnd = min(lineStart + maxLineLength, input.size());
        if (lineEnd < input.size()) {
            const size_t split = input.rfind(' ', lineEnd);
            if (split != std::string::npos && split > lineStart) {
                lineEnd = split;
            }
        }

        output.append(input.substr(lineStart, lineEnd - lineStart));
        if (lineEnd < input.size()) {
            output.push_back('\n');
            lineStart = lineEnd + (input[lineEnd] == ' ' ? 1 : 0);
        }
        else {
            lineStart = lineEnd;
        }
    }

    return output;
}

} // namespace ZL::Dialogue