#include <chrono>                   // for operator""s, chrono_literals
#include <iostream>                 // for cout, ostream
#include <fstream>
#include <memory>                   // for allocator, shared_ptr
#include <string>                   // for string, operator<<
#include <thread>                   // for sleep_for
#include <array>
#include <algorithm>

#include <ftxui/dom/elements.hpp>  // for hflow, paragraph, separator, hbox, vbox, filler, operator|, border, Element
#include <ftxui/dom/node.hpp>      // for Render
#include <ftxui/screen/box.hpp>    // for ftxui
#include <ftxui/component/loop.hpp>
#include <ftxui/screen/color.hpp>

#include <fmt/core.h>
#include "dbc.hpp"
#include "gui.hpp"
#include "rand.hpp"
#include "systems.hpp"
#include "events.hpp"
#include "render.hpp"
#include "save.hpp"

const int MAX_FONT_SIZE = 140;
const int MIN_FONT_SIZE = 20;

using std::string;
using namespace fmt;
using namespace std::chrono_literals;
using namespace ftxui;
using namespace components;

GUI::GUI(DinkyECS::World &world, Map& game_map) :
  $darkness({1600,800}),
  $game_map(game_map),
  $log({{"Welcome to the game!"}}),
  $status_ui(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
  $map_view(GAME_MAP_POS, 0, 0, 0, true),
  $world(world),
  $sounds("./assets"),
  $renderer()
{
  // this needs a config file soon
  // $sounds.load("ambient", "ambient_sound.mp3");
  $sounds.load("loot_gold", "loot_gold.mp3");
  $sounds.load("combat_player_hit", "combat_player_hit.mp3");
  $sounds.load("combat_enemy_hit", "combat_enemy_hit.mp3");
  $sounds.load("combat_miss", "combat_miss.mp3");

  $light_test.loadFromFile("./assets/light_test.png");
  $darkness.setTexture(&$light_test);

  resize_map(MAX_FONT_SIZE);
}

void GUI::resize_map(int new_size) {
  if($renderer.resize_grid(new_size, $map_view)) {
    // set canvas to best size
    $canvas = Canvas($map_view.width * 2, $map_view.height * 4);
  }
}

void GUI::save_world() {
  $log.log("Game saved!");
  save::to_file("./savefile.world", $world, $game_map);
}

void GUI::create_renderer() {
  Terminal::SetColorSupport(Terminal::Color::TrueColor);
  auto player = $world.get_the<Player>();

  $map_view.set_renderer(Renderer([&] {
    System::draw_map($world, $game_map, $canvas, $map_view.width, $map_view.height);
    return canvas($canvas);
  }));

  auto modal_buttons = Container::Horizontal({
      Button("OK", [&]{ $show_modal = false; }),
      Button("CANCEL", [&]{ $show_modal = false; }),
  });

  auto modal_test = Renderer([&, modal_buttons] {
    return hbox({
        hflow(
          vbox(
              text("Hello!"),
              modal_buttons->Render()
            ))}) | border;
  });
  modal_test->Add(modal_buttons);

  auto test_button = Container::Horizontal({
      Button("Open Test Modal", [&]{ $show_modal = true; }),
      Button("Close It", [&]{ $show_modal = false; }),
  });

  auto status_rend = Renderer(test_button, [&, modal_test, test_button, player]{
    const auto& player_combat = $world.get<Combat>(player.entity);
    const auto& inventory = $world.get<Inventory>(player.entity);
    $status_text = player_combat.hp > 0 ? "NOT DEAD" : "DEAD!!!!!!";

    std::vector<Element> log_list;
    for(auto msg : $log.messages) {
      log_list.push_back(text(msg));
    }

    auto log_box = vbox(log_list) | yflex_grow | border;

    return hbox({
        hflow(
          vbox(
              test_button->Render(),
              text(format("HP: {: >3} GOLD: {: >3}",
                  player_combat.hp, inventory.gold)) | border,
              text($status_text) | border,
              separator(),
              log_box
              )  | flex_grow
        ),
        separator(),
        hbox(),
    });
  });

  status_rend |= Modal(modal_test, &$show_modal);

  $status_ui.set_renderer(status_rend);
  $status_ui.add(modal_test);
}

void GUI::handle_world_events() {
  using eGUI = Events::GUI;
  auto player = $world.get_the<Player>();

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

    switch(evt) {
      case eGUI::COMBAT: {
          auto &damage = std::any_cast<Events::Combat&>(data);
          auto enemy_combat = $world.get<Combat>(entity);

          if(damage.enemy_did > 0) {
            $log.log(format("Enemy HIT YOU for {} damage!", damage.enemy_did));
            $log.log(format("-- Enemy has {} HP left.", enemy_combat.hp));
            $sounds.play("combat_enemy_hit");
            shake();
          } else {
            $log.log("Enemy MISSED YOU.");
          }

          if(damage.player_did > 0) {
            $log.log(format("You HIT enemy for {} damage!", damage.player_did));
            $sounds.play("combat_player_hit");
          } else {
            $sounds.play("combat_miss");
            $log.log("You MISSED the enemy.");
          }
        } break;
      case eGUI::LOOT: {
          auto &loot = std::any_cast<Loot&>(data);
          auto inventory = $world.get<Inventory>(player.entity);
          $sounds.play("loot_gold");
          $log.log(format("You found {} gold. You have {} now.",
                loot.amount, inventory.gold));
        }
        break;
      default:
        $log.log(format("INVALID EVENT! {},{}", evt, entity));
    }
  }
}

