#include "gui_fsm.hpp"
#include <iostream>
#include <chrono>
#include <numeric>
#include <functional>
#include "components.hpp"
#include <numbers>
#include "systems.hpp"
#include "events.hpp"
#include "sound.hpp"
#include <fmt/xchar.h>

namespace gui {
  using namespace components;

  FSM::FSM() :
    $window(sf::VideoMode({SCREEN_WIDTH, SCREEN_HEIGHT}), "Zed's Raycaster Thing"),
    $debug_ui($levels),
    $main_ui($window),
    $level($levels.current()),
    $combat_ui($level),
    $status_ui($level),
    $map_ui($level),
    $mini_map($level),
    $font{FONT_FILE_NAME}
    {
      $levels.temp_create_player_rituals();
    }

  void FSM::event(Event ev) {
    switch($state) {
      FSM_STATE(State, START, ev);
      FSM_STATE(State, MOVING, ev);
      FSM_STATE(State, ATTACKING, ev);
      FSM_STATE(State, ROTATING, ev);
      FSM_STATE(State, IDLE, ev);
      FSM_STATE(State, IN_COMBAT, ev);
      FSM_STATE(State, COMBAT_ROTATE, ev);
      FSM_STATE(State, NEXT_LEVEL, ev);
      FSM_STATE(State, END, ev);
    }
  }

  void FSM::START(Event ) {

    $main_ui.update_level($level);
    $level.world->set_the<Debug>({});
    $main_ui.init();

    // BUG: maybe this is a function on main_ui?
    auto cell = $main_ui.$overlay_ui.$gui.cell_for("left");
    $debug_ui.init(cell);
    $debug_ui.update_level($level);

    $combat_ui.init();
    $status_ui.init();

    $status_ui.log(L"Welcome to the game!");

    $boss_fight_ui = $levels.create_bossfight($level.world);
    $boss_fight_ui->init();

    $map_ui.init();
    $mini_map.init($main_ui.$overlay_ui.$gui);

    run_systems();
    state(State::IDLE);
  }

  void FSM::MOVING(Event ) {
    // this should be an optional that returns a point
    if(auto move_to = $main_ui.play_move()) {
      System::plan_motion(*$level.world, *move_to);
      run_systems();
      $main_ui.dirty();
      state(State::IDLE);
    }
  }

  void FSM::ATTACKING(Event ev) {
    using enum Event;
    switch(ev) {
      case TICK: {
        System::combat($level, $temp_attack_id);
        run_systems();
        state(State::IN_COMBAT);
       } break;
      case STOP_COMBAT:
        dbc::log("Exiting ATTACKING STATE");
        state(State::IDLE);
        break;
      case ATTACK:
        // ignore these since they're just from SFML not having discrete events
        break;
      default:
        dbc::log(fmt::format("In ATTACKING state, unhandled event {}", (int)ev));
        state(State::IDLE);
    }
  }

  void FSM::ROTATING(Event ) {
    if($main_ui.play_rotate()) {
      state(State::IDLE);
    }
  }

  void FSM::COMBAT_ROTATE(Event ) {
    if($main_ui.play_rotate()) {
      state(State::IN_COMBAT);
    }
  }

  void FSM::IDLE(Event ev) {
    using enum Event;

    sound::stop("walk");

    switch(ev) {
      case QUIT:
        $window.close();
        state(State::END);
        return; // done
      case MOVE_FORWARD:
        try_move(1, false);
        break;
      case MOVE_BACK:
        try_move(-1, false);
        break;
      case MOVE_LEFT:
        try_move(-1, true);
        break;
      case MOVE_RIGHT:
        try_move(1, true);
        break;
      case ROTATE_LEFT:
        $main_ui.plan_rotate(-1);
        state(State::ROTATING);
        break;
      case ROTATE_RIGHT:
        $main_ui.plan_rotate(1);
        state(State::ROTATING);
        break;
      case MAP_OPEN:
        $map_open = !$map_open;
        break;
      case ATTACK:
        state(State::ATTACKING);
        break;
      case START_COMBAT:
        $map_open = false;
        state(State::IN_COMBAT);
        break;
      case CLOSE:
        dbc::log("Nothing to close.");
        break;
      case STAIRS_DOWN:
        sound::stop("ambient");
        state(State::NEXT_LEVEL);
        break;
      case STOP_COMBAT:
      case TICK:
        // do nothing
        break;
      default:
        dbc::sentinel("unhandled event in IDLE");
    }
  }

