#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_CastleTooltip = std::make_unique<gui::CastleTooltip>(m_Client->GetClient());
    m_Client->GetWorldClient().GetWorldNotifier().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;
        }
    }

    if (ImGui::IsMouseDoubleClicked(1)) RemoveTower();
}

void WorldRenderer::RemoveTower() {
    glm::vec2 cursorPos = GetCursorWorldPos();

    game::TowerPtr clickedTower = m_World->GetTower(cursorPos);

    if (clickedTower != nullptr) {
        m_Client->GetClient()->RemoveTower(clickedTower->GetID());
    }
}

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;

    if (m_MobTooltip->IsShown() || m_CastleTooltip->IsShown()) 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();
    RenderTooltips();
    RenderPopups();
    DetectClick();
}

WorldRenderer::~WorldRenderer() {

}

void WorldRenderer::RenderTooltips() const {
    RenderMobTooltip();
    RenderCastleTooltip();
}

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::RenderCastleTooltip() const {
    if (ImGui::IsAnyItemHovered()) return;

    DetectCastleHovering();
    m_CastleTooltip->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::DetectCastleHovering() const {
    glm::vec2 cursorWorldPos = GetCursorWorldPos();
    for (const game::Team& team : m_World->GetTeams()) {
        if (team.GetCastle().CollidesWith({ cursorWorldPos.x, cursorWorldPos.y })) {
            m_CastleTooltip->SetCastle(&team.GetCastle());
            return;
        }
    }
    m_CastleTooltip->SetCastle(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
