#pragma once
#include "stdafx.h"
#include "CPU.h"
#include "BaseMapper.h"

class MMC1 : public BaseMapper
{
	private:
		enum class MMC1Registers
		{
			Reg8000 = 0,
			RegA000 = 1,
			RegC000 = 2,
			RegE000 = 3
		};

		enum class PrgMode
		{
			_16k = 16,
			_32k = 32,
		};

		enum class ChrMode
		{
			_4k = 4,
			_8k = 8,
		};

		enum class SlotSelect
		{
			x8000 = 0x8000,
			xC000 = 0xC000,
		};

		uint8_t _writeBuffer = 0;
		uint8_t _shiftCount = 0;

		bool _wramDisable;
		ChrMode _chrMode;
		PrgMode _prgMode;
		SlotSelect _slotSelect;

		uint8_t _chrReg0;
		uint8_t _chrReg1;
		uint8_t _prgReg;

		uint64_t _lastWriteCycle = 0;
		
		bool _forceWramOn;
		MMC1Registers _lastChrReg;
		
	private:
		bool HasResetFlag(uint8_t value)
		{
			return (value & 0x80) == 0x80;
		}

		void ResetBuffer()
		{
			_shiftCount = 0;
			_writeBuffer = 0;
		}

		bool IsBufferFull(uint8_t value)
		{
			if(HasResetFlag(value)) {
				//When 'r' is set:
				//	- 'd' is ignored
				//	- hidden temporary reg is reset (so that the next write is the "first" write)
				//	- bits 2,3 of reg $8000 are set (16k PRG mode, $8000 swappable)
				//	- other bits of $8000 (and other regs) are unchanged
				ResetBuffer();
				_state.Reg8000 |= 0x0C;
				UpdateState();
				return false;
			} else {
				_writeBuffer >>= 1;
				_writeBuffer |= ((value << 4) & 0x10);

				_shiftCount++;

				return _shiftCount == 5;
			}
		}

	protected:
		struct
		{
			uint8_t Reg8000;
			uint8_t RegA000;
			uint8_t RegC000;
			uint8_t RegE000;
		} _state;

		virtual void UpdateState()
		{
			switch(_state.Reg8000 & 0x03) {
				case 0: SetMirroringType(MirroringType::ScreenAOnly); break;
				case 1: SetMirroringType(MirroringType::ScreenBOnly); break;
				case 2: SetMirroringType(MirroringType::Vertical); break;
				case 3: SetMirroringType(MirroringType::Horizontal); break;
			}

			_wramDisable = (_state.RegE000 & 0x10) == 0x10;

			_slotSelect = ((_state.Reg8000 & 0x04) == 0x04) ? SlotSelect::x8000 : SlotSelect::xC000;
			_prgMode = ((_state.Reg8000 & 0x08) == 0x08) ? PrgMode::_16k : PrgMode::_32k;
			_chrMode = ((_state.Reg8000 & 0x10) == 0x10) ? ChrMode::_4k : ChrMode::_8k;

			_chrReg0 = _state.RegA000 & 0x1F;
			_chrReg1 = _state.RegC000 & 0x1F;
			_prgReg = _state.RegE000 & 0x0F;

			uint8_t extraReg = _lastChrReg == MMC1Registers::RegC000 && _chrMode == ChrMode::_4k ? _chrReg1 : _chrReg0;
			uint8_t prgBankSelect = 0;
			if(_prgSize == 0x80000) {
				//512kb carts use bit 7 of $A000/$C000 to select page
				//This is used for SUROM (Dragon Warrior 3/4, Dragon Quest 4)
				prgBankSelect = extraReg & 0x10;
			} 

			MemoryAccessType access = _wramDisable && !_forceWramOn ? MemoryAccessType::NoAccess : MemoryAccessType::ReadWrite;
			PrgMemoryType memType = HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam;
			if(_saveRamSize + _workRamSize > 0x4000) {
				//SXROM, 32kb of save ram
				SetCpuMemoryMapping(0x6000, 0x7FFF, (extraReg >> 2) & 0x03, memType, access);
			} else if(_saveRamSize + _workRamSize > 0x2000) {
				if(_saveRamSize == 0x2000 && _workRamSize == 0x2000) {
					//SOROM, half of the 16kb ram is battery backed
					SetCpuMemoryMapping(0x6000, 0x7FFF, 0, (extraReg >> 3) & 0x01 ? PrgMemoryType::WorkRam : PrgMemoryType::SaveRam, access);
				} else {
					//Unknown, shouldn't happen
					SetCpuMemoryMapping(0x6000, 0x7FFF, (extraReg >> 2) & 0x01, memType, access);
				}
			} else {
				//Everything else - 8kb of work or save ram
				SetCpuMemoryMapping(0x6000, 0x7FFF, 0, memType, access);
			}

			if(_romInfo.SubMapperID == 5) {
				//SubMapper 5
				//"001: 5 Fixed PRG    SEROM, SHROM, SH1ROM use a fixed 32k PRG ROM with no banking support.
				SelectPrgPage2x(0, 0);
			} else {
				if(_prgMode == PrgMode::_32k) {
					SelectPrgPage2x(0, (_prgReg & 0xFE) | prgBankSelect);
				} else if(_prgMode == PrgMode::_16k) {
					if(_slotSelect == SlotSelect::x8000) {
						SelectPRGPage(0, _prgReg | prgBankSelect);
						SelectPRGPage(1, 0x0F | prgBankSelect);
					} else if(_slotSelect == SlotSelect::xC000) {
						SelectPRGPage(0, 0 | prgBankSelect);
						SelectPRGPage(1, _prgReg | prgBankSelect);
					}
				}
			} 

			if(_chrMode == ChrMode::_8k) {
				SelectCHRPage(0, _chrReg0 & 0x1E);
				SelectCHRPage(1, (_chrReg0 & 0x1E) + 1);
			} else if(_chrMode == ChrMode::_4k) {
				SelectCHRPage(0, _chrReg0);
				SelectCHRPage(1, _chrReg1);
			}
		}

