
#include "../interface.h"
/*
supported mappers
- sega
- codemasters
	Dizzy, Cosmic Spacehead, Micro Machines, Dinobasher Starring Bignose the Caveman
- korean
	Samgukji III
- korean 8k bank
	penguin adventure, Street Master, Won Si In, Cyborg Z, Superboy 3
- no bank switching
	Fa Tetris, Flash Point
*/

Bus::Bus(_System* _sys, Cpu& cpu, Vdp& vdp)
:
_sys(_sys),
cpu(cpu),
vdp(vdp),
mmio(),
unmapped(),
mc(this) {
	mmio.map(0, this);
	slot[Slot::Cartridge].access = &mem::rom;
	slot[Slot::Cartridge].mapType = MapType::linear;
	slot[Slot::Bios].access = &mem::bios;
	slot[Slot::Bios].mapType = MapType::linear;
	slot[Slot::Wram].access = &mem::wram;
	slot[Slot::Wram].mapType = MapType::linear;
	slot[Slot::Unmapped].access = &unmapped;
	slot[Slot::Unmapped].mapType = MapType::direct;
}

void Bus::prepare(Type _type) {
	type = _type;
	romtype = _type;
	biosDetected = mem::bios.size() == 0 ? false : true;
	romDetected = mem::rom.size() == 0 ? false : true;
	
	if (type == SMS_BIOS) { //only a bios is loaded, maybe with game built in
		biosDetected = true;
		romDetected = false;
		mem::bios.map(mem::rom.handle(), mem::rom.size());
	}
	if (biosDetected) type = SMS_BIOS;
}

u8 Bus::mmio_read(u8 pos) {
	return cpu.openBus();
}

void Bus::mmio_write(u8 pos, u8 value) {
	bool doUpdate = false;

	if ( ((port_3e & 0x08) == 0) != ((value & 0x08) == 0) ) {
		if ((value & 0x08) == 0) type = SMS_BIOS;
		doUpdate = true;
	}
	if ( ((port_3e & 0x40) == 0) != ((value & 0x40) == 0) ) {
		if ((value & 0x40) == 0) type = romtype;
		doUpdate = true;
	}

	if (
		( ((port_3e & 0x10) == 0) != ((value & 0x10) == 0) ) //wram
		|| ( ((port_3e & 0x20) == 0) != ((value & 0x20) == 0) ) //card
		|| ( ((port_3e & 0x80) == 0) != ((value & 0x80) == 0) ) ) { //expansion

		doUpdate = true;
	}
	port_3e = value;
	if (doUpdate) {
		page0update = page1update = page2update = wramupdate = true;
		mapSystem();
	}
}

void Bus::mapSram(u8 bankLo, u8 bankHi, Slot _slot) {
	if (_slot == Slot::Cartridge && mem::sram.size() != 0) {
		map(linear, bankLo, bankHi, 0x00, 0xff, mem::sram);
	} else {
		map(direct, bankLo, bankHi, 0x00, 0xff, unmapped);
	}
}

void Bus::mapWram() {
	Slot _slot = Slot::Wram;
	if (isWorkRamDisabled()) {
		Slot _slot = Slot::Unmapped;
	}

	map(slot[_slot].mapType, 0xC0, 0xDF, 0x00, 0xff, *slot[_slot].access); //wram
	map(slot[_slot].mapType, 0xE0, 0xFF, 0x00, 0xff, *slot[_slot].access); //mirror wram
}

