#include "application.h"

#include "../utils/color.h"
#include "assets/assetManager.h"
#include "events/inputManager.h"
#include "renderer/rendererManager.h"
#include "screens/screenManager.h"

#include <SDL2/SDL.h>
#include <iostream>
#include <stdexcept>

using namespace std;
using namespace quokka_gfx;
//------------------------------------------------------------
/* Constructor */
Application::Application(const std::string &title)
{
  init(title);
}
//------------------------------------------------------------
/* Default destructor */
Application::~Application()
{
  cleanup();
}
//------------------------------------------------------------
/* Initialize the first screen */
void Application::initScreen(std::unique_ptr<Screen> screen) const
{
  m_screenManager->setScreen(std::move(screen));
}
//------------------------------------------------------------
/* Main loop */
void Application::run()
{
  auto       lastTime  = SDL_GetPerformanceCounter();
  const auto frequency = static_cast<double>(SDL_GetPerformanceFrequency());

  while (m_running)
  {
    // Calculate delta time in seconds for smooth updates regardless of frame rate
    const auto currentTime = SDL_GetPerformanceCounter();
    const auto deltaTime   = static_cast<double>((currentTime - lastTime)) / frequency;
    lastTime               = currentTime;

    handleEvents();
    update(deltaTime);
    render();
  }
}
//------------------------------------------------------------
/* Quit the application */
void Application::quit()
{
  m_running = false;
}
//------------------------------------------------------------
AssetManager *Application::assetManager() const
{
  return m_assetManager.get();
}
//------------------------------------------------------------
InputManager *Application::inputManager() const
{
  return m_inputManager.get();
}
//------------------------------------------------------------
RendererManager *Application::rendererManager() const
{
  return m_rendererManager.get();
}
//------------------------------------------------------------
ScreenManager *Application::screenManager() const
{
  return m_screenManager.get();
}
//------------------------------------------------------------
/* Initialisation */
void Application::init(const std::string &title)
{
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0)
    throw std::runtime_error("Unable to initialize SDL: " + std::string(SDL_GetError()));

  m_window = SDL_CreateWindow(
    title.c_str(),
    SDL_WINDOWPOS_CENTERED,
    SDL_WINDOWPOS_CENTERED,
    1200,
    896,
    SDL_WINDOW_SHOWN);

  if (!m_window)
    throw std::runtime_error("Unable to create window: " + std::string(SDL_GetError()));

  m_sdlRenderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
  if (!m_sdlRenderer)
    throw std::runtime_error("Unable to create renderer: " + std::string(SDL_GetError()));

  // Enable alpha blending for transparency support
  SDL_SetRenderDrawBlendMode(m_sdlRenderer, SDL_BLENDMODE_BLEND);

  // Initialize subsystems
  m_assetManager    = std::make_unique<AssetManager>(m_sdlRenderer);
  m_inputManager    = std::make_unique<InputManager>();
  m_rendererManager = std::make_unique<RendererManager>(this);
  m_screenManager   = std::make_unique<ScreenManager>();

  m_running = true;
}
//------------------------------------------------------------
/* Handle SDL events */
void Application::handleEvents()
{
  auto eventProcessing = m_inputManager->getEventProcessing();

  SDL_Event event;
  while (SDL_PollEvent(&event))
  {
    if (event.type == SDL_QUIT)
    {
      quit();
    }

    eventProcessing.process(event);
  }

  m_screenManager->handleEvents();
}
//------------------------------------------------------------
/* Update game state */
void Application::update(const double deltaTime) const
{
  m_screenManager->update(deltaTime);
}
//------------------------------------------------------------
/* Render the game */
void Application::render() const
{
  m_rendererManager->clear(Color(30, 30, 30));

  m_screenManager->render(m_rendererManager.get());     // Render the current screen

  m_rendererManager->present();     // Present the rendered content to the screen
}
//------------------------------------------------------------
/* Cleanup resources */
void Application::cleanup() const
{
  if (m_sdlRenderer)
    SDL_DestroyRenderer(m_sdlRenderer);

  if (m_window)
    SDL_DestroyWindow(m_window);

  SDL_Quit();
}
//------------------------------------------------------------
