#include <iostream>
#include <fstream>
#include <chrono>                   // for operator""s, chrono_literals
#include <thread>                   // for sleep_for
#include "dinkyecs.hpp"
#include "events.hpp"
#include "dbc.hpp"
#include "render.hpp"
#include "lights.hpp"
#include <filesystem>
#include <fcntl.h>
#include "constants.hpp"
#include "point.hpp"
#include <fmt/core.h>
#include <locale>
#include <codecvt>
#include <vector>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <exception>

using namespace nlohmann;
using namespace ftxui;
using namespace std::chrono_literals;
using lighting::LightSource, lighting::LightRender;
namespace fs = std::filesystem;
using std::string, std::wstring, std::vector;
using fmt::println, fmt::print, fmt::format;

const Point GRID_SIZE={15,8};
const int DEFAULT_FONT_SIZE=200;

struct FontGridCell {
  size_t cm_index;
  string as_string;
  wstring as_wstring;
};

struct FontGrid {
  size_t width;
  size_t height;
  vector<vector<FontGridCell>> $grid;
  string $font_list;
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> $converter;
  vector<wstring> $wcharmap;
  vector<string> $charmap;

  FontGrid(string font_list, size_t width, size_t height) :
    width(width), height(height),
    $grid(height, vector<FontGridCell>(width, {0, "", L""})),
    $font_list(font_list)
  {
    configure_font();
  }

  void configure_font() {
    dbc::check(fs::exists($font_list), std::format("font listing {} does not exist", $font_list));

    std::ifstream in_file($font_list);
    json input = json::parse(in_file);

    for(auto inchar : input) {
      $charmap.push_back(inchar);
      $wcharmap.push_back($converter.from_bytes(inchar));
    }
  }

  size_t max_chars() {
    return $wcharmap.size();
  }

  void render(size_t start_char, bool fill) {
    dbc::check(start_char > 0 && start_char < $charmap.size(), format("attempt render from bad start {}", start_char));
    size_t next_char = start_char;

    for(size_t y = 0; y < height; ++y) {
      for(size_t x = 0; x < width; ++x) {
        if(!fill) {
          next_char++;
        }

        // just get out of here, nothing more to render
        if(next_char >= $charmap.size()) return;

        $grid[y][x] = {
          .cm_index = next_char,
          .as_string = $charmap[next_char],
          .as_wstring = $wcharmap[next_char]
        };
      }
    }
  }

  size_t charmap_index(size_t x, size_t y) {
    FontGridCell &cell = $grid[y][x];
    return cell.cm_index;
  }

  string& as_string(size_t x, size_t y) {
    return $grid[y][x].as_string;
  }

  wstring& as_wstring(size_t x, size_t y) {
    return $grid[y][x].as_wstring;
  }

  wchar_t as_wchar(size_t cm_index) {
    return $wcharmap[cm_index][0];
  }


  unsigned int page_size() {
    return width * height;
  }
};

struct WhatTheColor {
  int h;
  int s;
  int v;
  Component h_slider;
  Component s_slider;
  Component v_slider;
};

class GUI {
  public:
  Panel $font_view;
  Panel $status_ui;
  Canvas $canvas;
  SFMLRender $renderer;
  FontGrid $font_grid;
  size_t $start_char = 1;
  size_t $fill_char = 1;
  WhatTheColor $fg_color;
  WhatTheColor $bg_color;
  Component $fg_settings;
  Component $bg_settings;

  GUI(string font_list) :
    $font_view(GAME_MAP_PIXEL_POS, 0, GRID_SIZE.x, GRID_SIZE.y, true),
    $status_ui(0, 0, STATUS_UI_WIDTH, STATUS_UI_HEIGHT),
    $font_grid(font_list, GRID_SIZE.x, GRID_SIZE.y),
    $fg_color{.h=20, .s=50, .v=20},
    $bg_color{.h=100, .s=100, .v=100}
  {
    resize_fonts(DEFAULT_FONT_SIZE);
    render_grid($start_char, false);
  }

  void render_grid(size_t start_char, bool fill) {
    $font_grid.render(start_char, fill);
  }

  void resize_fonts(int new_size) {
    $renderer.resize_grid(new_size, $font_view);
    // set canvas to best size
    $canvas = Canvas($font_view.width * 2, $font_view.height * 4);
  }

  void draw_font_grid() {
    for(size_t y = 0; y < $font_grid.height; y++) {
      for(size_t x = 0; x < $font_grid.width; x++) {
        $canvas.DrawText(x * 2, y * 4, $font_grid.as_string(x, y), [&](auto &pixel) {
          pixel.foreground_color = Color::HSV($fg_color.h, $fg_color.s, $fg_color.v);
          pixel.background_color = Color::HSV($bg_color.h, $bg_color.s, $bg_color.v);
        });
      }
    }
  }