void Bus::mapSystem(u8 data) {

	Slot _slot = getRequestedSlot();
	if (wramupdate) mapWram();
	if (_slot == Slot::Unmapped) {
		map(slot[_slot].mapType, 0x00, 0xBF, 0x00, 0xff, *slot[_slot].access);
		mapUpdated();
		return;
	}

	switch (type) {
		case SMS_ROM_GENERIC:
		case SMS_BIOS:
			if (page0update) {
				map(slot[_slot].mapType, 0x00, 0x03, 0x00, 0xff, *slot[_slot].access); //first k of rom
				map(slot[_slot].mapType, 0x04, 0x3F, 0x00, 0xff, *slot[_slot].access, calcSwift(reg[_slot].page0bank) * 0x4000 + 0x400); //15k slot 0
			}
			if (page1update) map(slot[_slot].mapType, 0x40, 0x7F, 0x00, 0xff, *slot[_slot].access, calcSwift(reg[_slot].page1bank) * 0x4000); //16k slot 1
			if (page2update) {
				if (reg[_slot].ramSelector & 0x8) { //map sram
					//reg.ramSelector & 0x4, sram bank select not needed because of:
					//emulate only 8k of sram, need pcb identification to identify more
					mapSram(0x80, 0x9F, _slot);
					mapSram(0xa0, 0xbf, _slot); //mirrow
				} else {
					map(slot[_slot].mapType, 0x80, 0xBF, 0x00, 0xff, *slot[_slot].access, calcSwift(reg[_slot].page2bank) * 0x4000); //16k slot 2
				}
			}
			map(direct, 0xFF, 0xFF, 0xfc, 0xff, mc); //memory control registers
			break;
		case SMS_ROM_CODEMASTERS:
			if (page0update) {
				map(slot[_slot].mapType, 0x00, 0x3F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page0bank * 0x4000); //16k slot 0
				if (data & 0x80) mapSram(0x20, 0x3f, _slot);
			}
			if (page1update) {
				map(slot[_slot].mapType, 0x40, 0x7F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page1bank * 0x4000); //16k slot 1
				if (data & 0x80) mapSram(0x60, 0x7f, _slot);
			} 
			if (page2update) {
				map(linear, 0x80, 0xBF, 0x00, 0xff, *slot[_slot].access, reg[_slot].page2bank * 0x4000); //16k slot 2
				if (data & 0x80) mapSram(0xa0, 0xbf, _slot);
			}

			map(direct, 0x00, 0x00, 0x00, 0x00, mc); //memory control register slot 0
			map(direct, 0x40, 0x40, 0x00, 0x00, mc); //memory control register slot 1
			map(direct, 0x80, 0x80, 0x00, 0x00, mc); //memory control register slot 2
			break;
		case SMS_ROM_KOREAN:
		case SMS_NO_BANK_SWITCHING:
			if (page0update) map(slot[_slot].mapType, 0x00, 0x3F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page0bank * 0x4000); //16k slot 0
			if (page1update) map(slot[_slot].mapType, 0x40, 0x7F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page1bank * 0x4000); //16k slot 1
			if (page2update) map(slot[_slot].mapType, 0x80, 0xBF, 0x00, 0xff, *slot[_slot].access, reg[_slot].page2bank * 0x4000); //16k slot 2
			if ( type == SMS_ROM_KOREAN )
				map(direct, 0xa0, 0xa0, 0x00, 0x00, mc); //memory control register slot 2 only
			break;
		case SMS_ROM_KOREAN_8K:
			if (page0update) map(slot[_slot].mapType, 0x00, 0x3F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page0bank * 0x4000); //16k slot 0
			if (page1update) {
				map(slot[_slot].mapType, 0x40, 0x5F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page1bank * 0x2000); //8k slot 1
				map(slot[_slot].mapType, 0x60, 0x7F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page2bank * 0x2000); //8k slot 2
			}
			if (page2update) {
				map(slot[_slot].mapType, 0x80, 0x9F, 0x00, 0xff, *slot[_slot].access, reg[_slot].page3bank * 0x2000); //8k slot 3
				map(slot[_slot].mapType, 0xA0, 0xBF, 0x00, 0xff, *slot[_slot].access, reg[_slot].page4bank * 0x2000); //8k slot 4
			}
			map(direct, 0x00, 0x00, 0x00, 0x03, mc);
			break;
	}
	mapUpdated();
}

u8 Bus::calcSwift(u8 bank) {
	Slot _slot = getRequestedSlot();
	switch(reg[_slot].ramSelector & 0x3) {
		case 0: return bank;
		case 1: return (bank + 0x18) & 0x1f;
		case 2: return (bank + 0x10) & 0x1f;
		case 3: return (bank + 0x08) & 0x1f;
	}
}

