#include "SDL.hxx"

#include <algorithm>
#include <cassert>
#include <sstream>

#include <SDL_image.h>
#include <SDL_ttf.h>

namespace {
  std::string build_error_message(std::string_view const message, std::source_location const location)
  {
    std::ostringstream strm;
    strm << location.file_name() << ":" << location.line() << ":" << location.column();
    strm << " - ";
    strm << message << " (" << SDL_GetError() << ")";
    return strm.str();
  }
}

SDLError::SDLError(std::string_view const message, std::source_location const location)
    :std::runtime_error{::build_error_message(message, location)}
{
}

SDL* SDL::instance_ = nullptr;

SDL::SDL(std::uint32_t const flags)
{
  assert(instance_==nullptr);
  if (SDL_Init(flags)!=0) {
    throw SDLError{"Failed to initialize SDL."};
  }
  SDL_Log("Initialized SDL successfully.");

  auto const img_flags = IMG_INIT_PNG | IMG_INIT_JPG;
  if (IMG_Init(img_flags)!=img_flags) {
    throw SDLError{"Failed to initialize SDL_image."};
  }
  SDL_Log("Initialized SDL_image successfully.");

  if (TTF_Init()!=0) {
    throw SDLError{"Failed to initialize SDL_ttf."};
  }
  SDL_Log("Initialized SDL_ttf successfully.");

  auto const num_joysticks = SDL_NumJoysticks();
  for (int n = 0; n<num_joysticks; ++n) {
    add_controller(n);
  }

  instance_ = this;
}

SDL::~SDL() noexcept
{
  assert(instance_!=nullptr);

  for (auto const controller: controllers_) {
    SDL_Log("Closing controller %s.", SDL_GameControllerName(controller));
    SDL_GameControllerClose(controller);
  }

  TTF_Quit();
  SDL_Log("Shut down SDL_ttf successfully.");

  IMG_Quit();
  SDL_Log("Shut down SDL_image successfully.");

  SDL_Quit();
  SDL_Log("Shut down SDL successfully.");
  instance_ = nullptr;
}

SDL& SDL::instance() noexcept
{
  assert(instance_!=nullptr);
  return *instance_;
}

SDL& SDL::require(std::uint32_t const flags) noexcept
{
  assert((SDL_WasInit(flags) | SDL_INIT_NOPARACHUTE)==(flags | SDL_INIT_NOPARACHUTE));
  return instance();
}

void SDL::add_controller(int const which)
{
  controllers_.push_back(SDL_GameControllerOpen(which));
  SDL_Log("Opened controller %s.", SDL_GameControllerNameForIndex(which));
}

void SDL::remove_controller(int which)
{
  auto const controller = SDL_GameControllerFromInstanceID(which);
  SDL_Log("Closing controller %s.", SDL_GameControllerName(controller));
  SDL_GameControllerClose(controller);
  auto const it = std::find(controllers_.begin(), controllers_.end(), controller);
  if (it!=controllers_.end()) {
    controllers_.erase(it);
  }
}

std::vector<SDL_GameController*> const& SDL::get_controllers() const
{
  return controllers_;
}