void InterfaceNES::initialize() {
  NES::interface = this;
  NES::system.init();
}

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

  if(port == 0) switch(device) {
  case 0: return NES::input.connect(0, NES::Input::Device::None);
  case 1: return NES::input.connect(0, NES::Input::Device::Joypad);
  }

  if(port == 1) switch(device) {
  case 0: return NES::input.connect(1, NES::Input::Device::None);
  case 1: return NES::input.connect(1, NES::Input::Device::Joypad);
  }
}

bool InterfaceNES::cartridgeLoaded() {
  return NES::cartridge.loaded();
}

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

  interface->unloadCartridge();
  interface->baseName = nall::basename(filename);

  string markup;
  markup.readfile({ interface->baseName, ".xml" });

  NES::cartridge.load(markup, data, size);
  NES::system.power();
  delete[] data;

  if(NES::cartridge.ram_size()) {
    filemap fp;
    if(fp.open(string{ interface->baseName, ".sav" }, filemap::mode::read)) {
      memcpy(NES::cartridge.ram_data(), fp.data(), min(NES::cartridge.ram_size(), fp.size()));
    }
  }

  interface->loadCartridge(::Interface::Mode::NES);
  NES::video.generate(NES::Video::Format::RGB30);
  return true;
}

void InterfaceNES::unloadCartridge() {
  if(NES::cartridge.ram_size()) {
    file::write({ interface->baseName, ".sav" }, NES::cartridge.ram_data(), NES::cartridge.ram_size());
  }
  NES::cartridge.unload();
  interface->baseName = "";
}

//

void InterfaceNES::power() {
  NES::system.power();
}

void InterfaceNES::reset() {
  NES::system.reset();
}

void InterfaceNES::run() {
  NES::system.run();
}

//

serializer InterfaceNES::serialize() {
  NES::system.runtosave();
  return NES::system.serialize();
}

bool InterfaceNES::unserialize(serializer &s) {
  return NES::system.unserialize(s);
}

//

void InterfaceNES::setCheats(const lstring &list) {
  NES::cheat.reset();
  for(auto &code : list) {
    lstring codelist;
    codelist.split("+", code);
    for(auto &part : codelist) {
      unsigned addr, data, comp;
      if(NES::Cheat::decode(part, addr, data, comp)) {
        NES::cheat.append({ addr, data, comp });
      }
    }
  }
  NES::cheat.synchronize();
}

//

void InterfaceNES::videoRefresh(const uint16_t *data) {
  static uint32_t output[256 * 240];

  for(unsigned y = 0; y < 240; y++) {
    const uint16_t *sp = data + y * 256;
    uint32_t *dp = output + y * 256;
    for(unsigned x = 0; x < 256; x++) {
      uint32_t color = *sp++;
      *dp++ = NES::video.palette[color];
    }
  }

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

    for(unsigned y = 0; y < 240; y++) {
      uint32_t *dp = output + y * 256;
      if(y < osh || y >= 240 - osh) {
        memset(dp, 0, 256 * sizeof(uint32_t));
      } else {
        memset(dp + 0, 0, osw * sizeof(uint32_t));
        memset(dp + 256 - osw, 0, osw * sizeof(uint32_t));
      }
    }
  }

  interface->videoRefresh(output, 256 * sizeof(uint32_t), 256, 240);
}

void InterfaceNES::audioSample(int16_t sample) {
  signed samples[] = { sample };
  dspaudio.sample(samples);
  while(dspaudio.pending()) {
    dspaudio.read(samples);
    audio.sample(samples[0], samples[0]);  //NES audio output is monaural; ruby only takes stereo audio
  }
}

int16_t InterfaceNES::inputPoll(bool port, unsigned device, unsigned id) {
  if(port == 0 && device == 0) return inputManager->nes.port1.gamepad.poll(id);
  return 0;
}
