#include "render/WorldRenderer.h"
#include "render/loader/WorldLoader.h"
#include "render/Renderer.h"
#include "render/gui/imgui/imgui.h"
#include "gui/imgui/imgui_internal.h"
#include "window/Display.h"
#include "game/client/ClientGame.h"
#include "game/client/Client.h"

#include <iostream>

namespace td {
namespace render {

ImVec4 WorldRenderer::getImGuiTeamColor(game::TeamColor color) {
    switch (color) {
    case td::game::TeamColor::None:
        break;
    case td::game::TeamColor::Red:
        return ImVec4(1, 0, 0, 1);
    case td::game::TeamColor::Blue:
        return ImVec4(0, 0, 1, 1);
    }
    return ImVec4(1, 1, 1, 1);
}

void WorldRenderer::loadModels() {
    std::cout << "World Created !\n";
    m_WorldVao = std::make_unique<GL::VertexArray>(std::move(WorldLoader::loadWorldModel(m_World)));
    m_MobVao = std::make_unique<GL::VertexArray>(std::move(WorldLoader::loadMobModel()));
    m_SelectTileVao = std::make_unique<GL::VertexArray>(std::move(WorldLoader::loadTileSelectModel()));
    std::cout << "Vertex Count : " << m_WorldVao->getVertexCount() << std::endl;
}

WorldRenderer::WorldRenderer(game::World* world, client::ClientGame* client) : m_Client(client), m_Renderer(m_Client->getRenderer()), m_World(world), m_Zoom(0.1) {
    m_Renderer->setZoom(m_Zoom);
    m_Renderer->setCamMovement({});
    m_TowerPlacePopup = std::make_unique<gui::TowerPlacePopup>(m_Client->getClient());
    m_MobTooltip = std::make_unique<gui::MobTooltip>(m_Client->getClient());
    m_Client->getWorld().bindListener(this);
}

void WorldRenderer::updateCursorPos() {
    m_CursorPos = getCursorWorldPos();
}

void WorldRenderer::update() {
    if (m_WorldVao == nullptr)
        return;
    ImGuiIO& io = ImGui::GetIO();
    if (io.MouseDown[0] && !ImGui::IsAnyItemActive()) {
        ImVec2 mouseDelta = ImGui::GetIO().MouseDelta;
        const float relativeX = mouseDelta.x / (float)Display::getWindowWidth() * 2;
        const float relativeY = mouseDelta.y / (float)Display::getWindowHeight() * 2;
        moveCam(relativeX, relativeY, Display::getAspectRatio());
    }
    if (io.MouseWheel != 0) {
        changeZoom(io.MouseWheel);
    }
    updateCursorPos();
    if (ImGui::IsMouseClicked(0)) {
        if (!m_PopupOpened) {
            m_HoldCursorPos = { io.MousePos.x, io.MousePos.y };
        } else {
            m_PopupOpened = false;
        }
    }
}

void WorldRenderer::renderWorld() const {
    m_Renderer->renderVAO(*m_WorldVao);
}

void WorldRenderer::renderMobs() const {
    for (game::MobPtr mob : m_World->getMobList()) {
        Renderer::Model model;
        model.vao = m_MobVao.get();
        model.positon = { mob->getCenterX(), mob->getCenterY() };
        m_Renderer->renderModel(model);
    }
}

void WorldRenderer::renderTowers() const {
    if (!m_TowersCache.isEmpty())
        m_Renderer->renderVAO(m_TowersCache.getVertexArray());
}

void WorldRenderer::renderTileSelect() const {
    if (ImGui::IsAnyItemHovered()) return;

    Renderer::Model tileSelectModel;
    tileSelectModel.vao = m_SelectTileVao.get();
    tileSelectModel.positon = { (int)m_CursorPos.x, (int)m_CursorPos.y };

    m_Renderer->renderModel(tileSelectModel);
}

void WorldRenderer::renderPopups() {
    m_TowerPlacePopup->render();
    renderTowerUpgradePopup();
}

void WorldRenderer::render() {
    if (m_WorldVao == nullptr)
        return;
    renderWorld();
    renderMobs();
    renderTowers();
    renderTileSelect();
    renderMobTooltip();
    renderPopups();
    detectClick();
}

WorldRenderer::~WorldRenderer() {

}

void WorldRenderer::moveCam(float relativeX, float relativeY, float aspectRatio) {
    if (m_WorldVao == nullptr)
        return;
    float movementX = -relativeX / m_Zoom * aspectRatio;
    float movementY = relativeY / m_Zoom;
    m_Renderer->setCamMovement({ movementX, movementY });
}

void WorldRenderer::changeZoom(float zoomStep) {
    if (m_WorldVao == nullptr)
        return;
    static float sensibility = 1.5f;
    if (zoomStep < 0) {
        m_Zoom /= -zoomStep * sensibility;
    } else {
        m_Zoom *= zoomStep * sensibility;
    }
    m_Renderer->setZoom(m_Zoom);
    m_Renderer->setCamMovement({});
}

void WorldRenderer::click() {
    const game::TowerPtr tower = m_Client->getWorld().getTower(getClickWorldPos());
    m_TowerPlacePopup->setClickPos(getClickWorldPos());
    if (tower != nullptr) { // there is a tower here
        ImGui::OpenPopup("TowerUpgrade");
    } else if (m_Client->getWorld().CanPlaceLittleTower(getClickWorldPos(), m_Client->getPlayer()->getID())) {
        ImGui::OpenPopup("TowerPlace");
    }
}

void WorldRenderer::setCamPos(float camX, float camY) {
    m_CamPos = { camX, camY };
    m_Renderer->setCamPos(m_CamPos);
}

void WorldRenderer::renderTowerUpgradePopup() {
    if (ImGui::BeginPopup("TowerUpgrade")) {
        m_PopupOpened = true;
        game::TowerPtr tower = m_Client->getWorld().getTower(getClickWorldPos());
        if (tower == nullptr) {
            ImGui::EndPopup();
            return;
        }
        ImGui::Text("Tower : %s", game::TowerFactory::getTowerName(tower->getType()).c_str());

        for (int y = 0; y < 3; y++) { // path: 0 -> top  1 -> middle  2 -> bottom
            for (int x = 0; x < 4; x++) { // level: 1, 2, 3, 4

                if (x > 0)
                    ImGui::SameLine();

                std::uint8_t currentLevel = x + 1;
                game::TowerPath currentPath = game::TowerPath(y);

                const game::TowerStats* towerStats = game::getTowerStats(tower->getType(), { currentLevel, currentPath });
                game::TowerPath towerPath = tower->getLevel().getPath();

                bool disabled = towerStats == nullptr;

                int towerLevel = tower->getLevel().getLevel();

                bool alreadyUpgraded = currentLevel <= towerLevel;
                bool canUpgrade = (towerLevel + 1) == currentLevel;

                if (canUpgrade && towerPath != game::TowerPath::Base) {
                    if (currentPath != towerPath) {
                        canUpgrade = false;
                    }
                }

                ImGui::PushID(x * 4 + y);
                if (disabled) {
                    ImGui::BeginDisabled();
                    ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
                    ImGui::Button("", ImVec2(100, 100));
                    ImGui::PopStyleColor();
                    ImGui::EndDisabled();
                } else if (alreadyUpgraded) {
                    ImGui::BeginDisabled();
                    ImGui::Button("Already", ImVec2(100, 100));
                    ImGui::EndDisabled();
                } else if (canUpgrade) {
                    if (ImGui::Button("Upgrade", ImVec2(100, 100))) {
                        m_Client->getClient()->upgradeTower(tower->getID(), { currentLevel, currentPath });
                    }
                } else {
                    ImGui::BeginDisabled();
                    ImGui::Button("Locked", ImVec2(100, 100));
                    ImGui::EndDisabled();
                }
                ImGui::PopID();
            }
        }

        ImGui::EndPopup();
    }
}

void WorldRenderer::detectClick() {
    ImGuiIO& io = ImGui::GetIO();
    if (ImGui::IsMouseReleased(0) && !ImGui::IsAnyItemHovered() && !ImGui::IsAnyItemFocused()) {
        glm::vec2 cursorPos = { io.MousePos.x, io.MousePos.y };
        if (cursorPos == m_HoldCursorPos) {
            m_LastClicked = m_HoldCursorPos;
            click();
        }
    }
}

void WorldRenderer::renderMobTooltip() const {
    if (ImGui::IsAnyItemHovered()) return;

    detectMobHovering();
    m_MobTooltip->render();
}

void WorldRenderer::detectMobHovering() const {
    glm::vec2 cursorWorldPos = getCursorWorldPos();
    for (game::MobPtr mob : m_World->getMobList()) {
        if (mob->collidesWith({ cursorWorldPos.x, cursorWorldPos.y })) {
            m_MobTooltip->setMob(mob.get());
            return;
        }
    }
    m_MobTooltip->setMob(nullptr);
}

void WorldRenderer::OnTowerAdd(game::TowerPtr tower) {
    WorldLoader::RenderData renderData = WorldLoader::loadTowerModel(tower);
    m_TowersCache.addData(tower->getID(), renderData.positions, renderData.colors);
    m_TowersCache.updateVertexArray();
}

void WorldRenderer::OnTowerRemove(game::TowerPtr tower) {
    m_TowersCache.removeData(tower->getID());
    m_TowersCache.updateVertexArray();
}

glm::vec2 WorldRenderer::getCursorWorldPos() const {
    ImGuiIO& io = ImGui::GetIO();
    return m_Renderer->getCursorWorldPos({ io.MousePos.x, io.MousePos.y }, Display::getAspectRatio(), m_Zoom, Display::getWindowWidth(), Display::getWindowHeight());
}

glm::vec2 WorldRenderer::getClickWorldPos() const {
    return m_Renderer->getCursorWorldPos(m_LastClicked, Display::getAspectRatio(), m_Zoom, Display::getWindowWidth(), Display::getWindowHeight());
}

} // namespace render
} // namespace td
