#include "Global.h"
#include "Input.h"
#include "Instance.h"
#include "Level.h"
#include "Log.h"
#include "Puzzle.h"
#include "SDL3/SDL_mouse.h"
#include "bgfx/bgfx.h"
#include "imgui.h"
#include "rendering/Rendering.h"
#include <SDL3/SDL.h>
#include <bx/math.h>
#include <cstdint>

namespace Game
{
    void EntityRenderData::Render(const Model* models, const Material* materials)
    {
        if (ModelHandle == UINT16_MAX || MaterialHandle == UINT16_MAX) return;
        if (!Visible) return;
        auto& rendering = GameRendering::Get();

        Transform.UpdateMatrix();
        bgfx::setTransform(Transform.M.M);

        const Model& currentModel = models[ModelHandle];
        const Material& currentMaterial = materials[MaterialHandle];
        bgfx::setVertexBuffer(0, currentModel.VertexBuffer);
        bgfx::setIndexBuffer(currentModel.IndexBuffer);
        bgfx::setState(currentMaterial.State);

        float timeValues[4]{0.0f};
        timeValues[0] = GetInstance().Time.Now;

        float texInfo[4]{0.0f};
        texInfo[0] = currentMaterial.Textures[0].Info.width;
        texInfo[1] = currentMaterial.Textures[0].Info.height;
        texInfo[2] = rendering.DitherTextures.DitherTexWH;
        texInfo[3] = rendering.DitherTextures.DitherTexDepth;

        bgfx::setTexture(0, currentMaterial.Textures[0].SamplerHandle, currentMaterial.Textures[0].Handle);
        bgfx::setTexture(1, rendering.DitherTextures.DitherSampler, rendering.DitherTextures.FinalTex);
        bgfx::setTexture(2, rendering.DitherTextures.RampSampler, rendering.DitherTextures.RampTex);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UTime], timeValues);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UDotColor], TestColor);
        bgfx::setUniform(currentMaterial.Uniforms[Material::UTexInfo], texInfo);

        bgfx::submit(currentMaterial.ViewID, currentMaterial.Shader);
    }

    namespace
    {
        void UpdatePlayerInputMode()
        {
            bool IsGaming = GetInstance().Player.InputM == InputMode::Game;
            SDL_SetWindowRelativeMouseMode(GetShared().Window.SDLWindow, IsGaming);
            auto& IO = ImGui::GetIO();
            IO.ConfigFlags = FlagBool(IO.ConfigFlags, ImGuiConfigFlags_NoMouse | ImGuiConfigFlags_NoKeyboard, IsGaming);
            GameRendering::Get().UIVisible = IsGaming ? UIVisibilityState::Game : UIVisibilityState::Debug;
        }
    } // namespace

    void Level::Setup(GameData& data)
    {
        LOG("Level setup");
        void* storagePtr = data.EntityStorage;
        bool needReset = false;
        needReset |= Cubes.Setup(storagePtr, needReset);
        needReset |= Tests.Setup(storagePtr, needReset);
        needReset |= PuzzleTiles.Setup(storagePtr, needReset);
        needReset |= UIQuads.Setup(storagePtr, needReset);

        Generated::Setup(PuzzleData);

        if (Cubes.Count == 0)
        {
            for (uint32_t yy = 0; yy < 11; ++yy)
            {
                for (uint32_t xx = 0; xx < 11; ++xx)
                {
                    Cube& c = Cubes.Get(Cubes.New());
                    c.TestX = xx;
                    c.TestY = yy;
                    c.Setup();
                }
            }
            Cubes.New(); // Floor
        }
        if (Tests.Count == 0)
        {
            Tests.Get(Tests.New()).Setup();
        }
        if (PuzzleTiles.Count == 0)
        {
            for (uint32_t puzI = 0; puzI < BX_COUNTOF(Puzzles); ++puzI)
            {
                Puzzles[puzI].Setup();
            }
        }

        UpdatePlayerInputMode();
    }

    void Level::Update()
    {
        START_PERF();
        PlayerData& player = GetInstance().Player;

        // Input
        float delta = GetInstance().Time.Delta;
        delta = 1.0f / 144.0f;
        constexpr float moveSpeed = 10.0f;
        constexpr float rotSpeed = 0.6f;

        float forwardInput = (GetKey(ScanCode::W) ? 1.0f : 0.0f) + (GetKey(ScanCode::S) ? -1.0f : 0.0f);
        float rightInput = (GetKey(ScanCode::D) ? 1.0f : 0.0f) + (GetKey(ScanCode::A) ? -1.0f : 0.0f);
        bx::Vec3 moveInput = bx::Vec3{rightInput, forwardInput, 0.0f};
        moveInput = bx::normalize(moveInput);
        bx::Vec3 inputVec = {moveInput.x * delta * moveSpeed, 0.0f, moveInput.y * delta * moveSpeed};
        Vec2 mouseMovement = GetMouseMovement();
        bx::Vec3 rotInput = {mouseMovement.y * delta * rotSpeed, mouseMovement.x * delta * rotSpeed, 0.0f};

        if (GetKeyPressedNow(ScanCode::F1))
        {
            player.CameraM = player.CameraM == CameraMode::Walk ? CameraMode::Freefly : CameraMode::Walk;
        }

        if (GetKeyPressedNow(ScanCode::F2))
        {
            if (player.InputM == InputMode::Game)
            {
                player.InputM = InputMode::UI;
            }
            else
            {
                player.InputM = InputMode::Game;
            }
            UpdatePlayerInputMode();
        }

        if (player.CameraM == CameraMode::Freefly)
        {
            if (GetMouseButton(MouseButton::Left))
            {
                player.FreeflyXRot += rotInput.x;
                player.FreeflyYRot += rotInput.y;
                bx::mtxRotateY(player.FreeflyCamTransform.Rotation.M, player.FreeflyYRot);
                player.FreeflyCamTransform.RotateLocal({player.FreeflyXRot, 0.0f, 0.0f});
            }

            player.FreeflyCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z});
            player.FreeflyCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f});
        }
        else if (player.CameraM == CameraMode::Walk)
        {
            player.PlayerCamTransform.TranslateLocal({0.0f, 0.0f, -inputVec.z});
            player.PlayerCamTransform.TranslateLocal({-inputVec.x, 0.0f, 0.0f});
            player.PlayerCamTransform.Position.y = -3.0f;

            player.WalkXRot += rotInput.x;
            player.WalkYRot += rotInput.y;
            bx::mtxRotateY(player.PlayerCamTransform.Rotation.M, player.WalkYRot);
            player.PlayerCamTransform.RotateLocal({player.WalkXRot, 0.0f, 0.0f});
        }

        // Cubes
        for (uint16_t i = 0; i < Cubes.Count; ++i)
        {
            Cubes.Get({i}).Update();
        }

        // Puzzle tiles
        for (int32_t i = 0; i < BX_COUNTOF(Puzzles); ++i)
        {
            Puzzles[i].Update();
        }

        END_PERF(GetShared().Window.PerfCounters, PerfCounterType::GameLevelUpdate, GetShared().Window.FrameCounter);
    }

    void Level::Render(uint16_t viewId, const Model* models, const Material* materials)
    {
        auto& shared = GetShared();

        float proj[16];
        bx::mtxProj(proj,
                    75.0f,
                    float(shared.Window.WindowWidth) / float(shared.Window.WindowHeight),
                    0.1f,
                    1000.0f,
                    bgfx::getCaps()->homogeneousDepth);

        auto& player = GetInstance().Player;
        if (player.CameraM == CameraMode::Freefly)
        {
            player.FreeflyCamTransform.UpdateMatrix();
            bgfx::setViewTransform(viewId, player.FreeflyCamTransform.M.M, proj);
        }
        else
        {
            player.PlayerCamTransform.UpdateMatrix();
            bgfx::setViewTransform(viewId, player.PlayerCamTransform.M.M, proj);
            bgfx::dbgTextPrintf(1, 0, 0b1100, "       ");
        }

        // Cubes.Render(models, materials);
        Tests.Render(models, materials);
        PuzzleTiles.Render(models, materials);
        UIQuads.Render(models, materials);
    }

    void Cube::Setup()
    {
        EData.MaterialHandle = 0;
        EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/cube.gltf");
    }

    void Cube::Update()
    {
        if (TestX >= 0 && TestY >= 0)
        {
            double globalTime = GetInstance().Time.Now;
            double time = TestY <= 5 ? globalTime * 1.0f : 0.0f;
            float scale = 1.0f + TestX * 0.4f;
            EData.Transform.Position = bx::Vec3{TestX * 2.0f, TestY * 2.0f, 0.0f};
            EData.Transform.Scale = {scale, scale, scale};
        }
        else
        {
            EData.Transform.Position = {0.0f, -1.0f, 0.0f};
            EData.Transform.Scale = {100.0f, 1.0f, 100.0f};
            EData.TestColor[0] = 0.3f;
            EData.TestColor[1] = 0.325f;
            EData.TestColor[2] = 0.3f;
        }
    }

    void TestEntity::Setup()
    {
        EData.MaterialHandle = 0;
        EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/zurg.gltf");

        EData.Transform.Position = {0.0f, 0.0f, 0.0f};
        EData.TestColor[0] = 0.0f;
    }

    void WorldPuzzle::Setup()
    {
        Data.PlacedCardCount = 16;
        for (int32_t i = 0; i < 16; ++i)
        {
            Data.PlacedCards[i].RefCard = {0};
            Data.PlacedCards[i].Position = {int8_t(i % 4), int8_t(i / 4)};
        }

        for (uint32_t cardI = 0; cardI < Data.PlacedCardCount; ++cardI)
        {
            const Generated::PlacedPuzzleCard& card = Data.PlacedCards[cardI];
            Level& level = GetInstance().GameLevel;
            TileHandles[cardI] = level.PuzzleTiles.New();
            UIPlacedCards[cardI] = level.UIQuads.New();

            bx::Vec3 Pos = {
                WorldPosition.x + card.Position.X * WorldCardSize.x,
                WorldPosition.y,
                WorldPosition.z + card.Position.Y * WorldCardSize.y,
            };
            PuzzleTileEntity& tile = level.PuzzleTiles.Get(TileHandles[cardI]);
            tile.EData.Transform.Position = Pos;
            tile.EData.MaterialHandle = 0;

            UIQuadEntity& quad = level.UIQuads.Get(UIPlacedCards[cardI]);
            quad.EData.MaterialHandle = 0;
            quad.EData.ModelHandle = GameRendering::Get().GetModelHandleFromPath("models/plane.glb");
        }
    }

    void WorldPuzzle::Update()
    {
        Transform& camTransform = GetInstance().Player.PlayerCamTransform;
        Vec3 cameraPos = camTransform.GetPosition() * -1;
        Level& level = GetInstance().GameLevel;

        for (int32_t cardI = 0; cardI < Data.PlacedCardCount; ++cardI)
        {
            Generated::PlacedPuzzleCard& card = Data.PlacedCards[cardI];
            if (!Generated::IsValid(card.RefCard)) continue;
            const Generated::StaticPuzzleCard& cData =
                Generated::GetCard(Generated::GetStaticPuzzleData(), card.RefCard);
            level.PuzzleTiles.Get(TileHandles[cardI]).EData.ModelHandle = cData.ModelHandle;

            auto& quad = level.UIQuads.Get(UIPlacedCards[cardI]);
            Vec3 fw = {0, -1, 0};
            // quad.EData.Transform.SetPosition(cameraPos + Vec3{0, -2, 0});
            quad.EData.Transform.SetPosition({});
            quad.EData.Transform.Rotation = camTransform.Rotation;
        }
    }
} // namespace Game
