#include "gameplay/game.h"
#include "gameplay/input.h"
#include "gameplay/scene.h"

#include "utility/command.h"
#include "utility/ftfont.h"
#include "utility/logger.h"
#include "utility/resourcemanager.h"

#include "graphics/glwindow.h"

#include "sound/engine.h"

#include <SDL_video.h>
#include <glm/gtc/matrix_transform.hpp>
#include <memory>
#include <utility/events.h>

bool Game::init() {
  window = std::make_shared<GLWindow>("Yupple Mayham", 1024, 768);

  if (!window->Init())
    ERROR_LOG("Failed to init GLWindow: {}", SDL_GetError());

  if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
    ERROR_LOG("Failed to load GLLoader", NULL);

#if _DEBUG
  LOG_LEVEL(DEBUG);
#else
  LOG_LEVEL(INFO);
#endif

  SDL_GL_SetSwapInterval(1);
  glViewport(0, 0, window->Width(), window->Height());
  glDisable(GL_DEPTH_TEST);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  // For now we'll init default bindings, but as we move forward controls will
  // be set by the player and saved in controls.xml
  inputHandler = std::make_shared<InputHandler>();
  inputHandler->bindKeyCommand(SDLK_w, 0, std::make_unique<MoveUpCommand>());
  inputHandler->bindKeyCommand(SDLK_a, 0, std::make_unique<MoveLeftCommand>());
  inputHandler->bindKeyCommand(SDLK_s, 0, std::make_unique<MoveDownCommand>());
  inputHandler->bindKeyCommand(SDLK_d, 0, std::make_unique<MoveRightCommand>());

  inputHandler->bindMouseCommand(MOUSE_BUTTON_LEFT, 100,
                                 std::make_unique<ShootCommand>());
  inputHandler->bindMouseMotion(std::make_unique<FollowMouseCommand>());
  inputHandler->bindMouseScroll(std::make_unique<CycleCommand>());

  game_state |= GAME_RUNNING;
  globalEventManager = std::make_shared<EventManager>();
  resourceManager = std::make_shared<ResourceManager>();
  renderer = std::make_shared<Renderer>(resourceManager, window);
  audioEngine = std::make_shared<AudioEngine>(resourceManager);
  camera = std::make_unique<Camera>(static_cast<float>(window->Width()),
                                    static_cast<float>(window->Height()));
  audioEngine->hookEventManager(globalEventManager);
  /* Testing */
  audioEngine->pushMusic("music/short_song.ogg");
  audioEngine->pushMusic("music/bright.ogg");
  audioEngine->pushMusic("music/main_song.ogg");
  audioEngine->playMusic();
  /* */
  renderer->hookEventManager(globalEventManager);
  textHandler = std::make_shared<Text>();
  if (!textHandler->loadFonts("fonts"))
    return false;
  return true;
}

const unsigned Game::getWindowWidth() const { return window->Width(); }
const unsigned Game::getWindowHeight() const { return window->Height(); }

bool Game::loadDebugScene() {
  currentScene = std::make_shared<Scene>(SCENE_SHOOTER, resourceManager,
                                         globalEventManager);
  currentScene->init();
  audioEngine->hookSceneManager(currentScene->getEventManager());
  if (currentScene->getPlayer() == nullptr)
    return false;
  inputHandler->setActor(currentScene->getPlayer().get());
  camera->setTarget(currentScene->getPlayer().get());
  return true;
}

void Game::captureInput(SDL_Event &e) {
  inputHandler->captureInput(e);
  if (e.type == SDL_WINDOWEVENT_RESIZED) {
    size_t width = static_cast<size_t>(e.window.data1);
    size_t height = static_cast<size_t>(e.window.data2);
    globalEventManager->notify<WindowResizeEvent>({width, height});
    window->resizeWindow(width, height);
    camera->setViewportSize(static_cast<float>(e.window.data1),
                            static_cast<float>(e.window.data2));
  }
}

void Game::executeInputs() {
  inputHandler->handleInput();
  inputHandler->executeCommands();
}

void Game::update(double deltaTime) {
  if (currentScene) {
    currentScene->update(deltaTime);
    if (auto player = currentScene->getPlayer()) {
      audioEngine->updateListener(player->getPosition());
      player->setLocalPosition(camera->worldToLocal(player->getPosition()));
    }
  }
  camera->update(deltaTime);
  audioEngine->poll();
}

void Game::render() {
  glClearColor(0.05f, 0.25f, 0.05f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  renderer->setProjAndViewMatrix(camera->getProjectionMatrix(),
                                 camera->getViewMatrix());

  if (currentScene)
    currentScene->render(renderer);

  window->swap();
}

void Game::quit() { game_state = GAME_QUITTING; }

Game::~Game() {
  if (audioEngine) {
    audioEngine->killMusic();
  }
  resourceManager->clearResources();
}
