#include <ws/ws.hpp>

namespace ares::WonderSwan {

auto enumerate() -> vector<string> {
  return {
    "[Bandai] WonderSwan",
    "[Bandai] WonderSwan Color",
    "[Bandai] SwanCrystal",
    "[Benesse] Pocket Challenge V2",
  };
}

auto load(Node::System& node, string name) -> bool {
  if(!enumerate().find(name)) return false;
  return system.load(node, name);
}

auto option(string name, string value) -> bool {
  if(name == "Pixel Accuracy") {
    apu.setAccurate(value.boolean());
    ppu.setAccurate(value.boolean());
  }
  return true;
}

Scheduler scheduler;
System system;
#define Model ares::WonderSwan::Model
#define SoC ares::WonderSwan::SoC
#include "controls.cpp"
#undef Model
#undef SoC
#include "io.cpp"
#include "debugger.cpp"
#include "serialization.cpp"

auto System::game() -> string {
  if(cartridge.node) {
    return cartridge.title();
  }

  return "(no cartridge connected)";
}

auto System::run() -> void {
  scheduler.enter();
  controls.poll();
}

auto System::load(Node::System& root, string name) -> bool {
  if(node) unload();

  information = {};
  if(name.find("WonderSwan")) {
    information.name = "WonderSwan";
    information.soc = SoC::ASWAN;
    information.model = Model::WonderSwan;
  }
  if(name.find("WonderSwan Color")) {
    information.name = "WonderSwan Color";
    information.soc = SoC::SPHINX;
    information.model = Model::WonderSwanColor;
  }
  if(name.find("SwanCrystal")) {
    information.name = "SwanCrystal";
    information.soc = SoC::SPHINX2;
    information.model = Model::SwanCrystal;
  }
  if(name.find("Pocket Challenge V2")) {
    information.name = "Pocket Challenge V2";
    information.soc = SoC::ASWAN;
    information.model = Model::PocketChallengeV2;
  }

  node = Node::System::create(information.name);
  node->setGame({&System::game, this});
  node->setRun({&System::run, this});
  node->setPower({&System::power, this});
  node->setSave({&System::save, this});
  node->setUnload({&System::unload, this});
  node->setSerialize({&System::serialize, this});
  node->setUnserialize({&System::unserialize, this});
  root = node;
  if(!node->setPak(pak = platform->pak(node))) return false;

  headphones = node->append<Node::Setting::Boolean>("Headphones", true, [&](auto value) {
    apu.io.headphonesConnected = value;
    ppu.updateIcons();
  });
  headphones->setDynamic(true);

  //the EEPROMs come factory-programmed to contain various model names and settings.
  //the model names are confirmed from video recordings of real hardware booting.
  //the other settings bytes are based on how the IPLROMs configure the EEPROMs after changing settings.
  //none of this can be considered 100% verified; direct EEPROM dumps from new-old stock would be required.
  auto initializeName = [&](string name) {
    //16-character limit, 'A'-'Z' only!
    for(u32 index : range(name.size())) {
      eeprom.program(0x60 + index, name[index] - 'A' + 0x0b);
    }
  };

  if(WonderSwan::Model::WonderSwan()) {
    if(auto fp = pak->read("boot.rom")) {
      bootROM.allocate(4_KiB);
      bootROM.load(fp);
    }
    eeprom.allocate(128, 16, 1, 0x00);
    eeprom.program(0x76, 0x01);
    eeprom.program(0x77, 0x00);
    eeprom.program(0x78, 0x24);
    eeprom.program(0x7c, 0x01);
    initializeName("WONDERSWAN");  //verified
  }

  if(WonderSwan::Model::WonderSwanColor()) {
    if(auto fp = pak->read("boot.rom")) {
      bootROM.allocate(8_KiB);
      bootROM.load(fp);
    }
    eeprom.allocate(2048, 16, 1, 0x00);
    eeprom.program(0x76, 0x01);
    eeprom.program(0x77, 0x01);
    eeprom.program(0x78, 0x27);
    eeprom.program(0x7c, 0x01);
    eeprom.program(0x80, 0x01);
    eeprom.program(0x81, 0x01);
    eeprom.program(0x82, 0x27);
    eeprom.program(0x83, 0x03);  //d0-d1 = volume (0-3); d6 = contrast (0=low, 1=high)
    initializeName("WONDERSWANCOLOR");  //verified
  }

  if(WonderSwan::Model::SwanCrystal()) {
    if(auto fp = pak->read("boot.rom")) {
      bootROM.allocate(8_KiB);
      bootROM.load(fp);
    }
    eeprom.allocate(2048, 16, 1, 0x00);
    //unverified; based on WonderSwan Color IPLROM
    eeprom.program(0x76, 0x01);
    eeprom.program(0x77, 0x01);
    eeprom.program(0x78, 0x27);
    eeprom.program(0x7c, 0x01);
    eeprom.program(0x80, 0x01);
    eeprom.program(0x81, 0x01);
    eeprom.program(0x82, 0x27);
    eeprom.program(0x83, 0x03);  //d0-d1 = volume (0-3)
    initializeName("SWANCRYSTAL");  //verified

    //Mama Mitte (used by the software to detect the specific Mama Mitte SwanCrystal hardware)
    eeprom.program(0x7a, 0x7f);
    eeprom.program(0x7b, 0x52);
    eeprom.program(0x07fe, 0x34);
    eeprom.program(0x07ff, 0x12);
  }

  if(WonderSwan::Model::PocketChallengeV2()) {
    if(auto fp = pak->read("boot.rom")) {
      bootROM.allocate(4_KiB);
      bootROM.load(fp);
    }
    //the internal EEPROM has been removed from the Pocket Challenge V2 PCB.
  }

  if(auto fp = pak->read("save.eeprom")) {
    if(fp->attribute("loaded").boolean()) {
      fp->read({eeprom.data, eeprom.size});
    }
  }

  scheduler.reset();
  controls.load(node);
  cpu.load(node);
  ppu.load(node);
  apu.load(node);
  serial.load(node);
  cartridgeSlot.load(node);
  debugger.load(node);
  return true;
}

auto System::save() -> void {
  if(!node) return;

  if(auto fp = pak->write("save.eeprom")) {
    fp->write({eeprom.data, eeprom.size});
  }

  cartridge.save();
}

auto System::unload() -> void {
  if(!node) return;

  save();
  debugger.unload(node);
  bootROM.reset();
  eeprom.reset();
  cpu.unload();
  ppu.unload();
  apu.unload();
  serial.unload();
  cartridgeSlot.unload();
  headphones.reset();
  pak.reset();
  node.reset();
}

auto System::power(bool reset) -> void {
  for(auto& setting : node->find<Node::Setting::Setting>()) setting->setLatch();

  bus.power();
  iram.power();
  eeprom.power();
  cpu.power();
  ppu.power();
  apu.power();
  cartridge.power();
  serial.power();
  scheduler.power(cpu);

  bus.map(this, 0x0060);
  bus.map(this, 0x00ba, 0x00be);

  io = {};
}

}
