#include <sfc/sfc.hpp>

namespace SuperFamicom {

#include "markup.cpp"
#include "serialization.cpp"
Cartridge cartridge;

auto Cartridge::manifest() -> string {
  string manifest = information.markup.cartridge;

  if(information.markup.gameBoy) {
    manifest.append("\n[[Game Boy]]\n\n");
    manifest.append(information.markup.gameBoy);
  }

  if(information.markup.bsMemory) {
    manifest.append("\n[[BS Memory]]\n\n");
    manifest.append(information.markup.bsMemory);
  }

  if(information.markup.sufamiTurboA) {
    manifest.append("\n[[Sufami Turbo - Slot A]]\n\n");
    manifest.append(information.markup.sufamiTurboA);
  }

  if(information.markup.sufamiTurboB) {
    manifest.append("\n[[Sufami Turbo - Slot B]]\n\n");
    manifest.append(information.markup.sufamiTurboB);
  }

  return manifest;
}

auto Cartridge::title() -> string {
  string title = information.title.cartridge;

  if(information.title.gameBoy) {
    title.append(" + ", information.title.gameBoy);
  }

  if(information.title.bsMemory) {
    title.append(" + ", information.title.bsMemory);
  }

  if(information.title.sufamiTurboA) {
    title.append(" + ", information.title.sufamiTurboA);
  }

  if(information.title.sufamiTurboB) {
    title.append(" + ", information.title.sufamiTurboB);
  }

  return title;
}

auto Cartridge::load() -> void {
  _region = Region::NTSC;

  hasICD2       = false;
  hasMCC        = false;
  hasNSSDIP     = false;
  hasEvent      = false;
  hasSA1        = false;
  hasSuperFX    = false;
  hasARMDSP     = false;
  hasHitachiDSP = false;
  hasNECDSP     = false;
  hasEpsonRTC   = false;
  hasSharpRTC   = false;
  hasSPC7110    = false;
  hasSDD1       = false;
  hasOBC1       = false;
  hasMSU1       = false;

  hasGameBoySlot      = false;
  hasBSMemorySlot     = false;
  hasSufamiTurboSlots = false;

  information.markup.cartridge    = "";
  information.markup.gameBoy      = "";
  information.markup.bsMemory     = "";
  information.markup.sufamiTurboA = "";
  information.markup.sufamiTurboB = "";

  information.title.cartridge     = "";
  information.title.gameBoy       = "";
  information.title.bsMemory      = "";
  information.title.sufamiTurboA  = "";
  information.title.sufamiTurboB  = "";

  interface->loadRequest(ID::Manifest, "manifest.bml", true);
  parseMarkup(information.markup.cartridge);

  //Game Boy
  if(cartridge.hasICD2()) {
    _sha256 = "";  //Game Boy cartridge not loaded yet: set later via loadGameBoy()
  }

  //BS Memory
  else if(cartridge.hasMCC() && cartridge.hasBSMemorySlot()) {
    _sha256 = Hash::SHA256(bsmemory.memory.data(), bsmemory.memory.size()).digest();
  }

  //Sufami Turbo
  else if(cartridge.hasSufamiTurboSlots()) {
    Hash::SHA256 sha;
    sha.data(sufamiturboA.rom.data(), sufamiturboA.rom.size());
    sha.data(sufamiturboB.rom.data(), sufamiturboB.rom.size());
    _sha256 = sha.digest();
  }

  //Super Famicom
  else {
    Hash::SHA256 sha;
    //hash each ROM image that exists; any with size() == 0 is ignored by sha256_chunk()
    sha.data(rom.data(), rom.size());
    sha.data(mcc.rom.data(), mcc.rom.size());
    sha.data(sa1.rom.data(), sa1.rom.size());
    sha.data(superfx.rom.data(), superfx.rom.size());
    sha.data(hitachidsp.rom.data(), hitachidsp.rom.size());
    sha.data(spc7110.prom.data(), spc7110.prom.size());
    sha.data(spc7110.drom.data(), spc7110.drom.size());
    sha.data(sdd1.rom.data(), sdd1.rom.size());
    //hash all firmware that exists
    vector<uint8> buffer;
    buffer = armdsp.firmware();
    sha.data(buffer.data(), buffer.size());
    buffer = hitachidsp.firmware();
    sha.data(buffer.data(), buffer.size());
    buffer = necdsp.firmware();
    sha.data(buffer.data(), buffer.size());
    //finalize hash
    _sha256 = sha.digest();
  }

  rom.write_protect(true);
  ram.write_protect(false);
}

auto Cartridge::loadGameBoy() -> void {
  #if defined(SFC_SUPERGAMEBOY)
  //invoked from ICD2::load()
  _sha256 = GameBoy::interface->sha256();
  information.markup.gameBoy = GameBoy::interface->manifest();
  information.title.gameBoy = GameBoy::interface->title();
  #endif
}

auto Cartridge::loadBSMemory() -> void {
  interface->loadRequest(ID::BSMemoryManifest, "manifest.bml", true);
  auto document = BML::unserialize(information.markup.bsMemory);
  information.title.bsMemory = document["information/title"].text();

  auto rom = document["board/rom"];

  if(rom["name"]) {
    uint size = rom["size"].natural();
    bsmemory.memory.map(allocate<uint8>(size, 0xff), size);
    interface->loadRequest(ID::BSMemoryROM, rom["name"].text(), true);

    bsmemory.readonly = (rom["type"].text() == "mrom");
  }
}

auto Cartridge::loadSufamiTurboA() -> void {
  interface->loadRequest(ID::SufamiTurboSlotAManifest, "manifest.bml", true);
  auto document = BML::unserialize(information.markup.sufamiTurboA);
  information.title.sufamiTurboA = document["information/title"].text();

  auto rom = document["board/rom"];
  auto ram = document["board/ram"];

  if(rom["name"]) {
    uint size = rom["size"].natural();
    sufamiturboA.rom.map(allocate<uint8>(size, 0xff), size);
    interface->loadRequest(ID::SufamiTurboSlotAROM, rom["name"].text(), true);
  }

  if(ram["name"]) {
    uint size = ram["size"].natural();
    sufamiturboA.ram.map(allocate<uint8>(size, 0xff), size);
    interface->loadRequest(ID::SufamiTurboSlotARAM, ram["name"].text(), false);
    memory.append({ID::SufamiTurboSlotARAM, ram["name"].text()});
  }

  if(document["board/linkable"]) {
    interface->loadRequest(ID::SufamiTurboSlotB, "Sufami Turbo", "st", false);
  }
}

auto Cartridge::loadSufamiTurboB() -> void {
  interface->loadRequest(ID::SufamiTurboSlotBManifest, "manifest.bml", true);
  auto document = BML::unserialize(information.markup.sufamiTurboB);
  information.title.sufamiTurboB = document["information/title"].text();

  auto rom = document["board/rom"];
  auto ram = document["board/ram"];

  if(rom["name"]) {
    uint size = rom["size"].natural();
    sufamiturboB.rom.map(allocate<uint8>(size, 0xff), size);
    interface->loadRequest(ID::SufamiTurboSlotBROM, rom["name"].text(), true);
  }

  if(ram["name"]) {
    uint size = ram["size"].natural();
    sufamiturboB.ram.map(allocate<uint8>(size, 0xff), size);
    interface->loadRequest(ID::SufamiTurboSlotBRAM, ram["name"].text(), false);
    memory.append({ID::SufamiTurboSlotBRAM, ram["name"].text()});
  }
}

auto Cartridge::unload() -> void {
  rom.reset();
  ram.reset();
  memory.reset();
}

}
