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

constexpr const int WINDOW_WIDTH=1280;
constexpr const int WINDOW_HEIGHT=720;
constexpr const int FRAME_LIMIT=60;
constexpr const bool VSYNC=true;

using std::string, std::wstring;

enum class Event {
  CLICKER, A_BUTTON
};

struct Shake {
  float scale_factor = 0.05f;
  int frames = 10;
  float ease_rate = 0.1f;
  bool playing = false;
  int current = 0;
  float x=0.0;
  float y=0.0;
  float w=0.0;
  float h=0.0;
  sf::Vector2f initial_scale;

  float ease() {
    float tick = float(frames) / float(current) * ease_rate;
    return (std::sin(tick) + 1.0) / 2.0;
  }

  void init(lel::Cell& cell) {
    x = cell.x;
    y = cell.y;
    w = cell.w;
    h = cell.h;
  }

  void play(guecs::Sprite& sprite) {
    if(!playing) {
      playing = true;
      current = 0;
      initial_scale = sprite.sprite->getScale();
    }
  }

  void render(guecs::Sprite& sprite) {
    current++;

    if(playing && current < frames) {
      float tick = ease();
      sf::Vector2f scale{
        std::lerp(initial_scale.x, initial_scale.x + scale_factor, tick),
          std::lerp(initial_scale.y, initial_scale.y + scale_factor, tick)};

      sprite.sprite->setScale(scale);
    } else {
      playing = false;
      current = 0;
      sprite.sprite->setScale(initial_scale);
    }
  }
};

struct ClickerUI {
  guecs::UI $gui;
  guecs::Entity $clicker;

  ClickerUI() {
    $gui.position(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    $gui.layout(
        "[_|*%(300,400)clicker|_|_|_]"
        "[_|_               |_|_|_]"
        "[_|_               |_|_|_]"
        "[_|_               |_|_|_]"
        "[a1|a2|a3|a4|a5]");
  }

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

    for(auto& [name, cell] : $gui.cells()) {
      auto id = $gui.entity(name);
      if(name != "clicker") {
        $gui.set<guecs::Rectangle>(id, {});
        $gui.set<guecs::Effect>(id, {});
        $gui.set<guecs::Sprite>(id, { "clicker_treat_bone" });
        fmt::println("button dim: {},{}", cell.w, cell.h);
        $gui.set<guecs::Clickable>(id, {
            [&](auto, auto) { handle_button(Event::A_BUTTON); }
            });
      }
    }

    $clicker = $gui.entity("clicker");
    $gui.set<guecs::Sprite>($clicker, {"clicker_the_dog"});
    $gui.set<guecs::Sound>($clicker, {"clicker_bark"});
    $gui.set<guecs::Clickable>($clicker, {
        [&](auto, auto) { handle_button(Event::CLICKER); }
        });

    // custom components need to be initialized manually
    $gui.set_init<Shake>($clicker, {});

    $gui.init();
  }

  void render(sf::RenderWindow& window) {
    auto& shaker = $gui.get<Shake>($clicker);

    if(shaker.playing) {
      auto& sprite = $gui.get<guecs::Sprite>($clicker);
      shaker.render(sprite);
      window.clear();
    }

    $gui.render(window);

    // $gui.debug_layout(window);
  }

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

  void handle_button(Event ev) {
    using enum Event;
    switch(ev) {
      case CLICKER: {
                      auto& shaker = $gui.get<Shake>($clicker);
                      auto& sprite = $gui.get<guecs::Sprite>($clicker);
                      shaker.play(sprite);
                      fmt::println("CLICKER LOVES YOU!");
                    } break;
      case A_BUTTON:
                    fmt::println("a button clicked");
                    break;

      default:
                    assert(false && "invalid event");
    }
  }
};

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

  sf::RenderWindow window(sf::VideoMode({WINDOW_WIDTH, WINDOW_HEIGHT}), "Clicker the Dog");
  window.setFramerateLimit(FRAME_LIMIT);
  window.setVerticalSyncEnabled(VSYNC);

  ClickerUI clicker;
  clicker.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);
          clicker.mouse(pos.x, pos.y, false);
        }
      }
    }

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