#include "engine.h"

#include <SFML/System/Clock.hpp>
#include <SFML/Window.hpp>
#include <SparrowInput/input.h>
#include <SparrowRenderer/sparrowrenderer.h>
#include <btBulletCollisionCommon.h>
#include <btBulletCollisionCommon.h>
#include <btBulletDynamicsCommon.h>
#include "resourcemanager.h"
#include "scene/scenetree.h"
#include "sparrowshell/sparrowshell.h"
#include "scene/physicsdebugnode.h"
#include "imgui/imgui.h"
#include "tools/loader.h"
#include "guitools.h"

Engine::Engine() :
    m_window(nullptr),
    m_input(nullptr),
    m_world(nullptr),
    m_physicsDebugNode(nullptr),
    m_togglePhysicsDebugAction(NO_ACTION),
    m_toggleShellAction(NO_ACTION),
    m_exitGameAction(NO_ACTION),
    m_showMouseAction(NO_ACTION),
    m_mouseVisible(true),
    m_pickerEnabled(false)
{
    m_clock = new sf::Clock();
    m_clock->restart();
    m_renderer = new SparrowRenderer();
}

Engine::~Engine()
{
    delete m_clock;
    delete m_renderer;
    if(m_window != NULL)
    {
        m_window->close();
        delete m_window;
        delete m_input;
    }
    if(m_world != NULL)
        delete m_world;
}

void Engine::createWindow(std::string title,
                unsigned int w,
                unsigned int h,
                const std::string &mode)
{
    sf::Uint32 style = sf::Style::Close;
    if(mode == "fullscreen")
        style = sf::Style::Fullscreen;
    else if(mode == "borderless")
        style = sf::Style::None;
    m_window = new sf::Window(sf::VideoMode(w, h),
                              title,
                              style,
                              sf::ContextSettings(24, 8, 0, 3, 3, sf::ContextSettings::Attribute::Core));
    m_window->setFramerateLimit(60);
    m_input = new Input(m_window);
    m_renderer->initGL(w, h);
    m_sparrowshell = new SparrowShell(m_window);
    m_guiTools = new GuiTools();
}

void Engine::initPhysics()
{
    btDefaultCollisionConfiguration *collisionConfiguration = new btDefaultCollisionConfiguration();
    btBroadphaseInterface *broadPhase = new btAxisSweep3(btVector3(-1000, -1000, -1000), btVector3(1000, 1000, 1000));
    btCollisionDispatcher *dispatcher = new btCollisionDispatcher(collisionConfiguration);
    btSequentialImpulseConstraintSolver *solver = new btSequentialImpulseConstraintSolver();
    m_world = new btDiscreteDynamicsWorld(dispatcher, broadPhase, solver, collisionConfiguration);
    m_world->setGravity(btVector3(0, -9.81f, 0));
}

void Engine::update()
{
    // update delta time
    m_lastTimeStamp = m_timeStamp;
    m_timeStamp = (unsigned int) m_clock->getElapsedTime().asMilliseconds();
    // update Events
    m_input->updateEvents();
    checkSpecialInputs();

	// initialize imgui frame
	ImGuiIO& io = ImGui::GetIO();
    io.DeltaTime = float(getDeltaTime()) / 1000.;
    ImGui::NewFrame();
	
    // settings gui
	{
        static bool settingsGuiOpen = true;
        if(settingsGuiOpen)
        {
            ImGui::Begin("Window Settings", &settingsGuiOpen);
            ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
            bool physicsDebugEnabled = (m_physicsDebugNode != nullptr);
            if(ImGui::Checkbox("Toggle physics debug", &physicsDebugEnabled))
            {
                if(physicsDebugEnabled)
                    enablePhysicsDebug();
                else
                    disablePhysicsDebug();
            }
            bool isMouseVisible = m_mouseVisible;
            if(ImGui::Checkbox("Mouse cursor ( shortcut : [M] )", &isMouseVisible))
                toggleMouseVisibility();
            ImGui::End();
        }
	}
	
    // update Physics
    if(m_world != nullptr)
    {
        m_world->stepSimulation(1000.f*(float)getDeltaTime());
        if(m_physicsDebugNode != nullptr)
        {
            m_physicsDebugNode->clearBuffers();
            m_world->debugDrawWorld();
            getScene()->registerMeshType(m_physicsDebugNode->getFlags());
        }
    }

    // update Scene
    getScene()->update();
    getScene()->updateShaders();

    // update Display
    if(m_input->isResized())
        m_renderer->resizeGL(m_window->getSize().x, m_window->getSize().y);
    m_renderer->renderGL();
    m_window->display();
}