void Bus::mapRamSelectorWrite(u8 value) {
	Slot _slot = getRequestedSlot();
	if (!isWorkRamDisabled())
		mem::wram.write(0x1ffc, value);
	
	if ((value & 0x03) != (reg[_slot].ramSelector & 0x03)) {
		page0update = page1update = page2update = true;
	}
	else if ((value & 0x04) != (reg[_slot].ramSelector & 0x04))  page2update = true;
	else if ((value & 0x08) != (reg[_slot].ramSelector & 0x08))  page2update = true;
	
	if ((value & 0x10) != (reg[_slot].ramSelector & 0x10)) {
		if (reg[_slot].ramSelector & 0x10) {
			mapSram(0xc0, 0xdf, _slot);
			mapSram(0xe0, 0xff, _slot); //mirrow
		} else {
			wramupdate = true;
		}
	}

	reg[_slot].ramSelector = value;
	mapSystem();
}

u8 Bus::mapRamSelectorRead() {
	return !isWorkRamDisabled() ? mem::wram.read(0x1ffc) : 0x00; 
}

void Bus::mapSlot2Write(u8 value) {
	Slot _slot = getRequestedSlot();
	reg[_slot].page2bank = value;
	if ((type == SMS_ROM_GENERIC || type == SMS_BIOS) && !isWorkRamDisabled()) mem::wram.write(0x1fff, value);
	page2update = true;
	mapSystem(value);
}

u8 Bus::mapSlot2Read() {
	Slot _slot = getRequestedSlot();
	switch(type) {
		case SMS_ROM_GENERIC:
		case SMS_BIOS:
			return !isWorkRamDisabled() ? mem::wram.read(0x1fff) : 0x00;
		case SMS_ROM_CODEMASTERS:
			return mem::rom.read( mirror(reg[_slot].page2bank * 0x4000, getSize()) );
		case SMS_ROM_KOREAN:
			return mem::rom.read( mirror(reg[_slot].page2bank * 0x4000 + 0x2000, getSize()) );
	}
}

void Bus::mapSlot1Write(u8 value) {
	Slot _slot = getRequestedSlot();
	reg[_slot].page1bank = value;
	if ((type == SMS_ROM_GENERIC || type == SMS_BIOS) && !isWorkRamDisabled()) mem::wram.write(0x1ffe, value);
	page1update = true;
	mapSystem(value);
}

u8 Bus::mapSlot1Read() {
	Slot _slot = getRequestedSlot();
	switch(type) {
		case SMS_ROM_GENERIC:
		case SMS_BIOS:
			return !isWorkRamDisabled() ? mem::wram.read(0x1ffe) : 0x00;
		case SMS_ROM_CODEMASTERS:
			return mem::rom.read( mirror(reg[_slot].page1bank * 0x4000, getSize()) );
	}
}

void Bus::mapSlot0Write(u8 value) {
	Slot _slot = getRequestedSlot();
	reg[_slot].page0bank = value;
	if ((type == SMS_ROM_GENERIC || type == SMS_BIOS) && !isWorkRamDisabled()) mem::wram.write(0x1ffd, value);
	page0update = true;
	mapSystem(value);
}

u8 Bus::mapSlot0Read() {
	Slot _slot = getRequestedSlot();
	switch(type) {
		case SMS_ROM_GENERIC:
		case SMS_BIOS:
			return !isWorkRamDisabled() ? mem::wram.read(0x1ffd) : 0x00;
		case SMS_ROM_CODEMASTERS:
			return mem::rom.read( mirror(reg[_slot].page0bank * 0x4000, getSize()) );
	}
}

void Bus::mapKorean8kWrite(u32 addr, u8 value) {
	Slot _slot = getRequestedSlot();
	switch(addr) {
		case 0x0000:
			reg[_slot].page3bank = value;
			page2update = true;
			break;
		case 0x0001:
			reg[_slot].page4bank = value;
			page2update = true;
			break;
		case 0x0002:
			reg[_slot].page1bank = value;
			page1update = true;
			break;
		case 0x0003:
			reg[_slot].page2bank = value;
			page1update = true;
			break;
	}
	mapSystem();
}

u8 Bus::mapKorean8kRead(u32 addr) {
	Slot _slot = getRequestedSlot();
	switch(addr) {
		case 0x0000:
			return mem::rom.read( 0 );
		case 0x0001:
			return mem::rom.read( 1 );
		case 0x0002:
			return mem::rom.read( 2 );
		case 0x0003:
			return mem::rom.read( 3 );
	}
}

void Bus::write_to(u16 addr, u8 value) {
	Mapper& m = mapper[addr];
	return m.access->write(m.offset, value);
}

