#include "Log.h"
#include "Puzzle.h"
#include "bx/string.h"
#include "imgui.h"
#include "rendering/Rendering.h"

#include "bx/bx.h"
#include <cassert>

namespace
{
    static constexpr Generated::PuzPos Dirs[4]{
        {-1, 0},
        {0, -1},
        {0, 1},
        {1, 0},
    };

    Generated::StaticPuzzleData* StaticDataInstance = nullptr;
} // namespace

namespace Generated
{
    void Setup(StaticPuzzleData& data)
    {
        StaticDataInstance = &data;
        LOG("Setting up static puzzle data");
        for (int32_t i = 0; i < BX_COUNTOF(data.Cards); ++i)
        {
            data.Cards[i].ModelHandle = Game::GameRendering::Get().GetModelHandleFromPath("models/w straight.glb");
        }
    }

    StaticPuzzleData& GetStaticPuzzleData()
    {
        assert(StaticDataInstance != nullptr);
        return *StaticDataInstance;
    }
    const StaticPuzzleCard& GetCard(const StaticPuzzleData& data, StaticPuzzleCardHandle H)
    {
        // assert(IsValid(H));
        return data.Cards[H.Idx];
    }

    bool HasElement(const PuzzleNode& node, PuzzleElementType::Enum search)
    {
        for (int32_t i = 0; i < Puzzle::Config::MaxElementsPerTile; ++i)
        {
            if (node.PlacedTypes[i] == search) return true;
        }
        return false;
    }

    bool IsEmpty(const PuzzleNode& node)
    {
        for (int32_t i = 0; i < Puzzle::Config::MaxElementsPerTile; ++i)
        {
            if (node.PlacedTypes[i] != PuzzleElementType::None) return false;
        }
        return true;
    }

    bool IsValid(StaticPuzzleCardHandle h)
    {
        return h.Idx != UINT16_MAX;
    }

    uint8_t GetRemainingCount(const PuzzleCardStack& stack)
    {
        assert(stack.MaxAvailableCount >= stack.UsedCount);
        return stack.MaxAvailableCount - stack.UsedCount;
    }

    const PuzzleNode& GetNodeAt(const PuzzleData& puz, PuzPos pos)
    {
        assert(pos.X < Puzzle::Config::MaxPuzzleSizeCards && pos.Y < Puzzle::Config::MaxPuzzleSizeCards && pos.X >= 0 &&
               pos.Y >= 0);
        return puz.PlacedNodes[pos.Y * Puzzle::Config::MaxPuzzleSizeCards + pos.X];
    }

    PuzzleElementType::Enum GetElementAt(const PuzzleData& puz, ElemPos pos)
    {
        assert(pos.ElemIdx < Puzzle::Config::MaxElementsPerTile);
        const PuzzleNode& node = GetNodeAt(puz, pos.Position);
        return node.PlacedTypes[pos.ElemIdx];
    }

    bool PuzzleSolver::IsPuzzleSolved(const PuzzleData& puzzle)
    {
        bool IsSolved = true;
        for (uint32_t i = 0; i < puzzle.GoalPositionCount; ++i)
        {
            if (!IsExitSatisfied(puzzle, puzzle.GoalPositions[i]))
            {
                IsSolved = false;
                break;
            }
        }
        return IsSolved;
    }

