void InterfaceSNES::setController(bool port, unsigned device) {
  if(port == 0) config->snes.controllerPort1Device = device;
  if(port == 1) config->snes.controllerPort2Device = device;

  if(port == 0) switch(device) {
  case 0: return connect(0, SNES::Input::Device::None);
  case 1: return connect(0, SNES::Input::Device::Joypad);
  case 2: return connect(0, SNES::Input::Device::Multitap);
  case 3: return connect(0, SNES::Input::Device::Mouse);
  }

  if(port == 1) switch(device) {
  case 0: return connect(1, SNES::Input::Device::None);
  case 1: return connect(1, SNES::Input::Device::Joypad);
  case 2: return connect(1, SNES::Input::Device::Multitap);
  case 3: return connect(1, SNES::Input::Device::Mouse);
  case 4: return connect(1, SNES::Input::Device::SuperScope);
  case 5: return connect(1, SNES::Input::Device::Justifier);
  case 6: return connect(1, SNES::Input::Device::Justifiers);
  case 7: return connect(1, SNES::Input::Device::Serial);
  }
}

bool InterfaceSNES::loadCartridge(const string &basename) {
  uint8_t *data;
  unsigned size;
  if(interface->loadFile(basename, data, size) == false) return false;

  interface->unloadCartridge();
  interface->baseName = nall::basename(basename);
  interface->slotName = { nall::basename(basename) };

  string markup;
  markup.readfile({ interface->baseName, ".bml" });
  if(markup == "") markup = SnesCartridge(data, size).markup;

  SNES::Interface::loadCartridge({ markup, data, size });
  delete[] data;

  loadMemory();
  interface->loadCartridge(::Interface::Mode::SNES);
  return true;
}

bool InterfaceSNES::loadSatellaviewSlottedCartridge(const string &basename, const string &slotname) {
  uint8_t *data[2];
  unsigned size[2];
  if(interface->loadFile(basename, data[0], size[0]) == false) return false;
  interface->loadFile(slotname, data[1], size[1]);

  interface->unloadCartridge();
  interface->baseName = nall::basename(basename);
  if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname)));
  interface->slotName = { nall::basename(basename), nall::basename(slotname) };

  string markup;
  markup.readfile({ interface->baseName, ".bml" });
  if(markup == "") markup = SnesCartridge(data[0], size[0]).markup;

  SNES::Interface::loadSatellaviewSlottedCartridge({ markup, data[0], size[0] }, { "", data[1], size[1] });
  delete[] data[0];
  if(data[1]) delete[] data[1];

  loadMemory();
  interface->loadCartridge(::Interface::Mode::SNES);
  return true;
}

bool InterfaceSNES::loadSatellaviewCartridge(const string &basename, const string &slotname) {
  uint8_t *data[2];
  unsigned size[2];
  if(interface->loadFile(basename, data[0], size[0]) == false) return false;
  interface->loadFile(slotname, data[1], size[1]);

  interface->unloadCartridge();
  interface->baseName = nall::basename(basename);
  if(data[1]) interface->baseName.append("+", nall::basename(notdir(slotname)));
  interface->slotName = { nall::basename(basename), nall::basename(slotname) };

  string markup;
  markup.readfile({ interface->baseName, ".bml" });
  if(markup == "") markup = SnesCartridge(data[0], size[0]).markup;

  SNES::Interface::loadSatellaviewCartridge({ markup, data[0], size[0] }, { "", data[1], size[1] });
  delete[] data[0];
  if(data[1]) delete[] data[1];

  loadMemory();
  interface->loadCartridge(::Interface::Mode::SNES);
  return true;
}