bool GUI::handle_ui_events() {
  using KB = sf::Keyboard;
  using MOUSE = sf::Mouse;
  bool event_happened = false;
  sf::Event event;
  auto player = $world.get_the<Player>();
  int map_font_size = $renderer.font_size();
  auto& player_motion = $world.get<Motion>(player.entity);

  while($renderer.poll_event(event)) {
    if(event.type == sf::Event::Closed) {
      $renderer.close();
    } else if(event.type ==  sf::Event::KeyPressed) {

      if(KB::isKeyPressed(KB::Left)) {
        player_motion.dx = -1;
        event_happened = true;
      } else if(KB::isKeyPressed(KB::Right)) {
        player_motion.dx = 1;
        event_happened = true;
      } else if(KB::isKeyPressed(KB::Up)) {
        player_motion.dy = -1;
        event_happened = true;
      } else if(KB::isKeyPressed(KB::Down)) {
        player_motion.dy = 1;
        event_happened = true;
      } else if(KB::isKeyPressed(KB::Equal)) {
        resize_map(map_font_size + 10);
      } else if(KB::isKeyPressed(KB::Hyphen)) {
        resize_map(map_font_size - 10);
      } else if(KB::isKeyPressed(KB::S)) {
        save_world();
      } else if(KB::isKeyPressed(KB::Tab)) {
        $status_ui.$component->OnEvent(Event::Tab);
      } else if(KB::isKeyPressed(KB::Enter)) {
        $status_ui.$component->OnEvent(Event::Return);
      }
    } else if(MOUSE::isButtonPressed(MOUSE::Left)) {
      sf::Vector2i pos = MOUSE::getPosition($renderer.$window);
      Mouse mev;
      mev.button = Mouse::Button::Left,
      mev.x=pos.x / $renderer.$ui_bounds.width; // this needs to be in...panel coordinates?
      mev.y=pos.y / $renderer.$ui_bounds.height;
      $status_ui.$component->OnEvent(Event::Mouse("", mev));
    }
  }

  return event_happened;
}


void GUI::run_systems() {
  auto player = $world.get_the<Player>();
  System::motion($world, $game_map);
  System::enemy_pathing($world, $game_map, player);
  System::lighting($world, $game_map, player);
  System::collision($world, player);
  System::death($world);
}

void GUI::shake() {
  for(int i = 0; i < 10; ++i) {
    int x = Random::uniform<int>(-20,20);
    int y = Random::uniform<int>(-20,20);
    // add x/y back to draw screen
    $renderer.draw($map_view, x, y);
    $renderer.display();
    std::this_thread::sleep_for(1ms);
  }
}

void GUI::render_scene() {
  $renderer.clear();

  $map_view.render();
  $renderer.draw($map_view);

  $status_ui.render();
  $renderer.draw($status_ui);


  $renderer.display();
}

int GUI::main() {
  // $sounds.play("ambient");
  create_renderer();
  run_systems();

  do {
    render_scene();

    if(handle_ui_events()) {
      run_systems();
      handle_world_events();
    }

    std::this_thread::sleep_for(10ms);
  } while($renderer.is_open());

  return 0;
}