  void create_renderer() {
    $renderer.init_terminal();

    $font_view.set_renderer(Renderer([&]{
        draw_font_grid();
        return canvas($canvas);
    }));

    $fg_color.h_slider = Slider("FG H:", &$fg_color.h, 0, 255, 1);
    $fg_color.s_slider = Slider("FG S:", &$fg_color.s, 0, 255, 1);
    $fg_color.v_slider = Slider("FG V:", &$fg_color.v, 0, 255, 1);
    $bg_color.h_slider = Slider("BG H:", &$bg_color.h, 0, 255, 1);
    $bg_color.s_slider = Slider("BG S:", &$bg_color.s, 0, 255, 1);
    $bg_color.v_slider = Slider("BG V:", &$bg_color.v, 0, 255, 1);

    $status_ui.set_renderer(Renderer([&]{
        wchar_t selected_char = $font_grid.as_wchar($fill_char);

        return hbox({
          hflow(
              vbox(
                text(format("\\u{:x} {} IDX: {}, MIN: {}, MAX: {}", int(selected_char), int(selected_char), $fill_char, WCHAR_MIN, WCHAR_MAX)) | border,
                separator(),
                text(format("FG H: {}, S: {}, V: {}",
                    $fg_color.h, $fg_color.s, $fg_color.v)) | border,
                $fg_color.h_slider->Render(),
                separator(),
                $fg_color.s_slider->Render(),
                separator(),
                $fg_color.v_slider->Render(),
                separator(),
                text(format("BG H: {}, S: {}, V: {}",
                    $bg_color.h, $bg_color.s, $bg_color.v)) | border,
                $bg_color.h_slider->Render(),
                separator(),
                $bg_color.s_slider->Render(),
                separator(),
                $bg_color.v_slider->Render()
              ) | flex_grow
          ),
          separator(),
          hbox(),
      });
    }));

    $status_ui.add($fg_color.h_slider);
    $status_ui.add($fg_color.s_slider);
    $status_ui.add($fg_color.v_slider);
    $status_ui.add($bg_color.h_slider);
    $status_ui.add($bg_color.s_slider);
    $status_ui.add($bg_color.v_slider);
  }

  void shutdown() {
    $renderer.close();
  }

  void select_cell(Point pos) {
    $fill_char = $font_grid.charmap_index(pos.x, pos.y);
    render_grid($fill_char, true);
  }

  void deselect_cell() {
    render_grid($start_char, false);
  }

  bool handle_ui_events() {
    bool event_happened;
    using KB = sf::Keyboard;
    sf::Event event;
    Point pos;
    int font_size = $renderer.font_size();
    int page_size = $font_grid.page_size();

    while($renderer.poll_event(event)) {
      if(event.type == sf::Event::Closed) {
        shutdown();
        return true;
      } else if(event.type ==  sf::Event::KeyPressed) {
        if(KB::isKeyPressed(KB::Up)) {
          $start_char = std::max(1, int($start_char) - page_size);
          render_grid($start_char, false);
          event_happened = true;
          $renderer.clear_cache();
        } else if(event.key.code == KB::C && event.key.control) {
          wchar_t selected_char = $font_grid.as_wchar($fill_char);
          string out = format("\\u{:x}", int(selected_char));
          println("COPIED {}", out);
          sf::Clipboard::setString(out);
        } else if(KB::isKeyPressed(KB::Down)) {
          $start_char = std::min($font_grid.max_chars() - page_size, $start_char + page_size);
          render_grid($start_char, false);
          $renderer.clear_cache();
        } else if(KB::isKeyPressed(KB::Tab)) {
          $status_ui.key_press(Event::Tab);
        } else if(KB::isKeyPressed(KB::Tab)) {
          $status_ui.key_press(Event::Return);
        } else if(KB::isKeyPressed(KB::Equal)) {
          resize_fonts(font_size + 10);
        } else if(KB::isKeyPressed(KB::Hyphen)) {
          resize_fonts(font_size - 10);
          event_happened = true;
        }
      } else if($renderer.mouse_position($font_view, pos)) {
        if(sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
          select_cell(pos);
          event_happened = true;
        } else if(sf::Mouse::isButtonPressed(sf::Mouse::Right)) {
          deselect_cell();
        }
      } else if($renderer.mouse_position($status_ui, pos)) {
        if(sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
          $status_ui.mouse_click(Mouse::Button::Left, pos);
        } else if(event.type == sf::Event::MouseMoved) {
          $status_ui.mouse_release(Mouse::Button::Left, pos);
        }
      }
    }

    return event_happened;
  }

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

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

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

    $renderer.display();
  }
};

int main(int argc, char *argv[]) {
  try {
    dbc::check(argc == 2, "USAGE: designer fontlist.json");

    string font_list{argv[1]};

    GUI gui(font_list);

    gui.create_renderer();

    do {
      gui.render_scene();

      if(gui.handle_ui_events()) {
      }

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

    return 0;
  } catch(const dbc::Error &e) {
    println("ERROR: {}", e.message);
  }
}
