#include "device.cpp"
#include "userinterface.cpp"

//=========
//InputCode
//=========

InputCode::type_t InputCode::type(uint16_t code) {
  for(unsigned i = 0; i < keyboard<>::count; i++) {
    unsigned index = keyboard<>::index(i, keyboard<>::none);
    if(code >= index && code < index + keyboard<>::length) return KeyboardButton;
  }

  for(unsigned i = 0; i < mouse<>::count; i++) {
    unsigned index = mouse<>::index(i, mouse<>::none);
    if(code == index + mouse<>::x) return MouseAxis;
    if(code == index + mouse<>::y) return MouseAxis;
    if(code == index + mouse<>::z) return MouseAxis;

    index = mouse<>::index(i, mouse<>::button);
    if(code >= index && code < index + mouse<>::buttons) return MouseButton;
  }

  for(unsigned i = 0; i < joypad<>::count; i++) {
    unsigned index;

    index = joypad<>::index(i, joypad<>::hat);
    if(code >= index && code < index + joypad<>::hats) return JoypadHat;

    index = joypad<>::index(i, joypad<>::axis);
    if(code >= index && code < index + joypad<>::axes) return JoypadAxis;

    index = joypad<>::index(i, joypad<>::button);
    if(code >= index && code < index + joypad<>::buttons) return JoypadButton;
  }

  return Unknown;
}

InputCode::axistype_t InputCode::axisType(uint16_t code) {
  for(unsigned i = 0; i < joypad<>::count; i++) {
    unsigned index = joypad<>::index(i, joypad<>::axis);
    if(code >= index && code < index + joypad<>::axes) {
      return (InputCode::axistype_t)inputManager.axisType(i, code - index);
    }
  }
  return InvalidAxis;
}

//finds what mouse# is associated with code, returns -1 if not a mouse code
int InputCode::mouseNumber(uint16_t code) {
  for(unsigned i = 0; i < mouse<>::count; i++) {
    unsigned index = mouse<>::index(i, mouse<>::none);
    if(code >= index && code < index + mouse<>::length) return i;
  }
  return -1;
}

//finds what joypad# is associated with code, returns -1 if not a joypad code
int InputCode::joypadNumber(uint16_t code) {
  for(unsigned i = 0; i < joypad<>::count; i++) {
    unsigned index = joypad<>::index(i, joypad<>::none);
    if(code >= index && code < index + joypad<>::length) return i;
  }
  return -1;
}

//============
//InputManager
//============

void InputManager::bind() {
  inputPool.bind();
  inputUiPool.bind();
}

void InputManager::poll() {
  if(config.input.focusPolicy == Configuration::Input::FocusPolicyIgnoreInput
  && winMain->window->isActiveWindow() == false) {
    inputPool.clear();
  } else {
    inputPool.poll(stateTable[activeState]);
  }
}

void InputManager::clear() {
  inputPool.clear();
}

void InputManager::flush() {
  for(unsigned i = 0; i < nall::input_limit; i++) {
    stateTable[0][i] = 0;
    stateTable[1][i] = 0;
  }
}

int16_t InputManager::state(uint16_t code) const {
  return stateTable[ activeState][code];
}

int16_t InputManager::lastState(uint16_t code) const {
  return stateTable[!activeState][code];
}

int16_t InputManager::getStatus(unsigned deviceid, unsigned id) const {
  InputDevice *device = inputPool.find((SNES::Input::DeviceID)deviceid);
  if(device) return device->state(id);
  return 0;
}

void InputManager::refresh() {
  bool last = activeState;
  activeState = !activeState;
  bool next = activeState;

  input.poll(stateTable[next]);
  for(unsigned i = 0; i < nall::input_limit; i++) {
    //alert via callback whenever input state changes for any ID ...
    if(onInput && stateTable[last][i] != stateTable[next][i]) onInput(i);
  }
}

void InputManager::calibrate(unsigned joy) {
  unsigned index = joypad<>::index(joy, joypad<>::none);

  for(unsigned axis = 0; axis < joypad<>::axes; axis++) {
    int16_t value = state(index + joypad<>::axis + axis);
    pad[joy].axis[axis] = (value >= -16384 && value <= +16384) ? InputCode::Stick : InputCode::Trigger;
  }

  pad[joy].calibrated = true;
}

bool InputManager::calibrated(unsigned joy) const {
  return pad[joy].calibrated;
}

