namespace Controllers {

void TurboInput::cache() {
  if(state) {
    cachedState = (counter < holdHi ? state : 0);
    if(++counter >= holdHi + holdLo) counter = 0;
  } else {
    cachedState = 0;
    counter = 0;
  }
}

TurboInput::TurboInput(const char *label, const char *configName) :
DigitalInput(label, configName) {
  holdHi = 2;
  holdLo = 2;
  counter = 0;
}

int16_t Gamepad::status(unsigned index, unsigned id) const {
  if(config().input.allowInvalidInput == false) {
    //block up+down and left+right combinations:
    //a real gamepad has a pivot in the D-pad that makes this impossible;
    //some software titles will crash if up+down or left+right are detected
    if(id == (unsigned)SNES::Input::JoypadID::Down && up.cachedState) return 0;
    if(id == (unsigned)SNES::Input::JoypadID::Right && left.cachedState) return 0;
  }

  switch(id) {
    case SNES::Input::JoypadID::Up: return up.cachedState;
    case SNES::Input::JoypadID::Down: return down.cachedState;
    case SNES::Input::JoypadID::Left: return left.cachedState;
    case SNES::Input::JoypadID::Right: return right.cachedState;
    case SNES::Input::JoypadID::A: return a.cachedState | turboA.cachedState;
    case SNES::Input::JoypadID::B: return b.cachedState | turboB.cachedState;
    case SNES::Input::JoypadID::X: return x.cachedState | turboX.cachedState;
    case SNES::Input::JoypadID::Y: return y.cachedState | turboY.cachedState;
    case SNES::Input::JoypadID::L: return l.cachedState | turboL.cachedState;
    case SNES::Input::JoypadID::R: return r.cachedState | turboR.cachedState;
    case SNES::Input::JoypadID::Select: return select.cachedState;
    case SNES::Input::JoypadID::Start: return start.cachedState;
  }
  return 0;
}

Gamepad::Gamepad(unsigned category, const char *label, const char *configName) :
InputGroup(category, label),
up("Up", string() << "input." << configName << ".up"),
down("Down", string() << "input." << configName << ".down"),
left("Left", string() << "input." << configName << ".left"),
right("Right", string() << "input." << configName << ".right"),
b("B", string() << "input." << configName << ".b"),
a("A", string() << "input." << configName << ".a"),
y("Y", string() << "input." << configName << ".y"),
x("X", string() << "input." << configName << ".x"),
l("L", string() << "input." << configName << ".l"),
r("R", string() << "input." << configName << ".r"),
select("Select", string() << "input." << configName << ".select"),
start("Start", string() << "input." << configName << ".start"),
turboB("Turbo B", string() << "input." << configName << ".turboB"),
turboA("Turbo A", string() << "input." << configName << ".turboA"),
turboY("Turbo Y", string() << "input." << configName << ".turboY"),
turboX("Turbo X", string() << "input." << configName << ".turboX"),
turboL("Turbo L", string() << "input." << configName << ".turboL"),
turboR("Turbo R", string() << "input." << configName << ".turboR") {
  attach(&up); attach(&down); attach(&left); attach(&right);
  attach(&b); attach(&a); attach(&y); attach(&x);
  attach(&l); attach(&r); attach(&select); attach(&start);
  attach(&turboB); attach(&turboA); attach(&turboY); attach(&turboX);
  attach(&turboL); attach(&turboR);

  if(this == &gamepad1) {
    up.name = "KB0::Up";
    down.name = "KB0::Down";
    left.name = "KB0::Left";
    right.name = "KB0::Right";
    b.name = "KB0::Z";
    a.name = "KB0::X";
    y.name = "KB0::A";
    x.name = "KB0::S";
    l.name = "KB0::D";
    r.name = "KB0::C";
    select.name = "KB0::Apostrophe";
    start.name = "KB0::Return";
  }
}

//

int16_t Multitap::status(unsigned index, unsigned id) const {
  switch(index & 3) { default:
    case 0: return port1.status(index, id);
    case 1: return port2.status(index, id);
    case 2: return port3.status(index, id);
    case 3: return port4.status(index, id);
  }
}

Multitap::Multitap(Gamepad &port1_, Gamepad &port2_, Gamepad &port3_, Gamepad &port4_) :
InputGroup(InputCategory::Hidden, "Multitap"),
port1(port1_), port2(port2_), port3(port3_), port4(port4_) {
}

//

void AsciiSwitch::poll() {
  DigitalInput::poll();

  //only change state when controller is active
  if(!parent) return;
  if(parent->category == InputCategory::Port1 && mapper().port1 != parent) return;
  if(parent->category == InputCategory::Port2 && mapper().port2 != parent) return;

  if(previousState != state && state) {
    switch(mode) {
      case Off: mode = Turbo; utility.showMessage(string() << label << " set to turbo."); break;
      case Turbo: mode = Auto; utility.showMessage(string() << label << " set to auto."); break;
      case Auto: mode = Off; utility.showMessage(string() << label << " set to off."); break;
    }
  }
}

AsciiSwitch::AsciiSwitch(const char *label, const char *configName) :
DigitalInput(label, configName) {
  mode = Off;
}

void AsciiInput::cache() {
  if(asciiSwitch->mode == AsciiSwitch::Off) {
    cachedState = state;
  } else if(asciiSwitch->mode == AsciiSwitch::Turbo) {
    if(state) {
      cachedState = (counter < holdHi ? state : 0);
      if(++counter >= holdHi + holdLo) counter = 0;
    } else {
      cachedState = 0;
      counter = 0;
    }
  } else if(asciiSwitch->mode == AsciiSwitch::Auto) {
    cachedState = (counter < holdHi);
    if(++counter >= holdHi + holdLo) counter = 0;
  }
}

AsciiInput::AsciiInput(const char *label, const char *configName) :
DigitalInput(label, configName) {
  holdHi = 2;
  holdLo = 2;
  counter = 0;
}

void AsciiSlowMotion::poll() {
  DigitalInput::poll();

  //only change state when controller is active
  if(!parent) return;
  if(parent->category == InputCategory::Port1 && mapper().port1 != parent) return;
  if(parent->category == InputCategory::Port2 && mapper().port2 != parent) return;

  if(previousState != state && state) {
    if(enabled == false) {
      enabled = true;
      utility.showMessage(string() << label << " enabled.");
    } else {
      enabled = false;
      utility.showMessage(string() << label << " disabled.");
    }
  }
}

void AsciiSlowMotion::cache() {
  if(enabled == false) {
    cachedState = 0;
  } else {
    cachedState = counter < holdHi;
    if(++counter >= holdHi + holdLo) counter = 0;
  }
}

AsciiSlowMotion::AsciiSlowMotion(const char *label, const char *configName) :
DigitalInput(label, configName) {
  enabled = false;
  holdHi = 2;
  holdLo = 2;
}

int16_t Asciipad::status(unsigned index, unsigned id) const {
  if(config().input.allowInvalidInput == false) {
    if(id == (unsigned)SNES::Input::JoypadID::Down && up.cachedState) return 0;
    if(id == (unsigned)SNES::Input::JoypadID::Right && left.cachedState) return 0;
  }

  switch(id) {
    case SNES::Input::JoypadID::Up: return up.cachedState;
    case SNES::Input::JoypadID::Down: return down.cachedState;
    case SNES::Input::JoypadID::Left: return left.cachedState;
    case SNES::Input::JoypadID::Right: return right.cachedState;
    case SNES::Input::JoypadID::A: return a.cachedState;
    case SNES::Input::JoypadID::B: return b.cachedState;
    case SNES::Input::JoypadID::X: return x.cachedState;
    case SNES::Input::JoypadID::Y: return y.cachedState;
    case SNES::Input::JoypadID::L: return l.cachedState;
    case SNES::Input::JoypadID::R: return r.cachedState;
    case SNES::Input::JoypadID::Select: return select.cachedState;
    case SNES::Input::JoypadID::Start: return start.cachedState | slowMotion.cachedState;
  }
  return 0;
}

Asciipad::Asciipad(unsigned category, const char *label, const char *configName) :
InputGroup(category, label),
up("Up", string() << "input." << configName << ".up"),
down("Down", string() << "input." << configName << ".down"),
left("Left", string() << "input." << configName << ".left"),
right("Right", string() << "input." << configName << ".right"),
b("B", string() << "input." << configName << ".b"),
a("A", string() << "input." << configName << ".a"),
y("Y", string() << "input." << configName << ".y"),
x("X", string() << "input." << configName << ".x"),
l("L", string() << "input." << configName << ".l"),
r("R", string() << "input." << configName << ".r"),
select("Select", string() << "input." << configName << ".select"),
start("Start", string() << "input." << configName << ".start"),
switchB("B Switch", string() << "input." << configName << ".bSwitch"),
switchA("A Switch", string() << "input." << configName << ".aSwitch"),
switchY("Y Switch", string() << "input." << configName << ".ySwitch"),
switchX("X Switch", string() << "input." << configName << ".xSwitch"),
switchL("L Switch", string() << "input." << configName << ".lSwitch"),
switchR("R Switch", string() << "input." << configName << ".rSwitch"),
slowMotion("Slow Motion", string() << "input." << configName << ".slowMotion") {
  b.asciiSwitch = &switchB;
  a.asciiSwitch = &switchA;
  y.asciiSwitch = &switchY;
  x.asciiSwitch = &switchX;
  l.asciiSwitch = &switchL;
  r.asciiSwitch = &switchR;

  attach(&up); attach(&down); attach(&left); attach(&right);
  attach(&b); attach(&a); attach(&y); attach(&x);
  attach(&l); attach(&r); attach(&select); attach(&start);
  attach(&switchB); attach(&switchA); attach(&switchY); attach(&switchX);
  attach(&switchL); attach(&switchR); attach(&slowMotion);

  if(this == &asciipad1) {
    up.name = "KB0::Up";
    down.name = "KB0::Down";
    left.name = "KB0::Left";
    right.name = "KB0::Right";
    b.name = "KB0::Z";
    a.name = "KB0::X";
    y.name = "KB0::A";
    x.name = "KB0::S";
    l.name = "KB0::D";
    r.name = "KB0::C";
    select.name = "KB0::Apostrophe";
    start.name = "KB0::Return";
  }
}

//

int16_t Mouse::status(unsigned index, unsigned id) const {
  switch(id) {
    case SNES::Input::MouseID::X: return x.cachedState;
    case SNES::Input::MouseID::Y: return y.cachedState;
    case SNES::Input::MouseID::Left: return left.cachedState;
    case SNES::Input::MouseID::Right: return right.cachedState;
  }
  return 0;
}

Mouse::Mouse(unsigned category, const char *label, const char *configName) :
InputGroup(category, label),
x("X-axis", string() << "input." << configName << ".x"),
y("Y-axis", string() << "input." << configName << ".y"),
left("Left Button", string() << "input." << configName << ".left"),
right("Right Button", string() << "input." << configName << ".right") {
  attach(&x); attach(&y); attach(&left); attach(&right);

  x.name = "MS0::Xaxis";
  y.name = "MS0::Yaxis";
  left.name = "MS0::Button0";
  right.name = "MS0::Button2";
}

//

int16_t SuperScope::status(unsigned index, unsigned id) const {
  switch(id) {
    case SNES::Input::SuperScopeID::X: return x.cachedState;
    case SNES::Input::SuperScopeID::Y: return y.cachedState;
    case SNES::Input::SuperScopeID::Trigger: return trigger.cachedState;
    case SNES::Input::SuperScopeID::Cursor: return cursor.cachedState;
    case SNES::Input::SuperScopeID::Turbo: return turbo.cachedState;
    case SNES::Input::SuperScopeID::Pause: return pause.cachedState;
  }
  return 0;
}

SuperScope::SuperScope(unsigned category, const char *label, const char *configName) :
InputGroup(category, label),
x("X-axis", string() << "input." << configName << ".x"),
y("Y-axis", string() << "input." << configName << ".y"),
trigger("Trigger", string() << "input." << configName << ".trigger"),
cursor("Cursor", string() << "input." << configName << ".cursor"),
turbo("Turbo", string() << "input." << configName << ".turbo"),
pause("Pause", string() << "input." << configName << ".pause") {
  attach(&x); attach(&y); attach(&trigger); attach(&cursor);
  attach(&turbo); attach(&pause);

  x.name = "MS0::Xaxis";
  y.name = "MS0::Yaxis";
  trigger.name = "MS0::Button0";
  cursor.name = "MS0::Button2";
  turbo.name = "KB0::T";
  pause.name = "KB0::P";
}

//

int16_t Justifier::status(unsigned index, unsigned id) const {
  switch(id) {
    case SNES::Input::JustifierID::X: return x.cachedState;
    case SNES::Input::JustifierID::Y: return y.cachedState;
    case SNES::Input::JustifierID::Trigger: return trigger.cachedState;
    case SNES::Input::JustifierID::Start: return start.cachedState;
  }
  return 0;
}

Justifier::Justifier(unsigned category, const char *label, const char *configName) :
InputGroup(category, label),
x("X-axis", string() << "input." << configName << ".x"),
y("Y-axis", string() << "input." << configName << ".y"),
trigger("Trigger", string() << "input." << configName << ".trigger"),
start("Start", string() << "input." << configName << ".start") {
  attach(&x); attach(&y); attach(&trigger); attach(&start);

  if(this == &justifier1) {
    x.name = "MS0::Xaxis";
    y.name = "MS0::Yaxis";
    trigger.name = "MS0::Button0";
    start.name = "MS0::Button2";
  }
}

//

int16_t Justifiers::status(unsigned index, unsigned id) const {
  switch(index & 1) { default:
    case 0: return port1.status(index, id);
    case 1: return port2.status(index, id);
  }
}

Justifiers::Justifiers(Justifier &port1_, Justifier &port2_) :
InputGroup(InputCategory::Hidden, "Justifiers"),
port1(port1_), port2(port2_) {
}

//

Gamepad gamepad1(InputCategory::Port1, "Gamepad", "gamepad1");
Asciipad asciipad1(InputCategory::Port1, "asciiPad", "asciipad1");
Gamepad multitap1a(InputCategory::Port1, "Multitap - Port 1", "multitap1a");
Gamepad multitap1b(InputCategory::Port1, "Multitap - Port 2", "multitap1b");
Gamepad multitap1c(InputCategory::Port1, "Multitap - Port 3", "multitap1c");
Gamepad multitap1d(InputCategory::Port1, "Multitap - Port 4", "multitap1d");
Multitap multitap1(multitap1a, multitap1b, multitap1c, multitap1d);
Mouse mouse1(InputCategory::Port1, "Mouse", "mouse1");

Gamepad gamepad2(InputCategory::Port2, "Gamepad", "gamepad2");
Asciipad asciipad2(InputCategory::Port2, "asciiPad", "asciipad2");
Gamepad multitap2a(InputCategory::Port2, "Multitap - Port 1", "multitap2a");
Gamepad multitap2b(InputCategory::Port2, "Multitap - Port 2", "multitap2b");
Gamepad multitap2c(InputCategory::Port2, "Multitap - Port 3", "multitap2c");
Gamepad multitap2d(InputCategory::Port2, "Multitap - Port 4", "multitap2d");
Multitap multitap2(multitap2a, multitap2b, multitap2c, multitap2d);
Mouse mouse2(InputCategory::Port2, "Mouse", "mouse2");
SuperScope superscope(InputCategory::Port2, "Super Scope", "superscope");
Justifier justifier1(InputCategory::Port2, "Justifier 1", "justifier1");
Justifier justifier2(InputCategory::Port2, "Justifier 2", "justifier2");
Justifiers justifiers(justifier1, justifier2);

}
