#include <typeinfo>
#include <iostream>

#include "GameMakerGUI.hpp"
#include "ControllerComponent.hpp"
#include "HealthComponent.h"
#include "RigidbodyComponent.hpp"
#include "Sprite.hpp"
#include "TileMap.hpp"
#include "TransformComponent.hpp"
#include "TinyMath.hpp"

static bool showGui = true;
static bool testBool = false;
static bool showDebugWindow = false;
static bool showGameObjectManager = false;
static bool showObjectCreationWindow = false;
static bool showGameObjectEditor = false;
static bool showComponentEditor = false;
static bool showComponentCreationWindow = false;

static int currentObjIndex = 0;
static Component *currentComponent = nullptr;

GameMakerGUI::GameMakerGUI(SDL_Window *window, SDL_Renderer *renderer, std::vector<GameObject *> *gameObjects)
{
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io = ImGui::GetIO();
    (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;

    ImGui::StyleColorsDark();

    ImGui_ImplSDL2_InitForSDLRenderer(window, renderer);
    ImGui_ImplSDLRenderer_Init(renderer);

    m_gameobjects = gameObjects;
}

GameMakerGUI::~GameMakerGUI()
{
    ImGui_ImplSDLRenderer_Shutdown();
    ImGui_ImplSDL2_Shutdown();
    ImGui::DestroyContext();
}

void GameMakerGUI::Input(SDL_Event e)
{
    ImGui_ImplSDL2_ProcessEvent(&e);
}

void GameMakerGUI::RenderEditor(IGraphicsEngineRenderer *mRenderer, bool *runGame)
{
    ImGuiIO &io = ImGui::GetIO();

    // Start the Dear ImGui frame
    ImGui_ImplSDLRenderer_NewFrame();
    ImGui_ImplSDL2_NewFrame();
    ImGui::NewFrame();

    if (testBool)
    {
        ImGui::ShowMetricsWindow(&testBool);
    }

    ImGuiWindowFlags window_flags = 0;
    window_flags |= ImGuiWindowFlags_MenuBar;

    if (ImGui::BeginMainMenuBar())
    {
        if (ImGui::BeginMenu("View"))
        {
            ImGui::MenuItem("Game Obect Editor", NULL, &showGameObjectManager);
            ImGui::MenuItem("Debug Window", NULL, &showDebugWindow);
            ImGui::MenuItem("Metrics Window", NULL, &testBool);
            ImGui::EndMenu();
        }
        if (ImGui::BeginMenu("Run"))
        {
            ImGui::MenuItem("Play Game", NULL, runGame);
            ImGui::EndMenu();
        }
        ImGui::EndMainMenuBar();
    }
    if (showGameObjectManager)
        ShowGameObjectWindow();
    if (showDebugWindow)
        ShowDebugWindow();
    if (showObjectCreationWindow)
        CreateNewGameObject();
    if (showGameObjectEditor)
        EditGameObject();
    if (showComponentEditor)
        currentComponent->ShowComponentEditor(&showComponentEditor);
    if (showComponentCreationWindow)
        CreateComponent(mRenderer);

    ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);

    SDL_Renderer *renderer = dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer();
    ImGui::Render();
    SDL_RenderSetScale(renderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
    // SDL_SetRenderDrawColor(renderer, (Uint8)(clear_color.x * 255), (Uint8)(clear_color.y * 255), (Uint8)(clear_color.z * 255), (Uint8)(clear_color.w * 255));
    // SDL_RenderClear(renderer);
    ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
    // SDL_RenderPresent(renderer);
}

void GameMakerGUI::ShowGameObjectWindow()
{
    ImGuiWindowFlags window_flags = 0;
    window_flags |= ImGuiWindowFlags_NoCollapse;

    if (ImGui::Begin("Game Object Manager", &showGameObjectManager, window_flags))
    {
        ImGui::Text("Game Objects");
        for (int i = 0; i < m_gameobjects->size(); ++i)
        {
            GameObject *obj = m_gameobjects->at(i);
            ImGui::Text("%s", obj->GetName().c_str());
            ImGui::SameLine();
            std::string buttonName = "Edit Object##" + std::to_string(i);
            if (ImGui::Button(buttonName.c_str()))
            {
                showGameObjectEditor = true;
                currentObjIndex = i;
            }
            ImGui::SameLine();
            std::string deleteButtonName = "Delete Object##" + std::to_string(i);
            if (ImGui::Button(deleteButtonName.c_str()))
            {
                m_gameobjects->erase(m_gameobjects->begin() + i);
            }
        }
        if (ImGui::Button("New Game Object"))
        {
            showObjectCreationWindow = true;
        }
        ImGui::End();
    }
}

void GameMakerGUI::ShowDebugWindow()
{
    if (ImGui::Begin("Debug Window"), &showDebugWindow)
    {
        ImGui::Text("showGui: %i", showGui);
        ImGui::Text("testBool: %i", testBool);
        ImGui::Text("showDebugWindow: %i", showDebugWindow);
        ImGui::Text("showGameObjectManager: %i", showGameObjectManager);
        ImGui::Text("showObjectCreationWindow: %i", showObjectCreationWindow);
        ImGui::Text("showGameObjectEditor: %i", showGameObjectEditor);
        ImGui::Text("currentObjIndex: %i", currentObjIndex);
        ImGui::InputInt("Set currentObjIndex", &currentObjIndex);
        ImGui::Text("showComponentEditor: %i", showComponentEditor);
        if (!(currentComponent == nullptr))
            ImGui::Text("currentComponent: %s", currentComponent->ComponentTypeName());
        ImGui::End();
    }
}

void GameMakerGUI::CreateNewGameObject()
{
    static char objectName[255] = "Name";
    if (ImGui::Begin("New Game Object", &showObjectCreationWindow))
    {
        ImGui::InputText("Name", objectName, IM_ARRAYSIZE(objectName));
        if (ImGui::Button("Create"))
        {
            m_gameobjects->push_back(new GameObject(std::string(objectName)));
            showObjectCreationWindow = false;
        }
        ImGui::End();
    }
}

void GameMakerGUI::EditGameObject()
{
    GameObject *current = m_gameobjects->at(currentObjIndex);
    std::vector<Component *> currentComponents = current->ListComponents();

    ImGuiWindowFlags window_flags = 0;
    window_flags |= ImGuiWindowFlags_NoCollapse;

    if (ImGui::Begin(("Editing " + current->GetName()).c_str(), &showGameObjectEditor, window_flags))
    {
        ImGui::Text("Components");
        for (int j = 0; j < currentComponents.size(); ++j)
        {
            Component *curComponent = currentComponents.at(j);
            ImGui::Text("%s", curComponent->ComponentTypeName());
            ImGui::SameLine();
            std::string buttonName = "Edit Component##" + std::to_string(j);
            if (ImGui::Button(buttonName.c_str()))
            {
                currentComponent = curComponent;
                showComponentEditor = true;
            }
            ImGui::SameLine();
            std::string deleteButtonName = "Delete Component##" + std::to_string(j);
            if (ImGui::Button(deleteButtonName.c_str()))
            {
                current->RemoveComponent(curComponent);
            }
        }
        if (ImGui::Button("New Component"))
        {
            showComponentCreationWindow = true;
        }
        ImGui::End();
    }
}

// There must be a better way than this, right?
static const std::string ComponentTypes[] = {
    "Controller",
    "Health",
    "Rigidbody",
    "Sprite",
    "TileMap",
    "Transform"};

void GameMakerGUI::CreateComponent(IGraphicsEngineRenderer *ren)
{
    ImGuiWindowFlags window_flags = 0;
    window_flags |= ImGuiWindowFlags_NoCollapse;
    static std::string selection = "None";

    if (ImGui::Begin("Create Component", &showComponentCreationWindow, window_flags))
    {
        GameObject *currentGameObject = m_gameobjects->at(currentObjIndex);
        ImGui::Text("Add a component to %s", currentGameObject->GetName().c_str());
        if (ImGui::BeginCombo("Component Type", selection.c_str()))
        {
            for (int i = 0; i < IM_ARRAYSIZE(ComponentTypes); ++i)
            {
                bool chosen = (selection == ComponentTypes[i]);
                if (ImGui::Selectable(ComponentTypes[i].c_str(), chosen))
                {
                    selection = ComponentTypes[i];
                }
                if (chosen)
                {
                    ImGui::SetItemDefaultFocus();
                }
            }
            ImGui::EndCombo();
        }
        // TODO: figure out a way to fit this into a switch statement instead of an if tree
        // This should be handled in an object oriented way
        // Factory pattern?
        if (selection == "Controller")
        {
            static Vec2D deltaPos;
            ImGui::InputFloat("Delta X", &deltaPos.x);
            ImGui::InputFloat("Delta Y", &deltaPos.y);
            if (ImGui::Button("Add Controller Component"))
            {
                ControllerComponent *component = new ControllerComponent(FindTransform(currentGameObject), deltaPos);
                currentGameObject->AddComponent(component);
                showComponentCreationWindow = false;
            }
        }
        else if (selection == "Health")
        {
            static int health = 0;
            ImGui::InputInt("Total Health", &health);
            if (ImGui::Button("Add Health Component"))
            {
                HealthComponent *component = new HealthComponent(health);
                currentGameObject->AddComponent(component);
                showComponentCreationWindow = false;
            }
        }
        else if (selection == "Rigidbody")
        {
            static Vec2D dim;
            static Vec2D vel;
            static float mass;
            static bool elastic = false;
            static bool gravity = false;

            ImGui::InputFloat("Width", &dim.x);
            ImGui::SameLine();
            ImGui::InputFloat("Height", &dim.y);
            ImGui::InputFloat("X Velocity", &vel.x);
            ImGui::SameLine();
            ImGui::InputFloat("Y Velocity", &vel.y);
            ImGui::InputFloat("Mass", &mass);
            ImGui::Checkbox("Elastic", &elastic);
            ImGui::Checkbox("Gravity", &gravity);
            if (ImGui::Button("Add Rigidbody Component"))
            {
                currentGameObject->AddComponent(new RigidbodyComponent(FindTransform(currentGameObject), dim, vel, mass, elastic, gravity));
                showComponentCreationWindow = false;
            }
        }
        else if (selection == "Sprite")
        {
            static int tileWidth;
            static int tileHeight;
            static int displayWidth;
            static int displayHeight;
            static int frameCount = 1;
            static char filePath[255] = "";

            ImGui::InputText("File Path", filePath, IM_ARRAYSIZE(filePath));
            ImGui::InputInt("Sprite Width", &tileWidth);
            ImGui::InputInt("Sprite Height", &tileHeight);
            ImGui::InputInt("Display Width", &displayWidth);
            ImGui::InputInt("Display Height", &displayHeight);
            ImGui::InputInt("FrameCount", &frameCount);
            if (ImGui::Button("Add Sprite Component"))
            {
                SpriteComponent *sprite;

                sprite = new SpriteComponent(FindTransform(currentGameObject));
                sprite->LoadImage(filePath, dynamic_cast<SDLGraphicsEngineRenderer *>(ren)->GetRenderer(), tileWidth, tileHeight, frameCount, Vec2D(displayWidth, displayHeight));
                currentGameObject->AddComponent(sprite);
                showComponentCreationWindow = false;
            }
        }
        else if (selection == "TileMap")
        {
            static char filePath[255] = "";
            static int rows;
            static int cols;
            static int tileWidth;
            static int tileHeight;
            static int mapX;
            static int mapY;

            ImGui::InputText("File Path", filePath, IM_ARRAYSIZE(filePath));
            ImGui::InputInt("Rows", &rows);
            ImGui::InputInt("Columns", &cols);
            ImGui::InputInt("Tile Width", &tileWidth);
            ImGui::InputInt("Tile Height", &tileHeight);
            ImGui::InputInt("Map X", &mapX);
            ImGui::InputInt("Map Y", &mapY);
            if (ImGui::Button("Add TileMap Component"))
            {
                TileMapComponent *tileMap;
                tileMap = new TileMapComponent(filePath, rows, cols, tileWidth, tileHeight, mapX, mapY, dynamic_cast<SDLGraphicsEngineRenderer *>(ren)->GetRenderer(), m_gameobjects);
                currentGameObject->AddComponent(tileMap);
                showComponentCreationWindow = false;
            }
        }
        else if (selection == "Transform")
        {
            static int xPos;
            static int yPos;
            ImGui::InputInt("X Position", &xPos);
            ImGui::InputInt("Y Position", &yPos);
            if (ImGui::Button("Add Transform Component"))
            {
                currentGameObject->AddComponent(new TransformComponent(xPos, yPos));
                showComponentCreationWindow = false;
            }
        }
        else
        {
            ImGui::Text("Select the type of Component to create");
        }
        ImGui::End();
    }
}

TransformComponent *GameMakerGUI::FindTransform(GameObject *curObj)
{
    std::vector<Component *> compList = curObj->ListComponents();
    for (int i = 0; i < compList.size(); i++)
    {
        if (strcmp(compList[i]->ComponentTypeName(), "Transform Component") == 0)
        {
            // WARNING: This is a hack and bad coding practice
            return dynamic_cast<TransformComponent *>(compList[i]);
        }
    }
    TransformComponent *transform = new TransformComponent(0, 0);
    curObj->AddComponent(transform);
    return transform;
}