  void FSM::NEXT_LEVEL(Event ev) {
    using enum Event;

    switch(ev) {
      case STAIRS_DOWN:
        sound::play("ambient");
        next_level();
        state(State::IDLE);
      default:
        break; // do nothing for now
    }
  }

  void FSM::IN_COMBAT(Event ev) {
    using enum Event;

    switch(ev) {
      case ATTACK:
        $main_ui.dirty();
        sound::play("Sword_Hit_1");
        state(State::ATTACKING);
        break;
      case ROTATE_LEFT:
        $main_ui.plan_rotate(-1);
        state(State::COMBAT_ROTATE);
        break;
      case ROTATE_RIGHT:
        $main_ui.plan_rotate(1);
        state(State::COMBAT_ROTATE);
        break;
      case STOP_COMBAT:
        $main_ui.$overlay_ui.close_sprite("top_right");
        state(State::IDLE);
        break;
      case QUIT:
        $window.close();
        state(State::END);
        return;
      default:
        break;
    }
  }

  void FSM::try_move(int dir, bool strafe) {
    using enum State;
    // prevent moving into occupied space
    Point move_to = $main_ui.plan_move(dir, strafe);

    if($level.map->can_move(move_to) && !$level.collision->occupied(move_to)) {
      sound::play("walk");
      state(MOVING);
    } else {
      state(IDLE);
      $main_ui.abort_plan();
    }
  }

  void FSM::END(Event ev) {
    dbc::log(fmt::format("END: received event after done: {}", int(ev)));
  }

  void FSM::keyboard_mouse() {
    while(const auto ev = $window.pollEvent()) {
      if(ev->is<sf::Event::Closed>()) {
        event(Event::QUIT);
      }

      if(const auto* mouse = ev->getIf<sf::Event::MouseButtonPressed>()) {
        if(mouse->button == sf::Mouse::Button::Left) {
          sf::Vector2f pos = $window.mapPixelToCoords(mouse->position);
          if(in_state(State::NEXT_LEVEL)) {
            $boss_fight_ui->mouse(pos.x, pos.y, false);

            if($boss_fight_ui->boss_dead()) {
              event(Event::STAIRS_DOWN);
            }
          } else {
            $debug_ui.mouse(pos.x, pos.y, false);
            $combat_ui.mouse(pos.x, pos.y, false);
            $status_ui.mouse(pos.x, pos.y, false);
            $main_ui.mouse(pos.x, pos.y, false);
          }
        }
      } else if(const auto* mouse = ev->getIf<sf::Event::MouseMoved>()) {
        sf::Vector2f pos = $window.mapPixelToCoords(mouse->position);
        $debug_ui.mouse(pos.x, pos.y, true);
        $combat_ui.mouse(pos.x, pos.y, true);
        $status_ui.mouse(pos.x, pos.y, true);
        $main_ui.mouse(pos.x, pos.y, true);
      }

      if(const auto* key = ev->getIf<sf::Event::KeyPressed>()) {
        using KEY = sf::Keyboard::Scan;

        switch(key->scancode) {
          case KEY::W:
            event(Event::MOVE_FORWARD);
            break;
          case KEY::S:
            event(Event::MOVE_BACK);
            break;
          case KEY::Q:
            event(Event::ROTATE_LEFT);
            break;
          case KEY::E:
            event(Event::ROTATE_RIGHT);
            break;
          case KEY::D:
            event(Event::MOVE_RIGHT);
            break;
          case KEY::A:
            event(Event::MOVE_LEFT);
            break;
          case KEY::R:
            dbc::log("HEY! DIPSHIT! You need to move debug ui so you can rest stats.");
            break;
          case KEY::M:
            event(Event::MAP_OPEN);
            break;
          case KEY::Escape:
            event(Event::CLOSE);
            break;
          case KEY::Space:
            event(Event::ATTACK);
            break;
          case KEY::P:
            sound::mute(false);
            $debug_ui.debug();
            shaders::reload();
            break;
          case KEY::O:
            autowalking = true;
            break;
          case KEY::L:
            event(Event::STAIRS_DOWN);
            break;
          default:
            break; // ignored
        }
      }
    }
  }

