#include "gui/main_ui.hpp"
#include "components.hpp"
#include "easings.hpp"
#include <fmt/xchar.h>
#include "constants.hpp"
#include "game_level.hpp"
#include "ai.hpp"

namespace gui {
  using namespace components;

  MainUI::MainUI(sf::RenderWindow& window) :
    $window(window),
    $rayview(std::make_shared<Raycaster>(RAY_VIEW_WIDTH, RAY_VIEW_HEIGHT))
  {
    $window.setVerticalSyncEnabled(VSYNC);
    $window.setFramerateLimit(FRAME_LIMIT);

    auto config = settings::get("config");

    $hand = textures::get_sprite(config["player"]["hands"]);
    $hand_anim = animation::load(config["player"]["hands"]);
  }

  void MainUI::dirty() {
    $needs_render = true;
  }

  void MainUI::init() {
    auto& player_position = GameDB::player_position();
    auto player = player_position.location;

    $rayview->init_shaders();
    $rayview->set_position(RAY_VIEW_X, RAY_VIEW_Y);
    $rayview->position_camera(player.x + 0.5, player.y + 0.5);

    $overlay_ui.init();
  }

  void MainUI::render() {
    if($needs_render) $rayview->render();
    $rayview->draw($window);

    if($mind_reading) render_mind_reading();
    $overlay_ui.render($window);

    if($hand_anim.playing) render_hands();
  }

  lel::Cell MainUI::overlay_cell(const std::string& name) {
    return $overlay_ui.$gui.cell_for(name);
  }

  std::optional<Point> MainUI::play_rotate() {
    if($rayview->play_rotate()) {
      $needs_render = false;
      return std::make_optional<Point>($rayview->aiming_at);
    } else {
      $needs_render = true;
      return std::nullopt;
    }
  }

  std::optional<components::Position> MainUI::play_move() {
    if($rayview->play_move()) {
      $needs_render = false;
      return std::make_optional<Position>(
          $rayview->camera_at,
          $rayview->aiming_at);
    } else {
      $needs_render = true;
      return std::nullopt;
    }
  }

  void MainUI::plan_rotate(int dir, float amount) {
    // -1 is left, 1 is right
    int extra = (amount == 0.5) * dir;
    $compass_dir = ($compass_dir + dir + extra) % COMPASS.size();
    $rayview->plan_rotate(dir, amount);
  }

  Point MainUI::plan_move(int dir, bool strafe) {
    return $rayview->plan_move(dir, strafe);
  }

  void MainUI::abort_plan() {
    $rayview->abort_plan();
  }

  void MainUI::dead_entity(DinkyECS::Entity entity) {
    auto world = GameDB::current_world();
    if(world->has<components::Sprite>(entity)) {
      auto &sprite = world->get<components::Sprite>(entity);
      $rayview->update_sprite(entity, sprite);
    }
  }

  void MainUI::toggle_mind_reading() {
    $mind_reading = !$mind_reading;

    if($mind_reading) {
      render_mind_reading();
    } else {
      $overlay_ui.close_text("left");
    }
  }

  void MainUI::render_mind_reading() {
    auto level = GameDB::current_level();
    if(auto entity = level.collision->occupied_by($rayview->aiming_at)) {
      if(auto enemy_ai = level.world->get_if<ai::EntityAI>(entity)) {
        $overlay_ui.show_text("left", fmt::format(L"AI: {}",
              guecs::to_wstring(enemy_ai->to_string())));
      } else {
        $overlay_ui.show_text("left", L"no mind to read");
      }
    } else {
      $overlay_ui.show_text("left", L"nothing there");
    }
  }

  void MainUI::update_level() {
    auto& level = GameDB::current_level();
    auto& player_position = GameDB::player_position();
    auto player = player_position.location;

    $rayview->update_level(level);
    $rayview->position_camera(player.x + 0.5, player.y + 0.5);

    player_position.aiming_at = $rayview->aiming_at;

    $compass_dir = 0;

    $overlay_ui.update_level();
    dirty();
  }

  void MainUI::mouse(int x, int y, guecs::Modifiers mods) {
    $overlay_ui.$gui.mouse(x, y, mods);
  }

  void MainUI::play_hands() {
    $hand_anim.play();
  }

  void MainUI::render_hands() {
    if(animation::apply($hand_anim, *$hand.sprite)) {
      $hand.sprite->setPosition({RAY_VIEW_X, RAY_VIEW_Y});
      $window.draw(*$hand.sprite);
    }
  }
}
