#include "../ui-base.hpp"

#include "controller.cpp"
#include "userinterface-general.cpp"
#include "userinterface-system.cpp"
#include "userinterface-emulationspeed.cpp"
#include "userinterface-states.cpp"
#include "userinterface-videosettings.cpp"

void MappedInput::bind() {
  lstring part;
  part.split("+", name);

  modifier = InputModifier::None;
  for(unsigned i = 0; i < part.size(); i++) {
    if(part[i] == "Shift") modifier |= InputModifier::Shift;
    if(part[i] == "Control") modifier |= InputModifier::Control;
    if(part[i] == "Alt") modifier |= InputModifier::Alt;
    if(part[i] == "Super") modifier |= InputModifier::Super;
  }

  string temp = part[part.size() - 1];
  part.split(".", temp);
  scancode = Scancode::decode(part[0]);
  specifier = InputSpecifier::None;
  if(part[1] == "Up") specifier = InputSpecifier::Up;
  if(part[1] == "Down") specifier = InputSpecifier::Down;
  if(part[1] == "Left") specifier = InputSpecifier::Left;
  if(part[1] == "Right") specifier = InputSpecifier::Right;
  if(part[1] == "Lo") specifier = InputSpecifier::Lo;
  if(part[1] == "Hi") specifier = InputSpecifier::Hi;
  if(part[1] == "Trigger") specifier = InputSpecifier::Trigger;

  //re-encode name, in case previous name was invalid
  name = "";
  if(modifier & InputModifier::Shift) name << "Shift+";
  if(modifier & InputModifier::Control) name << "Control+";
  if(modifier & InputModifier::Alt) name << "Alt+";
  if(modifier & InputModifier::Super) name << "Super+";
  name << Scancode::encode(scancode);
  if(specifier == InputSpecifier::Up) name << ".Up";
  if(specifier == InputSpecifier::Down) name << ".Down";
  if(specifier == InputSpecifier::Left) name << ".Left";
  if(specifier == InputSpecifier::Right) name << ".Right";
  if(specifier == InputSpecifier::Lo) name << ".Lo";
  if(specifier == InputSpecifier::Hi) name << ".Hi";
  if(specifier == InputSpecifier::Trigger) name << ".Trigger";
}

void MappedInput::cache() {
  cachedState = state;
}

MappedInput::MappedInput(const char *label_, const char *configName) : parent(0), label(label_) {
  specifier = InputSpecifier::None;
  state = 0;
  previousState = 0;
  cachedState = 0;
  config().attach(name = "None", configName);
}

//

void DigitalInput::poll() {
  previousState = state;
  if(modifier == mapper().modifier || !config().input.modifierEnable) {
    if(specifier == InputSpecifier::None) {
      state = mapper().state(scancode);
    } else if(specifier == InputSpecifier::Up) {
      state = (bool)(mapper().state(scancode) & Joypad::HatUp);
    } else if(specifier == InputSpecifier::Down) {
      state = (bool)(mapper().state(scancode) & Joypad::HatDown);
    } else if(specifier == InputSpecifier::Left) {
      state = (bool)(mapper().state(scancode) & Joypad::HatLeft);
    } else if(specifier == InputSpecifier::Right) {
      state = (bool)(mapper().state(scancode) & Joypad::HatRight);
    } else if(specifier == InputSpecifier::Lo) {
      state = mapper().state(scancode) < -16384;
    } else if(specifier == InputSpecifier::Hi) {
      state = mapper().state(scancode) > +16384;
    } else if(specifier == InputSpecifier::Trigger) {
      state = mapper().state(scancode) < 0;
    }
  } else {
    state = 0;
  }
}

bool DigitalInput::isPressed() const { return state; }
bool DigitalInput::wasPressed() const { return previousState; }

DigitalInput::DigitalInput(const char *label, const char *configName) : MappedInput(label, configName) {
}

//

void AnalogInput::poll() {
  previousState = state;
  if(Mouse::isAnyAxis(scancode)) {
    if(input.acquired()) {
      state = mapper().state(scancode);
    } else {
      state = 0;
    }
  } else if(Joypad::isAnyAxis(scancode)) {
    state = mapper().state(scancode) / 8192;
  }
}

AnalogInput::AnalogInput(const char *label, const char *configName) : MappedInput(label, configName) {
}

//

void HotkeyInput::poll() {
  DigitalInput::poll();
  if(mainWindow->isActive() && state != previousState) {
    state ? pressed() : released();
  }
}

HotkeyInput::HotkeyInput(const char *label, const char *configName) : DigitalInput(label, configName) {
}