void Engine::start()
{
    m_running = true;
    while(!m_input->isCloseRequested() && m_running)
        update();
}

void Engine::stop()
{
    m_running = false;
}

unsigned int Engine::getTime() const
{
    return m_timeStamp;
}

unsigned int Engine::getDeltaTime() const
{
    return m_timeStamp - m_lastTimeStamp;
}

//void Engine::setScene(SceneTree *scene)
void Engine::setScene(std::string scene)
{
    if(m_current_scene.empty()){
        m_current_scene = scene;
    }
    SceneTree* previous_scene = RESOURCE_GET(SceneTree,m_current_scene);
    SceneTree* new_scene = RESOURCE_GET(SceneTree,scene);
    m_current_scene = scene;

    if(m_physicsDebugNode != nullptr)
    {
        previous_scene->removeFromIndex(m_physicsDebugNode);
        new_scene->addToIndex(m_physicsDebugNode);
    }

    previous_scene->getRootObject()->removeChild(m_sparrowshell);
    previous_scene->getRootObject()->removeChild(m_guiTools);

    m_renderer->setScene(new_scene);
    m_renderer->resizeGL(m_window->getSize().x, m_window->getSize().y);

    new_scene->getRootObject()->addChild(m_sparrowshell);
    new_scene->getRootObject()->addChild(m_guiTools);
}

void Engine::enablePhysicsDebug()
{
    if(m_world != nullptr && m_physicsDebugNode == nullptr)
    {
        m_physicsDebugNode = new PhysicsDebugNode();
        getScene()->addToIndex(m_physicsDebugNode);
        m_world->setDebugDrawer(m_physicsDebugNode);
        m_world->getDebugDrawer()->setDebugMode(btIDebugDraw::DBG_DrawWireframe);
    }
}

void Engine::disablePhysicsDebug()
{
    if(m_world != nullptr && m_physicsDebugNode != nullptr)
    {
        m_world->setDebugDrawer(nullptr);
        getScene()->removeFromIndex(m_physicsDebugNode);
        delete m_physicsDebugNode;
        m_physicsDebugNode = nullptr;
    }
}


void Engine::toggleMouseVisibility()
{
    m_mouseVisible = !m_mouseVisible;
    m_window->setMouseCursorVisible(m_mouseVisible);
    m_input->setMouseGrabbed(!m_mouseVisible);
}

void Engine::setTogglePhysicsDebugAction(int action)
{
    m_togglePhysicsDebugAction = action;
}

void Engine::setToggleShellAction(int action)
{
    m_toggleShellAction = action;
}

void Engine::setExitGameAction(int action)
{
    m_exitGameAction = action;
}

void Engine::setShowMouseAction(int action)
{
    m_showMouseAction = action;
}

void Engine::checkSpecialInputs()
{
    for(Action action : m_input->getActions())
    {
        if(action.action == NO_ACTION)
            continue;

        if(action.action == m_togglePhysicsDebugAction)
        {
            if(m_physicsDebugNode == nullptr)
                enablePhysicsDebug();
            else
                disablePhysicsDebug();
        }
        else if(action.action == m_toggleShellAction)
            m_sparrowshell->toggleShell();
        else if(action.action == m_exitGameAction)
        {
            if(m_sparrowshell->isVisible())
                m_sparrowshell->toggleShell();
            else
                m_running = false;
        }
        else if(action.action == m_showMouseAction)
            toggleMouseVisibility();
    }
}


void Engine::createScene(std::string scene_name)
{
    RESOURCE_ADD(new SceneTree(*this), SceneTree, scene_name);
    //return new SceneTree(*this);
}

SceneTree* Engine::getScene() const
{
    return RESOURCE_GET(SceneTree,m_current_scene);
}