    bool PuzzleSolver::IsExitSatisfied(const PuzzleData& puzzle, ElemPos pos)
    {
        const PuzzleNode& goalNode = GetNodeAt(puzzle, pos.Position);
        PuzzleElementType::Enum goalType = GetElementAt(puzzle, pos);

        uint32_t currentPositionQueueIdx = 0;
        uint32_t positionQueueCount = 0;
        PuzPos positionQueue[Puzzle::Config::MaxTilesInPuzzle];

        positionQueue[0] = pos.Position;
        positionQueueCount++;
        while (positionQueueCount > 0)
        {
            assert(currentPositionQueueIdx < Puzzle::Config::MaxTilesInPuzzle);
            PuzPos currentPos = positionQueue[currentPositionQueueIdx];
            for (PuzPos dir : Dirs)
            {
                PuzPos nextPos = currentPos + dir;
                if (IsValidGoalConnection(puzzle, nextPos, currentPos, goalType))
                {
                    const PuzzleNode& node = GetNodeAt(puzzle, nextPos);
                    for (PuzzleElementType::Enum e : node.PlacedTypes)
                    {
                        if (IsValidSource(e, goalType))
                        {
                            return true;
                        }
                    }
                    positionQueue[positionQueueCount] = nextPos;
                    positionQueueCount++;
                }
            }
            currentPositionQueueIdx++;
        }
        return false;
    }

    bool PuzzleSolver::IsValidGoalConnection(const PuzzleData& puzzle,
                                             PuzPos flowFrom,
                                             PuzPos flowTo,
                                             PuzzleElementType::Enum goalType)
    {
        const PuzzleNode& from = GetNodeAt(puzzle, flowFrom);
        const PuzzleNode& to = GetNodeAt(puzzle, flowTo);

        if (goalType == PuzzleElementType::WaterGoal)
        {
            return HasElement(from, PuzzleElementType::WaterIn) || HasElement(from, PuzzleElementType::WaterChannel) ||
                   HasElement(from, PuzzleElementType::WaterGoal);
        }
        else if (goalType == PuzzleElementType::ElectricGoal)
        {
            return HasElement(from, PuzzleElementType::ElectricIn) ||
                   HasElement(from, PuzzleElementType::ElectricGoal) || IsEmpty(from);
        }
        assert(false);
        return false;
    }
    bool PuzzleSolver::IsValidSource(PuzzleElementType::Enum sourceType, PuzzleElementType::Enum goalType)
    {
        return (sourceType == PuzzleElementType::WaterIn && goalType == PuzzleElementType::WaterGoal) ||
               (sourceType == PuzzleElementType::ElectricIn && goalType == PuzzleElementType::ElectricGoal);
    }

} // namespace Generated

namespace Generated
{
    bool RenderDebugUI(PuzzleData& obj)
    {
        bool dataChanged = false;

        bool isVisible = true;
        if (ImGui::Begin("Puzzle", &isVisible))
        {
            ImGui::Text("%s", obj.PuzzleName);

            int32_t W = obj.WidthTiles;
            int32_t H = obj.HeightTiles;
            ImGui::PushID("width");
            ImGui::SetNextItemWidth(40);
            if (ImGui::DragInt("", &W, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles))
            {
                obj.WidthTiles = uint8_t(W);
                dataChanged = true;
            }
            ImGui::PopID();
            ImGui::SameLine();
            ImGui::Text("x");
            ImGui::SameLine();
            ImGui::SetNextItemWidth(40);
            ImGui::PushID("height");
            if (ImGui::DragInt("", &H, 0.3f, 0, Puzzle::Config::MaxPuzzleSizeTiles))
            {
                obj.HeightTiles = uint8_t(H);
                dataChanged = true;
            }
            ImGui::PopID();
        }
        ImGui::End();

        if (dataChanged)
        {
            char path[128]{0};
            bx::snprintf(path, sizeof(path), "game/data/%s.pzl", obj.PuzzleName);
            Serializer ser;
            ser.Init(path);
            if (Save(&obj, 1, ser))
            {
                LOG("Saved to %s", path);
            }
            else
            {
                LOG_ERROR("Failed to save to %s", path);
            }
            ser.Finish();
        }

        return isVisible;
    }

    PuzPos operator+=(PuzPos lhs, const PuzPos& rhs)
    {
        lhs.X += rhs.X;
        lhs.Y += rhs.Y;
        return lhs;
    }
    PuzPos operator+(PuzPos lhs, const PuzPos& rhs)
    {
        lhs += rhs;
        return lhs;
    }
} // namespace Generated