//

void InputGroup::attach(MappedInput *input) {
  input->parent = this;
  append(input);
}

void InputGroup::bind() {
  for(unsigned i = 0; i < size(); i++) {
    (*this)[i]->bind();
  }
}

void InputGroup::poll() {
  for(unsigned i = 0; i < size(); i++) {
    (*this)[i]->poll();
  }
}

void InputGroup::cache() {
  for(unsigned i = 0; i < size(); i++) {
    (*this)[i]->cache();
  }
}

void InputGroup::flushCache() {
  for(unsigned i = 0; i < size(); i++) {
    MappedInput &input = *((*this)[i]);
    input.cachedState = 0;
  }
}

InputGroup::InputGroup(unsigned category_, const char *label_) : category(category_), label(label_) {
  mapper().append(this);
}

//

InputMapper& mapper() {
  static InputMapper mapper;
  return mapper;
}

void InputMapper::calibrate() {
  calibrated = true;
  audio.clear();
  QMessageBox::information(settingsWindow, "Joypad Calibration",
    "Joypads must be calibrated prior to mapping. Please ensure that "
    "all axes and analog buttons are not pressed or moved in any specific "
    "direction, and then press ok."
  );

  poll();
  for(unsigned i = 0; i < Joypad::Count; i++) {
    for(unsigned axis = 0; axis < Joypad::Axes; axis++) {
      int16_t n = state(joypad(i).axis(axis));
      isTrigger[i][axis] = n < -16384 || n > +16384;
    }
  }
}

void InputMapper::bind() {
  for(unsigned i = 0; i < size(); i++) {
    (*this)[i]->bind();
  }
}

void InputMapper::poll() {
  activeState = !activeState;
  input.poll(stateTable[activeState]);

  modifier = 0;
  for(unsigned i = 0; i < Keyboard::Count; i++) {
    if(state(keyboard(i)[Keyboard::Shift])) modifier |= InputModifier::Shift;
    if(state(keyboard(i)[Keyboard::Control])) modifier |= InputModifier::Control;
    if(state(keyboard(i)[Keyboard::Alt])) modifier |= InputModifier::Alt;
    if(state(keyboard(i)[Keyboard::Super])) modifier |= InputModifier::Super;
  }

  for(unsigned i = 0; i < size(); i++) {
    (*this)[i]->poll();
  }

  for(unsigned i = 0; i < Scancode::Limit; i++) {
    if(state(i) != previousState(i)) {
      utility.inputEvent(i);
      inputSettingsWindow->inputEvent(i);
    }
  }
}

void InputMapper::cache() {
  if(config().input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput && !mainWindow->isActive()) {
    for(unsigned i = 0; i < size(); i++) {
      InputGroup &group = *((*this)[i]);
      group.flushCache();
    }
  } else {
    for(unsigned i = 0; i < size(); i++) {
      InputGroup &group = *((*this)[i]);
      if(group.category == InputCategory::Port1 || group.category == InputCategory::Port2) {
        group.cache();
      }
    }
  }
}

int16_t InputMapper::status(bool port, SNES::Input::Device device, unsigned index, unsigned id) {
  int16_t result = 0;

  if(port == InputCategory::Port1 && port1) result = port1->status(index, id);
  if(port == InputCategory::Port2 && port2) result = port2->status(index, id);

  if(movie.state == Movie::Playback) {
    result = movie.read();
  } else if(movie.state == Movie::Record) {
    movie.write(result);
  }

  return result;
}

string InputMapper::modifierString() const {
  string name;
  if(modifier & InputModifier::Shift) name << "Shift+";
  if(modifier & InputModifier::Control) name << "Control+";
  if(modifier & InputModifier::Alt) name << "Alt+";
  if(modifier & InputModifier::Super) name << "Super+";
  return name;
}

int16_t InputMapper::state(uint16_t scancode) const { return stateTable[activeState][scancode]; }
int16_t InputMapper::previousState(uint16_t scancode) const { return stateTable[!activeState][scancode]; }
unsigned InputMapper::distance(uint16_t scancode) const { return abs(state(scancode) - previousState(scancode)); }

InputMapper::InputMapper() : port1(0), port2(0) {
  calibrated = false;
  for(unsigned i = 0; i < Joypad::Count; i++) {
    for(unsigned axis = 0; axis < Joypad::Axes; axis++) {
      isTrigger[i][axis] = false;
    }
  }

  activeState = 0;
  for(unsigned i = 0; i < Scancode::Limit; i++) {
    stateTable[0][i] = stateTable[1][i] = 0;
  }
}
