#include "event_router.hpp"
#include "dbc.hpp"
#include "events.hpp"

namespace gui {
  namespace routing {
    using enum Event;
    using enum State;

    gui::Event Router::process_event(std::optional<sf::Event> ev) {
      $next_event = gui::Event::TICK;

      if(ev->is<sf::Event::Closed>()) {
        return gui::Event::QUIT;
      }

      if(const auto* mouse = ev->getIf<sf::Event::MouseButtonPressed>()) {
        if(mouse->button == sf::Mouse::Button::Left) {
           position = mouse->position;
           event(MOUSE_DOWN);
        }
      } else if(const auto* mouse = ev->getIf<sf::Event::MouseButtonReleased>()) {
        if(mouse->button == sf::Mouse::Button::Left) {
           position = mouse->position;
           event(MOUSE_UP);
        }
      } else if(const auto* mouse = ev->getIf<sf::Event::MouseMoved>()) {
        position = mouse->position;
        event(MOUSE_MOVE);
      }

      if(const auto* key = ev->getIf<sf::Event::KeyPressed>()) {
        scancode = key->scancode;
        event(KEY_PRESS);
      }

      return $next_event;
    }

    void Router::event(Event ev) {
      switch($state) {
        FSM_STATE(State, START, ev);
        FSM_STATE(State, IDLE, ev);
        FSM_STATE(State, MOUSE_ACTIVE, ev);
        FSM_STATE(State, MOUSE_MOVING, ev);
        FSM_STATE(State, MOUSE_DRAGGING, ev);
      }
    }

    void Router::START(Event ) {
      state(State::IDLE);
    }

    void Router::IDLE(Event ev) {
      switch(ev) {
        case MOUSE_DOWN:
          move_count=0;
          set_event(gui::Event::TICK);
          state(State::MOUSE_ACTIVE);
          break;
        case MOUSE_MOVE:
          set_event(gui::Event::MOUSE_MOVE);
          break;
        case KEY_PRESS:
          set_event(gui::Event::KEY_PRESS);
          break;
        default:
          dbc::sentinel("invalid event");
      }
    }

    void Router::MOUSE_ACTIVE(Event ev) {
      switch(ev) {
        case MOUSE_UP:
          set_event(gui::Event::MOUSE_CLICK);
          state(State::IDLE);
          break;
        case MOUSE_MOVE:
          move_count++;
          set_event(gui::Event::MOUSE_DRAG);
          state(State::MOUSE_MOVING);
          break;
        case KEY_PRESS:
          set_event(gui::Event::KEY_PRESS);
          state(State::IDLE);
          break;
        default:
          dbc::sentinel("invalid event");
      }
    }

    void Router::MOUSE_MOVING(Event ev) {
      switch(ev) {
        case MOUSE_UP: {
          dbc::check(move_count < $drag_tolerance, "mouse up but not in dragging state");
          set_event(gui::Event::MOUSE_CLICK);
          state(State::IDLE);
         } break;
        case MOUSE_MOVE:
          move_count++;

          if(move_count < $drag_tolerance) {
            set_event(gui::Event::MOUSE_DRAG);
          } else {
            set_event(gui::Event::MOUSE_DRAG_START);
            state(State::MOUSE_DRAGGING);
          }
          break;
        case KEY_PRESS:
          set_event(gui::Event::KEY_PRESS);
          break;
        default:
          dbc::sentinel("invalid event");
      }
    }


    void Router::MOUSE_DRAGGING(Event ev) {
      switch(ev) {
        case MOUSE_UP:
          set_event(gui::Event::MOUSE_DROP);
          state(State::IDLE);
          break;
        case MOUSE_MOVE:
          move_count++;
          set_event(gui::Event::MOUSE_DRAG);
          break;
        case KEY_PRESS:
          set_event(gui::Event::KEY_PRESS);
          break;
        default:
          dbc::sentinel("invalid events");
      }
    }
  }
}