#include "guecs/sfml/backend.hpp"
#include "guecs/sfml/components.hpp"
#include "guecs/ui.hpp"
#include <fmt/xchar.h>
#include <deque>

constexpr const int WINDOW_WIDTH=300;
constexpr const int WINDOW_HEIGHT=400;
constexpr const int FRAME_LIMIT=60;
constexpr const bool VSYNC=true;

using std::string, std::wstring;

enum class Event {
  NUMBER, ADD, SUB, MUL,
  DIV, CLR, NEG, EQ, PUSH, DUP, DEL
};

const std::unordered_map<string, wstring> LABELS {
  {"stack", L""}, {"readout", L""}, {"clear", L"CLR"},
    {"btn0", L"0"}, {"btn1", L"1"},
    {"btn2", L"2"}, {"btn3", L"3"}, {"btn4", L"4"},
    {"btn5", L"5"}, {"btn6", L"6"}, {"btn7", L"7"},
    {"btn8", L"8"}, {"btn9", L"9"}, {"mul", L"*"},
    {"sub", L"-"}, {"add", L"+"}, {"neg", L"±"},
    {"div", L"/"}, {"eq", L"="},
    {"push", L"^"}, {"pop", L"v"}, {"del", L"DEL"}};

const std::unordered_map<wchar_t, Event> EVENTS {
  {L'0', Event::NUMBER}, {L'1', Event::NUMBER},
    {L'2', Event::NUMBER}, {L'3', Event::NUMBER},
    {L'4', Event::NUMBER}, {L'5', Event::NUMBER},
    {L'6', Event::NUMBER}, {L'7', Event::NUMBER},
    {L'8', Event::NUMBER}, {L'9', Event::NUMBER},
    {L'*', Event::MUL}, {L'-', Event::SUB},
    {L'+', Event::ADD}, {L'/', Event::DIV},
    {L'C', Event::CLR}, {L'^', Event::PUSH},
    {L'±', Event::NEG}, {L'v', Event::DUP},
    {L'=', Event::EQ}, {L'D', Event::DEL}};


struct Calculator {
  wstring input;
  wstring curstack;
  std::deque<double> stack;
  double temp = 0.0;
  double result = 0.0;

  void display() {
    curstack = L"";
    for(auto num : stack) {
      curstack += fmt::format(L"{} ", num);
    }
  }

  void do_op(Event ev) {
    if(stack.size() < 2) return;
    using enum Event;

    double left = stack.back();
    stack.pop_back();
    double right = stack.back();
    stack.pop_back();

    switch(ev) {
      case MUL:
        temp = left * right;
        break;
      case ADD:
        temp = left + right;
        break;
      case SUB:
        temp = left - right;
        break;
      case DIV:
        temp = left / right;
        break;
      default:
        assert(true && "invalid op passed to op()");
    }

    stack.push_back(temp);
  }

  void event(Event ev, wchar_t op) {
    using enum Event;
    switch(ev) {
      case NUMBER:
        input += op;
        break;
      case MUL: do_op(MUL); break;
      case ADD: do_op(ADD); break;
      case SUB: do_op(SUB); break;
      case DIV: do_op(DIV); break;
      case DEL:
        if(input.size() > 0) {
          input.pop_back();
        }
        break;
      case CLR:
        input = L"";
        stack.clear();
        break;
      case DUP:
        if(stack.size() > 0) {
          input = fmt::format(L"{}", stack.back());
        }
        break;
      case PUSH:
        if(input.size() > 0) {
          temp = std::stof(input);
          input = L"";
          stack.push_back(temp);
        }
        break;
      case NEG:
        if(input.size() > 0) {
          temp = std::stof(input);
          input = fmt::format(L"{}", temp * -1);
        }
        break;
      case EQ:
        if(!stack.empty()) {
          temp = stack.back();
          input = fmt::format(L"{}", temp);
          stack.pop_back();
        }
        break;
    }

    display();
  }
};

struct CalculatorUI {
  guecs::UI $gui;
  Calculator $calc;

  CalculatorUI() {
    $gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    $gui.layout(
        "[*%(400)stack  |_|_|_]"
        "[*%(400)readout|_|_|_]"
        "[push|pop|clear|eq]"
        "[add |sub|mul  |div]"
        "[btn7|btn8|btn9]"
        "[btn4|btn5|btn6]"
        "[btn1|btn2|btn3]"
        "[neg |btn0|del]");
  }

  void init() {
    $gui.set<guecs::Background>($gui.MAIN, {$gui.$parser});

    for(auto& [name, cell] : $gui.cells()) {
      auto id = $gui.entity(name);
      auto& label = LABELS.at(name);

      $gui.set<guecs::Rectangle>(id, {});

      if(name == "readout") {
        $gui.set<guecs::Textual>(id, {L"", 40});
      } else if(name == "stack") {
        $gui.set<guecs::Textual>(id, {L"", 20});
      } else {
        $gui.set<guecs::Effect>(id, {});
        $gui.set<guecs::Label>(id, { label });
        wchar_t op = label[0];
        $gui.set<guecs::Clickable>(id, {
            [&, op](auto, auto) { handle_button(op); }
            });
      }
    }

    $gui.init();
  }

  void render(sf::RenderWindow& window) {
    $gui.render(window);
    // $gui.debug_layout(window);
  }

  void mouse(float x, float y, bool hover) {
    $gui.mouse(x, y, hover);
  }

  void handle_button(wchar_t op) {
    $calc.event(EVENTS.at(op), op);
    $gui.show_text("readout", $calc.input);
    $gui.show_text("stack", $calc.curstack);
  }
};

int main() {
  sfml::Backend backend;
  guecs::init(&backend);

  sf::RenderWindow window(sf::VideoMode({WINDOW_WIDTH, WINDOW_HEIGHT}), "LEL-GUECS Calculator");
  window.setFramerateLimit(FRAME_LIMIT);
  window.setVerticalSyncEnabled(VSYNC);

  CalculatorUI calc;
  calc.init();

  while(window.isOpen()) {
    while (const auto event = window.pollEvent()) {
      if(event->is<sf::Event::Closed>()) {
        window.close();
      }

      if(const auto* mouse = event->getIf<sf::Event::MouseButtonPressed>()) {
        if(mouse->button == sf::Mouse::Button::Left) {
          sf::Vector2f pos = window.mapPixelToCoords(mouse->position);
          calc.mouse(pos.x, pos.y, false);
        }
      }
    }

    calc.render(window);
    window.display();
  }
}