bool InterfaceSNES::loadSufamiTurboCartridge(const string &basename, const string &slotAname, const string &slotBname) {
  uint8_t *data[3];
  unsigned size[3];
  if(interface->loadFile(basename, data[0], size[0]) == false) return false;
  interface->loadFile(slotAname, data[1], size[1]);
  interface->loadFile(slotBname, data[2], size[2]);

  interface->unloadCartridge();
  interface->baseName = nall::basename(basename);
  if(data[1] && data[2]) interface->baseName = { nall::basename(slotAname), "+", nall::basename(notdir(slotBname)) };
  else if(data[1]) interface->baseName = nall::basename(slotAname);
  else if(data[2]) interface->baseName = nall::basename(slotBname);
  interface->slotName = { nall::basename(basename), nall::basename(slotAname), nall::basename(slotBname) };

  string markup;
  markup.readfile({ interface->baseName, ".bml" });
  if(markup == "") markup = SnesCartridge(data[0], size[0]).markup;

  SNES::Interface::loadSufamiTurboCartridge({ markup, data[0], size[0] }, { "", data[1], size[1] }, { "", data[2], size[2] });
  delete[] data[0];
  if(data[1]) delete[] data[1];
  if(data[2]) delete[] data[2];

  loadMemory();
  interface->loadCartridge(::Interface::Mode::SNES);
  return true;
}

bool InterfaceSNES::loadSuperGameBoyCartridge(const string &basename, const string &slotname) {
  uint8_t *data[2];
  unsigned size[2];
  if(interface->loadFile(basename, data[0], size[0]) == false) return false;
  interface->loadFile(slotname, data[1], size[1]);

  interface->unloadCartridge();
  interface->baseName = nall::basename(basename);
  if(data[1]) interface->baseName = nall::basename(slotname);
  interface->slotName = { nall::basename(basename), nall::basename(slotname) };

  string markup;
  markup.readfile({ interface->baseName, ".bml" });
  if(markup == "") markup = SnesCartridge(data[0], size[0]).markup;

  string gbMarkup;
  gbMarkup.readfile({ nall::basename(slotname), ".bml" });
  if(gbMarkup == "") gbMarkup = GameBoyCartridge(data[1], size[1]).markup;
  SNES::Interface::loadSuperGameBoyCartridge({ markup, data[0], size[0] }, { gbMarkup, data[1], size[1] });
  delete[] data[0];
  if(data[1]) delete[] data[1];

  loadMemory();
  interface->loadCartridge(::Interface::Mode::SNES);
  return true;
}

void InterfaceSNES::unloadCartridge() {
  saveMemory();
  SNES::Interface::unloadCartridge();
  interface->baseName = "";
}

//slot[] array = Cartridge::Slot to slot# conversion:
//{ Base, Bsx, SufamiTurbo, SufamiTurboA, SufamiTurboB, GameBoy }

void InterfaceSNES::loadMemory() {
  static unsigned slot[] = { 0, 0, 0, 1, 2, 1 };
  for(auto &memory : SNES::Interface::memory()) {
    if(memory.size == 0) continue;
    string filename = { interface->slotName[slot[(unsigned)memory.slot]], memory.id };
    uint8_t *data;
    unsigned size;
    if(file::read(filename, data, size)) {
      memcpy(memory.data, data, min(memory.size, size));
      delete[] data;
    }
  }
}

void InterfaceSNES::saveMemory() {
  static unsigned slot[] = { 0, 0, 0, 1, 2, 1 };
  for(auto &memory : SNES::Interface::memory()) {
    if(memory.size == 0) continue;
    string filename = { interface->slotName[slot[(unsigned)memory.slot]], memory.id };
    file::write(filename, memory.data, memory.size);
  }
}

bool InterfaceSNES::saveState(const string &filename) {
  serializer s = serialize();
  return file::write(filename, s.data(), s.size());
}

bool InterfaceSNES::loadState(const string &filename) {
  uint8_t *data;
  unsigned size;
  if(file::read(filename, data, size) == false) return false;
  serializer s(data, size);
  delete[] data;
  return unserialize(s);
}

//

