#pragma once
#include "stdafx.h"
#include "Snapshotable.h"

//SST39SF040 chip emulation - used by mapper 30 (UNROM512) & mapper 111 (GTROM)
class FlashSST39SF040 : public Snapshotable
{
private:
	enum class ChipMode
	{
		WaitingForCommand,
		Write,
		Erase
	};

	ChipMode _mode = ChipMode::WaitingForCommand;
	uint8_t _cycle = 0;
	uint8_t _softwareId = false;

	//PRG data and size
	uint8_t* _data;
	uint32_t _size;

protected:
	void StreamState(bool saving)
	{
		Stream(_mode, _cycle, _softwareId);
	}

public:
	FlashSST39SF040(uint8_t* data, uint32_t size)
	{
		_data = data;
		_size = size;
	}

	int16_t Read(uint32_t addr)
	{
		if(_softwareId) {
			switch(addr & 0x1FF) {
				case 0x00: return 0xBF;
				case 0x01: return 0xB7;
				default: return 0xFF;
			}
		}
		return -1;
	}

	void ResetState()
	{
		_mode = ChipMode::WaitingForCommand;
		_cycle = 0;
	}

	void Write(uint32_t addr, uint8_t value)
	{
		uint16_t cmd = addr & 0x7FFF;
		if(_mode == ChipMode::WaitingForCommand) {
			if(_cycle == 0) {
				if(cmd == 0x5555 && value == 0xAA) {
					//1st write, $5555 = $AA
					_cycle++;
				} else if(value == 0xF0) {
					//Software ID exit
					ResetState();
					_softwareId = false;
				}
			} else if(_cycle == 1 && cmd == 0x2AAA && value == 0x55) {
				//2nd write, $2AAA = $55
				_cycle++;
			} else if(_cycle == 2 && cmd == 0x5555) {
				//3rd write, determines command type
				_cycle++;
				switch(value) {
					case 0x80: _mode = ChipMode::Erase; break;
					case 0x90: ResetState();  _softwareId = true; break;
					case 0xA0: _mode = ChipMode::Write; break;
					case 0xF0: ResetState(); _softwareId = false; break;
				}
			} else {
				_cycle = 0;
			}
		} else if(_mode == ChipMode::Write) {
			//Write a single byte
			if(addr < _size) {
				_data[addr] &= value;
			}
			ResetState();
		} else if(_mode == ChipMode::Erase) {
			if(_cycle == 3) {
				//4th write for erase command, $5555 = $AA
				if(cmd == 0x5555 && value == 0xAA) {
					_cycle++;
				} else {
					ResetState();
				}
			} else if(_cycle == 4) {
				//5th write for erase command, $2AAA = $55
				if(cmd == 0x2AAA && value == 0x55) {
					_cycle++;
				} else {
					ResetState();
				}
			} else if(_cycle == 5) {
				if(cmd == 0x5555 && value == 0x10) {
					//Chip erase
					memset(_data, 0xFF, _size);
				} else if(value == 0x30) {
					//Sector erase
					uint32_t offset = (addr & 0x7F000);
					if(offset + 0x1000 <= _size) {
						memset(_data + offset, 0xFF, 0x1000);
					}
				}
				ResetState();
			}
		}
	}
};