///|
struct KeyboardInputManager {
  events : Map[String, Array[(Any) -> Unit]]
  event_touch_start : String
  event_touch_move : String
  event_touch_end : String
}

///|
extern "js" fn is_ie() -> Bool =
  #|() => window.navigator.msPointerEnabled

///|
pub fn KeyboardInputManager::new() -> KeyboardInputManager {
  let self = if is_ie() {
    {
      events: {},
      event_touch_start: "MSPointerDown",
      event_touch_move: "MSPointerMove",
      event_touch_end: "MSPointerUp",
    }
  } else {
    {
      events: {},
      event_touch_start: "touchstart",
      event_touch_move: "touchmove",
      event_touch_end: "touchend",
    }
  }
  self.listen()
  self
}

///|
pub fn KeyboardInputManager::on(
  self : KeyboardInputManager,
  event : String,
  callback : (Any) -> Unit,
) -> Unit {
  let handlers = self.events
    .get(event)
    .unwrap_or_else(() => {
      let handlers = []
      self.events[event] = handlers
      handlers
    })
  handlers.push(callback)
}

///|
pub fn KeyboardInputManager::emit(
  self : KeyboardInputManager,
  event : String,
  data? : Any,
) -> Unit {
  if self.events.get(event) is Some(handlers) {
    for handler in handlers {
      if data is Some(data) {
        handler(data)
      } else {
        handler(Any::undefined())
      }
    }
  }
}

///|
#external
pub type Event

///|
pub extern "js" fn Event::prevent_default(self : Event) -> Unit =
  #|self => self.preventDefault()

///|
extern "js" fn install_keydown_listener(
  move_ : (Int) -> Unit,
  restart : (Event) -> Unit,
) -> Unit =
  #|(move, restart) => {
  #|  let map = {
  #|    38: 0, // Up
  #|    39: 1, // Right
  #|    40: 2, // Down
  #|    37: 3, // Left
  #|    75: 0, // Vim up
  #|    76: 1, // Vim right
  #|    74: 2, // Vim down
  #|    72: 3, // Vim left
  #|    87: 0, // W
  #|    68: 1, // D
  #|    83: 2, // S
  #|    65: 3  // A
  #|  };
  #|  document.addEventListener("keydown", function (event) {
  #|    var modifiers = event.altKey || event.ctrlKey || event.metaKey ||
  #|                    event.shiftKey;
  #|    var mapped    = map[event.which];
  #|
  #|    if (!modifiers) {
  #|      if (mapped !== undefined) {
  #|        event.preventDefault();
  #|        move(mapped);
  #|      }
  #|    }
  #|
  #|    // R key restarts the game
  #|    if (!modifiers && event.which === 82) {
  #|      restart(event);
  #|    }
  #|  });
  #|}

///|
extern "js" fn KeyboardInputManager::install_touch_listeners(
  self : KeyboardInputManager,
  move_ : (Int) -> Unit,
) -> Unit =
  #|(self, move) => {
  #|  var touchStartClientX, touchStartClientY;
  #|  var gameContainer = document.getElementsByClassName("container")[0];
  #|
  #|  gameContainer.addEventListener(self.event_touch_start, function (event) {
  #|    if ((!window.navigator.msPointerEnabled && event.touches.length > 1) ||
  #|        event.targetTouches.length > 1) {
  #|      return; // Ignore if touching with more than 1 finger
  #|    }
  #|
  #|    if (window.navigator.msPointerEnabled) {
  #|      touchStartClientX = event.pageX;
  #|      touchStartClientY = event.pageY;
  #|    } else {
  #|      touchStartClientX = event.touches[0].clientX;
  #|      touchStartClientY = event.touches[0].clientY;
  #|    }
  #|
  #|    event.preventDefault();
  #|  });
  #|
  #|  gameContainer.addEventListener(self.event_touch_move, function (event) {
  #|    event.preventDefault();
  #|  });
  #|
  #|  gameContainer.addEventListener(self.event_touch_end, function (event) {
  #|    if ((!window.navigator.msPointerEnabled && event.touches.length > 0) ||
  #|        event.targetTouches.length > 0) {
  #|      return; // Ignore if still touching with one or more fingers
  #|    }
  #|
  #|    var touchEndClientX, touchEndClientY;
  #|
  #|    if (window.navigator.msPointerEnabled) {
  #|      touchEndClientX = event.pageX;
  #|      touchEndClientY = event.pageY;
  #|    } else {
  #|      touchEndClientX = event.changedTouches[0].clientX;
  #|      touchEndClientY = event.changedTouches[0].clientY;
  #|    }
  #|
  #|    var dx = touchEndClientX - touchStartClientX;
  #|    var absDx = Math.abs(dx);
  #|
  #|    var dy = touchEndClientY - touchStartClientY;
  #|    var absDy = Math.abs(dy);
  #|
  #|    if (Math.max(absDx, absDy) > 10) {
  #|      // (right : left) : (down : up)
  #|      move(absDx > absDy ? (dx > 0 ? 1 : 3) : (dy > 0 ? 2 : 0));
  #|    }
  #|  });
  #|}

///|
pub fn KeyboardInputManager::listen(self : KeyboardInputManager) -> Unit {
  install_keydown_listener(data => self.emit("move", data=Any::from(data)), e => {
    self.restart_with_confirmation(e)
  })
  self.bind_button_press(".retry-button", e => self.restart(Any::to(e)))
  self.bind_button_press(".restart-button", e => {
    self.restart_with_confirmation(Any::to(e))
  })
  self.bind_button_press(".undo-button", e => {
    self.undo_with_confirmation(Any::to(e))
  })
  self.bind_button_press(".keep-playing-button", e => {
    self.keep_playing(Any::to(e))
  })
  self.bind_button_press(".undo-move-button", e => self.undo_move(Any::to(e)))
  self.bind_button_press(".confirm-button", e => self.restart(Any::to(e)))
  self.bind_button_press(".cancel-button", e => self.keep_playing(Any::to(e)))
  self.install_touch_listeners(data => self.emit("move", data=Any::from(data)))
}

///|
fn KeyboardInputManager::undo_move(
  self : KeyboardInputManager,
  e : Event,
) -> Unit {
  e.prevent_default()
  self.emit("undoMove")
}

///|
fn KeyboardInputManager::restart(
  self : KeyboardInputManager,
  e : Event,
) -> Unit {
  e.prevent_default()
  self.emit("restart")
}

///|
fn KeyboardInputManager::restart_with_confirmation(
  self : KeyboardInputManager,
  e : Event,
) -> Unit {
  e.prevent_default()
  self.emit("restartWithConfirmation")
}

///|
fn KeyboardInputManager::undo_with_confirmation(
  self : KeyboardInputManager,
  e : Event,
) -> Unit {
  e.prevent_default()
  self.emit("undoWithConfirmation")
}

///|
fn KeyboardInputManager::keep_playing(
  self : KeyboardInputManager,
  e : Event,
) -> Unit {
  e.prevent_default()
  self.emit("keepPlaying")
}

///|
extern "js" fn KeyboardInputManager::bind_button_press(
  self : KeyboardInputManager,
  selector : String,
  fun : (Any) -> Unit,
) -> Unit =
  #|(self, selector, fun) => {
  #|  var button = document.querySelector(selector);
  #|  button.addEventListener("click", fun);
  #|  button.addEventListener(self.event_touch_start, fun);
  #|}