#include "Instance.h"
#include "Mesh.h"
#include "Tools.h"

#include <imgui.h>
#include <tracy/Tracy.hpp>

namespace Tools
{
    const char* GetAssetPath(Generated::AssetHandle assetHandle)
    {
        const auto& inst = Game::GetInstance();
        for (int32_t j = 0; j < inst.DebugData.AssetCount; ++j)
        {
            if (inst.DebugData.AssetHandles[j] == assetHandle)
            {
                return inst.DebugData.AssetHandlePaths[j];
            }
        }
        return "---";
    }

    void ModelDropdown(Generated::ModelHandle& modelHandle)
    {
        auto& R = Game::GameRendering::Get();
        const char* name = GetAssetPath(modelHandle.Asset);
        if (ImGui::BeginCombo("Model", name))
        {
            for (int32_t i = 0; i < R.ModelCount; ++i)
            {
                if (ImGui::Selectable(GetAssetPath(R.Models[i].Handle.Asset), i == modelHandle.ModelIdx))
                {
                    modelHandle = R.Models[i].Handle;
                }
            }
            ImGui::EndCombo();
        }
    }

    void TextureDropdown(Generated::TextureHandle& texHandle)
    {
        auto& R = Game::GameRendering::Get();
        const char* name = GetAssetPath(texHandle.Asset);
        if (ImGui::BeginCombo("Texture", name))
        {
            for (int32_t i = 0; i < R.MaxTextures; ++i)
            {
                if (!IsValid(R.Textures[i].TexHandle)) continue;
                ImGui::PushID(i);
                ImVec2 pos = ImGui::GetCursorScreenPos();
                if (ImGui::Selectable("", i == texHandle.TextureIdx, ImGuiSelectableFlags_AllowOverlap, {0, 64}))
                {
                    texHandle = R.Textures[i].TexHandle;
                }
                ImGui::SetCursorScreenPos(pos);
                ImGui::Image(R.Textures[i].RenderHandle.idx, {64, 64});
                ImGui::SameLine();
                ImGui::Text("%s", GetAssetPath(R.Textures[i].TexHandle.Asset));
                ImGui::PopID();
            }
            ImGui::EndCombo();
        }
    }
    void RenderDebugUI(Game::GameRendering& rendering)
    {
        auto& time = Game::GetInstance().Time;
        if (rendering.UIVisible == Game::UIVisibilityState::Debug)
        {
            ZoneScopedN("DebugUI");

            auto& shared = Game::GetShared();
            auto& debug = Game::GetInstance().DebugData;
            auto& level = Game::GetInstance().GameLevel;

            if (ImGui::IsMouseClicked(ImGuiMouseButton_Right))
            {
                debug.DebugCardRotation++;
                if (debug.DebugCardRotation >= 4) debug.DebugCardRotation = 0;
            }
            if (ImGui::Begin("Log"))
            {
                ImGui::Checkbox("Shorten File Names", &debug.ShortenLogFileNames);
                ImGui::BeginTable(
                    "tbl", 4, ImGuiTableFlags_Resizable | ImGuiTableFlags_Hideable | ImGuiTableFlags_SizingFixedFit);
                ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_NoResize);
                ImGui::TableSetupColumn("Log");
                ImGui::TableSetupColumn("Line", ImGuiTableColumnFlags_NoResize);
                ImGui::TableSetupColumn("File", ImGuiTableColumnFlags_NoResize);
                ImGui::TableHeadersRow();
                for (int32_t i = 0; i < bx::min(100, LogInternal::LogHistorySize); ++i)
                {
                    int32_t idx = GetLogHistory().WriteIdx - i - 1;
                    if (idx < 0) idx += LogInternal::LogHistorySize;
                    const char* line = &GetLogHistory().LogBuffer[idx * LogInternal::MaxLineSize];
                    if (line[0] != 0)
                    {
                        int64_t timeOffset = GetLogHistory().WriteTime[idx] - time.StartTime;
                        double writeTime = (double)timeOffset / bx::getHPFrequency();
                        uint32_t fileLine = GetLogHistory().LineBuffer[idx];
                        const char* filePath = &GetLogHistory().FileBuffer[idx * LogInternal::MaxLineSize];
                        const char* filePathRes =
                            debug.ShortenLogFileNames ? bx::FilePath{filePath}.getFileName().getPtr() : filePath;

                        ImGui::TableNextRow();
                        ImGui::TableNextColumn();
                        ImGui::Text("%.01f", writeTime);

                        ImGui::TableNextColumn();
                        ImGui::Text("%s", line);
                        ImGui::SetItemTooltip("%f\n%s%s:%u", writeTime, line, filePath, fileLine);

                        ImGui::TableNextColumn();
                        ImGui::Text("%u", fileLine);

                        ImGui::TableNextColumn();
                        ImGui::Text("%s", filePathRes);
                    }
                }
                ImGui::EndTable();
            }
            ImGui::End();
            if (ImGui::Begin("Rendering"))
            {
                if (rendering.LastShaderLoadTime >= 0.0f)
                {
                    ImGui::TextColored({0.2f, 0.9f, 0.2f, 1.0f},
                                       "Shader loaded %.0f seconds ago",
                                       time.Now - rendering.LastShaderLoadTime);
                }
                else
                {
                    ImGui::TextColored({0.9f, 0.2f, 0.2f, 1.0f}, "Shader load Failiure!");
                }
                if (ImGui::Button("Reload Meshes"))
                {
                    LoadModels(rendering.Models, rendering.ModelCount);
                }
                ImGui::SameLine();
                if (ImGui::Button("Reload Level"))
                {
                    level = {};
                    level.Setup(shared.Game);
                }
                ImGui::Checkbox("Show ImGui Demo", &debug.ShowImguiDemo);
                if (debug.ShowImguiDemo) ImGui::ShowDemoWindow(&debug.ShowImguiDemo);
                ImGui::Separator();

                ImGui::Checkbox("Cubes", &level.Cubes.IsEnabled);
                ImGui::Checkbox("Tests", &level.Tests.IsEnabled);
                ImGui::Checkbox("PuzzleTiles", &level.PuzzleTiles.IsEnabled);
                ImGui::Checkbox("UIQuads", &level.UIQuads.IsEnabled);

                if (ImGui::Button("Dithergen"))
                {
                    DitherGen(rendering.DitherTextures, rendering.DitherRecursion);
                }
                ImGui::SameLine();
                ImGui::SliderInt("Recursion", &rendering.DitherRecursion, 1, 4);
                ImGui::Text("%ux%ux%u",
                            rendering.DitherTextures.DitherTexWH,
                            rendering.DitherTextures.DitherTexWH,
                            rendering.DitherTextures.DitherTexDepth);
                if (!isValid(rendering.DitherTextures.PreviewTex))
                {
                    ImGui::Text("Invalid Texture");
                }
                else
                {
                    ImGui::Image(
                        rendering.DitherTextures.PreviewTex.idx,
                        {(float)rendering.DitherTextures.DitherTexWH,
                         (float)rendering.DitherTextures.DitherTexWH * rendering.DitherTextures.DitherTexDepth});
                }
                if (isValid(rendering.DitherTextures.RampTex))
                {
                    ImGui::Image(rendering.DitherTextures.RampTex.idx,
                                 {BX_COUNTOF(rendering.DitherTextures.BrightnessRamp), 8});
                }
                Vec3 quadPos = level.UIQuads.Get({0}).EData.Transform.GetPosition();
                ImGui::Text("%f %f %f", quadPos.x, quadPos.y, quadPos.z);

                if (ImGui::ColorEdit3("Base Color", &rendering.DefaultBaseColor.x))
                {
                    auto& tiles = level.PuzzleTiles;
                    for (int32_t i = 0; i < tiles.Count; ++i)
                    {
                        tiles.Data[i].EData.BaseColor = rendering.DefaultBaseColor;
                    }
                }
                if (ImGui::ColorEdit3("Dot Color", &rendering.DefaultTileColor.x))
                {
                    auto& tiles = level.PuzzleTiles;
                    for (int32_t i = 0; i < tiles.Count; ++i)
                    {
                        tiles.Data[i].EData.TestColor = rendering.DefaultTileColor;
                    }
                }

                ImGui::Text("Shader log:");
                ImGui::TextWrapped("%s", Game::GetShared().Dev.ShaderLog);
            }
            ImGui::End();
            if (ImGui::Begin("Textures"))
            {
                if (ImGui::Button("Reload"))
                {
                    rendering.LoadTextures();
                }
                for (int32_t i = 0; i < rendering.MaxTextures; ++i)
                {
                    if (!isValid(rendering.Textures[i].RenderHandle)) continue;
                    ImGui::Text("%i", i);
                    float width = bx::min<float>(ImGui::GetContentRegionAvail().x, rendering.Textures[i].Info.width);
                    float height = bx::min<float>(ImGui::GetContentRegionAvail().x, rendering.Textures[i].Info.height);
                    ImGui::Image(rendering.Textures[i].RenderHandle.idx, {width, height});
                }
            }
            ImGui::End();
            if (ImGui::Begin("Puzzles"))
            {
                if (ImGui::Button("Add"))
                {
                    bool found = false;
                    for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i)
                    {
                        auto& puz = level.Puzzles[i].Data;
                        if (puz.ID == UINT16_MAX)
                        {
                            bx::strCopy(puz.PuzzleName, sizeof(puz.PuzzleName), "Unnamed Puzzle");
                            puz.ID = i;
                            found = true;
                            break;
                        }
                    }
                    if (!found)
                    {
                        LOG_ERROR("Too many puzzles!");
                    }
                }
                ImGui::Separator();
                for (int32_t i = 0; i < BX_COUNTOF(level.Puzzles); ++i)
                {
                    auto& puzzleData = level.Puzzles[i].Data;
                    if (puzzleData.ID == UINT16_MAX) continue;

                    bool isSelected = debug.SelectedDebugLevel == i;
                    ImGui::PushID("selectable");
                    if (ImGui::Selectable(puzzleData.PuzzleName, isSelected))
                    {
                        debug.SelectedDebugLevel = isSelected ? UINT16_MAX : i;
                    }
                    ImGui::PopID();
                }
            }
            ImGui::End();
            if (debug.SelectedDebugLevel < BX_COUNTOF(level.Puzzles))
            {
                if (!Puzzle::RenderDebugUI(level.Puzzles[debug.SelectedDebugLevel].Data))
                {
                    debug.SelectedDebugLevel = UINT16_MAX;
                }
            }
            if (ImGui::Begin("Cards"))
            {
                Generated::StaticPuzzleData& staticData = Puzzle::GetStaticPuzzleData();
                if (ImGui::Button("Save"))
                {
                    Puzzle::SaveStaticPuzzleData();
                }
                ImGui::SameLine();
                if (ImGui::Button("Reload"))
                {
                    Puzzle::LoadStaticPuzzleData();
                }

                for (int32_t i = 0; i < BX_COUNTOF(staticData.Cards); ++i)
                {
                    ImGui::Separator();

                    Generated::StaticPuzzleCard& card = staticData.Cards[i];
                    ImGui::PushID(i);
                    char cardName[64]{0};
                    bx::snprintf(cardName, sizeof(cardName), "%i", i);
                    ImGui::Selectable(cardName);
                    if (ImGui::BeginDragDropSource())
                    {
                        Puzzle::DrawCard(card, debug.DebugCardRotation, ImGui::GetCursorScreenPos());
                        ImGui::SetDragDropPayload("cardtype", &i, sizeof(i));
                        ImGui::EndDragDropSource();
                    }

                    Tools::ModelDropdown(card.ModelHandle);
                    Tools::TextureDropdown(card.BoardTextureHandle);
                    for (int8_t y = 0; y < Puzzle::Config::CardSize; ++y)
                    {
                        ImGui::PushID(y);
                        for (int8_t x = 0; x < Puzzle::Config::CardSize; ++x)
                        {
                            if (x > 0) ImGui::SameLine();
                            ImGui::PushID(x);
                            auto& node = Puzzle::EditCardNodeAt(card, 0, x, y);
                            if (ImGui::Button(Generated::PuzzleElementType::ShortName[node], {26, 24}))
                            {
                                int32_t newVal = int32_t(node) + 1;
                                if (newVal >= Generated::PuzzleElementType::EntryCount)
                                {
                                    newVal = 0;
                                }
                                node = Generated::PuzzleElementType::Enum(newVal);
                            }
                            ImGui::PopID();
                        }
                        ImGui::PopID();
                    }
                    ImGui::PopID();
                }
            }
            ImGui::End();
        }
        ImGui::SetNextWindowPos({0, 0});
        if (ImGui::Begin("Stats",
                         nullptr,
                         ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs |
                             ImGuiWindowFlags_AlwaysAutoResize))
        {
            if (Game::GetInstance().Player.CameraM == Game::CameraMode::Freefly)
            {
                ImGui::PushStyleColor(ImGuiCol_Text, {0.8f, 0.1f, 0.1f, 1.0f});
                ImGui::Text("NOCLIP");
                ImGui::PopStyleColor();
            }
            ImGui::Text("Delta: %.01fms", time.Delta * 1000);
            ImGui::Text("FPS: %.0f", 1.0 / time.Delta);

            constexpr ImVec2 FpsPlotSize{200, 60};
            if (ImGui::BeginChild("FpsPlot", FpsPlotSize))
            {
                auto& drawList = *ImGui::GetWindowDrawList();
                ImVec2 pos = ImGui::GetWindowPos();
                drawList.AddRectFilled(pos, {pos.x + FpsPlotSize.x, pos.y + FpsPlotSize.y}, 0xFFFFFF33);
            }
            ImGui::EndChild();
        }
        ImGui::End();
    }
} // namespace Tools