  void FSM::draw_gui() {
    if(in_state(State::NEXT_LEVEL)) {
      $boss_fight_ui->render($window);
    } else {
      // BUG: maybe pass the stats to main_ui for this?
      auto start = $debug_ui.time_start();
      $main_ui.render();
      $debug_ui.sample_time(start);
      $debug_ui.render($window);
      $status_ui.render($window);
      $combat_ui.render($window);

      if($map_open) {
        $map_ui.render($window, $main_ui.$compass_dir);
      } else {
        $mini_map.render($window, $main_ui.$compass_dir);
      }
    }
  }

  void FSM::render() {
    if(in_state(State::NEXT_LEVEL)) {
      $window.clear();
      $boss_fight_ui->render($window);
    } else {
      draw_gui();
    }

    $window.display();
  }

  void FSM::run_systems() {
    System::generate_paths($level);
    System::enemy_ai_initialize($level);
    System::enemy_pathing($level);
    System::collision($level);
    System::motion($level);
    System::lighting($level);
    System::death($level, $levels.$components);
  }

  bool FSM::active() {
    return !in_state(State::END);
  }

  void FSM::handle_world_events() {
    using eGUI = Events::GUI;
    auto& world = *$level.world;

    while(world.has_event<eGUI>()) {
      auto [evt, entity, data] = world.recv<eGUI>();
      auto player = world.get_the<Player>();

      switch(evt) {
        case eGUI::COMBAT: {
            auto &damage = std::any_cast<Events::Combat&>(data);

            if(damage.enemy_did > 0) {
              $status_ui.log(fmt::format(L"Enemy HIT YOU for {} damage!", damage.enemy_did));
            } else {
              $status_ui.log(L"Enemy MISSED YOU.");
            }

            if(damage.player_did > 0) {
              $status_ui.log(fmt::format(L"You HIT enemy for {} damage!", damage.player_did));
            } else {
              $status_ui.log(L"You MISSED the enemy.");
            }
          }
          break;
        case eGUI::COMBAT_START:
          event(Event::START_COMBAT);
          break;
        case eGUI::ENEMY_SPAWN:
          $debug_ui.update_level($level);
          $main_ui.update_level($level);
          run_systems();
          break;
        case eGUI::NO_NEIGHBORS:
          event(Event::STOP_COMBAT);
          break;
        case eGUI::LOOT: {
            // auto &item = std::any_cast<InventoryItem&>(data);
            // $status_ui.log(fmt::format("You picked up a {}.",
            //      std::string(item.data["name"])));
            $status_ui.log(L"You picked up an item.");
          } break;
        case eGUI::EVADE:
        case eGUI::BLOCK:
        case eGUI::HEAL:
          dbc::log("YOU NEED TO IMPLEMENT THIS!!!!!");
          break;
        case eGUI::ATTACK:
          $temp_attack_id = std::any_cast<int>(data);
          event(Event::ATTACK);
          break;
        case eGUI::STAIRS_DOWN:
          event(Event::STAIRS_DOWN);
          break;
        case eGUI::DEATH: {
            $status_ui.update();
            if(entity != player.entity) {
              $main_ui.dead_entity(entity);
            }
          } break;
        case eGUI::NOOP: {
            if(data.type() == typeid(std::string)) {
              auto name = std::any_cast<std::string>(data);
              $status_ui.log(fmt::format(L"NOOP EVENT! {},{}", evt, entity));
            }
          } break;
        default:
          $status_ui.log(fmt::format(L"INVALID EVENT! {},{}", evt, entity));
      }
    }
  }

  void FSM::next_level() {
    $levels.create_level($level.world);
    $level = $levels.next();

    $debug_ui.update_level($level);
    $status_ui.update_level($level);
    $map_ui.update_level($level);
    $mini_map.update_level($level);
    $combat_ui.update_level($level);
    $main_ui.update_level($level);

    $boss_fight_ui = $levels.create_bossfight($level.world);
    $boss_fight_ui->init();

    run_systems();
  }
}