InputCode::axistype_t InputManager::axisType(unsigned joy, unsigned axis) const {
  return pad[joy].axis[axis];
}

InputManager::InputManager() {
  activeState = 0;
  flush();

  for(unsigned i = 0; i < joypad<>::count; i++) {
    pad[i].calibrated = false;
    for(unsigned n = 0; n < joypad<>::axes; n++) pad[i].axis[n] = InputCode::InvalidAxis;
  }
}

//===========
//InputObject
//===========

void InputObject::bind() {
  lstring part;
  part.split("::", id);

  code = nall::input_find((const char*)part[0]);
  codetype = InputCode::type(code);

  modifier = None;
  if(part.size() > 1) {
    if(part[1] == "up"     ) modifier = Up;
    if(part[1] == "down"   ) modifier = Down;
    if(part[1] == "left"   ) modifier = Left;
    if(part[1] == "right"  ) modifier = Right;
    if(part[1] == "lo"     ) modifier = Lo;
    if(part[1] == "hi"     ) modifier = Hi;
    if(part[1] == "trigger") modifier = Trigger;
  }

  if(code == nall::input_none) id = "none";
}

void InputObject::bind(uint16_t newCode) {
  code = newCode;
  codetype = InputCode::type(code);

  id = nall::input_find(newCode);
  modifier = None;

  if(type == Button && codetype == InputCode::JoypadHat) {
    switch(inputManager.state(code)) {
      case joypad<>::hat_up:    id << "::up";    modifier = Up;    break;
      case joypad<>::hat_down:  id << "::down";  modifier = Down;  break;
      case joypad<>::hat_left:  id << "::left";  modifier = Left;  break;
      case joypad<>::hat_right: id << "::right"; modifier = Right; break;
    }
  } else if(type == Button && codetype == InputCode::JoypadAxis) {
    InputCode::axistype_t type = InputCode::axisType(code);
    int16_t state = inputManager.state(code);

    if(type == InputCode::Stick) {
      if(state < 0) {
        id << "::lo";
        modifier = Lo;
      } else {
        id << "::hi";
        modifier = Hi;
      }
    } else if(type == InputCode::Trigger) {
      id << "::trigger";
      modifier = Trigger;
    }
  }
}

void InputObject::poll(int16_t newState) {
  if(type == Button && codetype == InputCode::JoypadHat) {
    //map 4-way hat input to button state
    state = (modifier == Up    && (newState & joypad<>::hat_up   ))
         || (modifier == Down  && (newState & joypad<>::hat_down ))
         || (modifier == Left  && (newState & joypad<>::hat_left ))
         || (modifier == Right && (newState & joypad<>::hat_right));
  } else if(type == Button && codetype == InputCode::JoypadAxis) {
    //convert analog input to button state
    state = (modifier == Lo && newState < -16384)
         || (modifier == Hi && newState > +16384)
         || (modifier == Trigger && newState < 0);
  } else if(codetype == InputCode::MouseAxis && !input.acquired()) {
    //mouse must be acquired (locked to window) to move axes
    state = 0;
  } else if(0 && codetype == InputCode::MouseButton && !input.acquired()) {
    //same for buttons
    //note: disabled for now ... requiring exclusive access makes it much more difficult
    //to utilize mouse buttons for eg SNES joypad input.
    state = 0;
  } else if(codetype == InputCode::JoypadAxis) {
    //joypad axis range = -32768 to +32767, scale to -8 to +7 to roughly match mouse delta
    //todo: scale mouse up instead of joypad down? (would require mouse DPI / resolution)
    state = newState / 4096;
  } else {
    state = newState;
  }
}

InputObject::InputObject(InputObject::type_t t, const char *n, string &s) : parent(0), type(t), name(n), id(s) {
}

//==========
//InputGroup
//==========

void InputGroup::attach(InputObject &object) {
  list.add(&object);
  object.parent = this;
}

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

void InputGroup::clear() {
  for(unsigned i = 0; i < list.size(); i++) list[i]->state = 0;
}

void InputGroup::poll(const int16_t *table) {
  for(unsigned i = 0; i < list.size(); i++) list[i]->poll(table[list[i]->code]);
}

int16_t InputGroup::state(unsigned index) const {
  if(index < list.size()) return list[index]->state;
  return 0;
}

InputGroup::InputGroup(const char *n) : list(*this), name(n) {
}