		virtual void StreamState(bool saving) override
		{
			BaseMapper::StreamState(saving);
			Stream(_state.Reg8000, _state.RegA000, _state.RegC000, _state.RegE000, _writeBuffer, _shiftCount, _lastWriteCycle, _lastChrReg);
			if(!saving) {
				UpdateState();
			}
		}

		virtual uint16_t GetPRGPageSize() override { return 0x4000; }
		virtual uint16_t GetCHRPageSize() override {	return 0x1000; }

		virtual void InitMapper() override
		{
			_state.Reg8000 = GetPowerOnByte() | 0x0C; //On powerup: bits 2,3 of $8000 are set (this ensures the $8000 is bank 0, and $C000 is the last bank - needed for SEROM/SHROM/SH1ROM which do no support banking)
			_state.RegA000 = GetPowerOnByte();
			_state.RegC000 = GetPowerOnByte();
			_state.RegE000 = (_romInfo.DatabaseInfo.Board.find("MMC1B") != string::npos ? 0x10 : 0x00); //WRAM Disable: enabled by default for MMC1B

			//"MMC1A: PRG RAM is always enabled" - Normally these roms should be classified as mapper 155
			_forceWramOn = (_romInfo.DatabaseInfo.Board.compare("MMC1A") == 0);

			_lastChrReg = MMC1Registers::RegA000;

			UpdateState();
		}

		virtual void WriteRegister(uint16_t addr, uint8_t value) override
		{
			uint64_t currentCycle = _console->GetCpu()->GetCycleCount();
			
			//Ignore write if within 2 cycles of another write (i.e the real write after a dummy write)
			if(currentCycle - _lastWriteCycle >= 2) {
				if(IsBufferFull(value)) {
					switch((MMC1Registers)((addr & 0x6000) >> 13)) {
						case MMC1Registers::Reg8000: _state.Reg8000 = _writeBuffer; break;
						case MMC1Registers::RegA000: 
							_lastChrReg = MMC1Registers::RegA000;
							_state.RegA000 = _writeBuffer; 
							break;

						case MMC1Registers::RegC000: 
							_lastChrReg = MMC1Registers::RegC000;
							_state.RegC000 = _writeBuffer;
							break;

						case MMC1Registers::RegE000: _state.RegE000 = _writeBuffer; break;
					}

					UpdateState();

					//Reset buffer after writing 5 bits
					ResetBuffer();
				}
			}
			_lastWriteCycle = currentCycle;
		}
};
