#include "addressSpace.hpp"
#include "defines.hpp"

void AddressSpace::determineMBCInfo() {
  MBC = static_cast<MBCType_enum>(memoryLayout.romBank0[0x147]);
  romSize = 32768 * (1 << memoryLayout.romBank0[0x147]);
  romBanks = 1 << (memoryLayout.romBank0[0x147] + 1);

  const Byte ramSize = memoryLayout.romBank0[0x0149];
  switch (ramSize) {
  case 0x02:
    externalRamSize = 8196;
    externalRamBanks = 1;
    break;
  case 0x03:
    externalRamSize = 32768;
    externalRamBanks = 4;
    break;
  case 0x04:
    externalRamSize = 131072;
    externalRamBanks = 16;
    break;
  case 0x05:
    externalRamSize = 65536;
    externalRamBanks = 8;
    break;
  default:
    externalRamSize = 0;
    externalRamBanks = 0;
    break;
  }

  if (MBC == MBC2 || MBC == MBC2Battery) {
    // only the lower 4 bits are usable
    externalRamSize = 512;
  }
}

Byte *AddressSpace::MBCWrite(const Word address) {
  if (MBC == MBC1) {
    if (address <= 0x1FFF)
      return &ramEnable;
    if (address <= 0x3FFF)
      return &romBankRegister;
    if (address <= 0x5FFF)
      return &twoBitBankRegister;
    if (address <= 0x7FFF)
      return &romRamSelect;
  } else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
    if (address <= 0x1FFF)
      return &ramEnable;
    // bits 0-4
    if (address <= 0x3FFF)
      return &romBankRegister;
    if (address <= 0x5FFF) {
      return &twoBitBankRegister;
    }
    if (address <= 0x7FFF)
      return &romRamSelect;
  } else if (MBC == MBC2 || MBC == MBC2Battery) {
    if (address <= 0x3FFF) {
      if (address & 0x0100)
        return &romBankRegister;
      else
        return &ramEnable;
    }
  } else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
    if (address <= 0x1FFF)
      return &ramEnable;
  } else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
             MBC == MBC3TimerRamBattery) {
    if (address <= 0x1FFF)
      return &ramEnable;
    if (address <= 0x3FFF)
      return &romBankRegister;
    if (address <= 0x5FFF)
      return &ramBankRTCRegister;
  }
  return &dummyVal;
}

void AddressSpace::MBCUpdate() {
  // TODO: multicart roms need to be able to switch the first rom bank as well
  // see: https://gbdev.io/pandocs/MBC1.html
  if (MBC == MBC1) {
    // Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
    romBankRegister &= 0b11111;
    twoBitBankRegister &= 0b11;

    // 512 KiB can only have 8KiB of ram
    if (romSize >= 524288) {
      if (romBankRegister == 0)
        selectedRomBank = (twoBitBankRegister << 5) + 1;
      selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
    } else {
      if (romBankRegister == 0)
        selectedRomBank = 1;
      else
        selectedRomBank = romBankRegister;
    }
    if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
        romBankRegister == 0x60)
      romBankRegister += 1;
    selectedExternalRamBank = 0;

    loadRomBank();
    loadRamBank();
  } else if (MBC == MBC1Ram || MBC == MBC1RamBattery) {
    // Selected ROM Bank = (Secondary Bank << 5) + ROM Bank
    romBankRegister &= 0b11111;
    twoBitBankRegister &= 0b11;

    // 512 KiB can only have 8KiB of ram
    if (romSize >= 524288) {
      if (romBankRegister == 0)
        selectedRomBank = (twoBitBankRegister << 5) + 1;
      selectedRomBank = (twoBitBankRegister << 5) + romBankRegister;
      selectedExternalRamBank = 0;
    } else {
      if (romBankRegister == 0)
        selectedRomBank = 1;
      else
        selectedRomBank = romBankRegister;
      selectedExternalRamBank = twoBitBankRegister;
    }
    if (romBankRegister == 0x20 || romBankRegister == 0x40 ||
        romBankRegister == 0x60)
      romBankRegister += 1;

    loadRomBank();
    loadRamBank();
  } else if (MBC == MBC2 || MBC == MBC2Battery) {
    selectedRomBank = romBankRegister & 0x0F;
    selectedExternalRamBank = 0;

    loadRomBank();
    loadRamBank();
  } else if (MBC == MBC3 || MBC == MBC3TimerBattery) {
    selectedRomBank = romBankRegister & 0x7F;
    selectedExternalRamBank = 0;

    if (MBC == MBC3TimerBattery) {
      if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
        enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
      else
        enabledRTCRegister = NONE;
    }
    loadRomBank();
    loadRamBank();
  } else if (MBC == MBC3Ram || MBC == MBC3RamBattery ||
             MBC == MBC3TimerRamBattery) {
    selectedRomBank = romBankRegister & 0x7F;
    selectedExternalRamBank = ramBankRTCRegister & 0b11;

    if (MBC == MBC3TimerRamBattery) {
      if(ramBankRTCRegister >= 0x8 && ramBankRTCRegister <= 0xC)
        enabledRTCRegister = static_cast<enabledRTCRegister_enum>(ramBankRTCRegister);
      else
        enabledRTCRegister = NONE;
    }
    loadRomBank();
    loadRamBank();
  }
}

void AddressSpace::loadRomBank() {
  memoryLayout.romBankSwitch = game.data() + (ROM_BANK_SIZE * selectedRomBank);
}

void AddressSpace::createRamBank() {
  if (externalRamSize) {
    cartridgeRam = new Byte[externalRamSize];
    memoryLayout.externalRam = cartridgeRam;
  }
}

void AddressSpace::loadRamBank() {
  if (cartridgeRam != nullptr) {
    memoryLayout.externalRam =
        cartridgeRam + (RAM_BANK_SIZE * selectedExternalRamBank);
  }
}