void InterfaceSNES::videoRefresh(const uint32_t *data, bool hires, bool interlace, bool overscan) {
  static uint16_t output[512 * 480];

  unsigned width = 256 << hires;
  unsigned height = 240 << interlace;
  unsigned pitch = 1024 >> interlace;

  //skip first line; as it is always blank (by SNES design)
  if(overscan == false) data +=  1 * 1024;  // 8 + 224 +  8
  if(overscan == true ) data +=  9 * 1024;  // 0 + 240 +  0

  for(unsigned y = 0; y < height; y++) {
    const uint32_t *sp = data + y * pitch;
    uint16_t *dp = output + y * 512;
    for(unsigned x = 0; x < width; x++) {
      *dp++ = palette[*sp++];
    }
  }

  if(config->video.maskOverscan) {
    unsigned osw = config->video.maskOverscanHorizontal << hires;
    unsigned osh = config->video.maskOverscanVertical << interlace;

    for(unsigned y = 0; y < height; y++) {
      uint16_t *dp = output + y * 512;
      if(y < osh || y >= height - osh) {
        memset(dp, 0, width * 2);
      } else {
        memset(dp + 0, 0, osw * 2);
        memset(dp + width - osw, 0, osw * 2);
      }
    }
  }

  interface->videoRefresh(output, 512 * 2, width, height);
}

void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) {
  signed samples[] = { lsample, rsample };
  dspaudio.sample(samples);
  while(dspaudio.pending()) {
    dspaudio.read(samples);
    audio.sample(samples[0], samples[1]);
  }
}

int16_t InterfaceSNES::inputPoll(bool port, SNES::Input::Device device, unsigned index, unsigned id) {
  if(port == 0) {
    if(device == SNES::Input::Device::Joypad) return inputManager->snes.port1.gamepad.poll(id);
    if(device == SNES::Input::Device::Multitap) {
      if(index == 0) return inputManager->snes.port1.multitap1.poll(id);
      if(index == 1) return inputManager->snes.port1.multitap1.poll(id);
      if(index == 2) return inputManager->snes.port1.multitap1.poll(id);
      if(index == 3) return inputManager->snes.port1.multitap1.poll(id);
    }
    if(device == SNES::Input::Device::Mouse) return inputManager->snes.port1.mouse.poll(id);
  }

  if(port == 1) {
    if(device == SNES::Input::Device::Joypad) return inputManager->snes.port2.gamepad.poll(id);
    if(device == SNES::Input::Device::Multitap) {
      if(index == 0) return inputManager->snes.port2.multitap1.poll(id);
      if(index == 1) return inputManager->snes.port2.multitap1.poll(id);
      if(index == 2) return inputManager->snes.port2.multitap1.poll(id);
      if(index == 3) return inputManager->snes.port2.multitap1.poll(id);
    }
    if(device == SNES::Input::Device::Mouse) return inputManager->snes.port2.mouse.poll(id);
    if(device == SNES::Input::Device::SuperScope) return inputManager->snes.port2.superScope.poll(id);
    if(device == SNES::Input::Device::Justifier) return inputManager->snes.port2.justifier1.poll(id);
    if(device == SNES::Input::Device::Justifiers) {
      if(index == 0) return inputManager->snes.port2.justifier1.poll(id);
      if(index == 1) return inputManager->snes.port2.justifier2.poll(id);
    }
  }

  return 0;
}

string InterfaceSNES::path(SNES::Cartridge::Slot slot, const string &hint) {
  static unsigned index[] = { 0, 0, 0, 1, 2, 1 };
  return { interface->slotName[index[(unsigned)slot]], hint };
}

void InterfaceSNES::message(const string &text) {
  MessageWindow::information(*mainWindow, text);
}

InterfaceSNES::InterfaceSNES() {
  //{llll bbbbb ggggg rrrrr} -> { rrrrr ggggg bbbbb }
  palette = new uint32_t[16 * 32 * 32 * 32];
  for(unsigned l = 0; l < 16; l++) {
    for(unsigned r = 0; r < 32; r++) {
      for(unsigned g = 0; g < 32; g++) {
        for(unsigned b = 0; b < 32; b++) {
          double luma = (double)l / 15.0;
          unsigned ar = (luma * r + 0.5);
          unsigned ag = (luma * g + 0.5);
          unsigned ab = (luma * b + 0.5);
          palette[(l << 15) + (r << 10) + (g << 5) + (b << 0)] = (ab << 10) + (ag << 5) + (ar << 0);
        }
      }
    }
  }
}

InterfaceSNES::~InterfaceSNES() {
  delete[] palette;
}