u8 Bus::read_from(u16 addr) {
	Mapper& m = mapper[addr];
	return m.access->read(m.offset);
}

void Bus::map(	MapType t, u8 bank_lo, u8 bank_hi, u8 page_lo, u8 page_hi,
				MemMap& access, u32 offset) {

	if ( (t != direct) && !access.size() ) return;

	u32 index = 0;
	for(u16 bank = bank_lo; bank <= bank_hi; bank++) {
		for(u16 page = page_lo; page <= page_hi; page++) {
			u16 pos = (bank << 8) | page;
			mapper[pos].access = &access;
			mapper[pos].offset = (t == direct) ? (bank << 8) | page : mirror(offset + index, access.size());
			index += 1;
		}
	}
}

u32 Bus::mirror(u32 index, u32 size) {
	if (index < size) return index;
	return index % size;
}

void Bus::power() {
	mem::wram.init(0);
	reg[Slot::Cartridge].ramSelector = reg[Slot::Bios].ramSelector = 0x00;
	reg[Slot::Cartridge].page0bank = reg[Slot::Bios].page0bank = 0;
	reg[Slot::Cartridge].page1bank = reg[Slot::Bios].page1bank = 1;
	reg[Slot::Cartridge].page2bank = romtype == SMS_ROM_GENERIC ? 2 : 0;
	reg[Slot::Bios].page2bank = 2;
	//for korean 8k mapper
	reg[Slot::Cartridge].page3bank = 0;
	reg[Slot::Cartridge].page4bank = 0;
	if (romtype == Type::SMS_ROM_KOREAN_8K) {
		reg[Slot::Cartridge].page1bank = 0;
		reg[Slot::Cartridge].page2bank = 0;
	}

	port_3e = biosDetected ? 0xe3 : 0xab;
	page0update = page1update = page2update = wramupdate = true;
	mapSystem();
}

u8 Bus::MemoryControl::read(u32 addr) {
	if (bus->getType() == Bus::Type::SMS_ROM_KOREAN_8K) {
		return bus->mapKorean8kRead(addr);
	}

	switch(addr) {
		case 0xfffc: return bus->mapRamSelectorRead();
		case 0xfffd:
		case 0x0000: return bus->mapSlot0Read();
		case 0xfffe: 
		case 0x4000: return bus->mapSlot1Read();
		case 0xffff:
		case 0x8000:
		case 0xa000: return bus->mapSlot2Read();
	}
}

void Bus::MemoryControl::write(u32 addr, u8 value) {

	if (bus->getType() == Bus::Type::SMS_ROM_KOREAN_8K) {
		bus->mapKorean8kWrite(addr, value);
		return;
	}

	switch(addr) {
		case 0xfffc: bus->mapRamSelectorWrite(value); break;
		case 0xfffd:
		case 0x0000: bus->mapSlot0Write(value); break;
		case 0xfffe: 
		case 0x4000: bus->mapSlot1Write(value); break;
		case 0xffff:
		case 0x8000:
		case 0xa000: bus->mapSlot2Write(value); break;
	}
}

u8 Bus::MemUnmapped::read(u32 addr) { return 0xff; }

u8 Bus::MemMmio::read(u8 addr) {
	u8 pos = route(addr);
	return _mmio[pos]->mmio_read(pos);
}

void Bus::MemMmio::write(u8 addr, u8 value) {
	u8 pos = route(addr);
	_mmio[pos]->mmio_write(pos, value);
}

void Bus::MemMmio::map(u8 pos, Mmio* access) { 
	_mmio[pos & 7] = access;
}

u8 Bus::MemMmio::route(u8 addr) {
	switch (addr & 0xc1) {
		case 0x00: return 0; //Memory control -> 0x3e
		case 0x01: return 1; //I/O port control -> 0x3f
		case 0x40: return 2; //V counter, SN76489 data write -> 0x7e
		case 0x41: return 3; //H counter, SN76489 data write -> 0x7f
		case 0x80: return 4; //data port -> 0xBE
		case 0x81: return 5; //control port -> 0xBF
		case 0xc0: return 6; //Joypads Port A + B -> 0xdc
		case 0xc1: return 7; //Joypads Port A + B -> 0xdd
	}
}