#include "stdafx.h"
#include "MemoryAccessCounter.h"
#include "MemoryManager.h"
#include "DebugBreakHelper.h"
#include "Debugger.h"
#include "MemoryDumper.h"
#include "Spc.h"
#include "Sa1.h"
#include "Gsu.h"
#include "Cx4.h"
#include "Gameboy.h"
#include "BaseCartridge.h"

MemoryAccessCounter::MemoryAccessCounter(Debugger* debugger, Console *console)
{
	_debugger = debugger;
	_memoryManager = console->GetMemoryManager().get();
	_spc = console->GetSpc().get();
	_sa1 = console->GetCartridge()->GetSa1();
	_gsu = console->GetCartridge()->GetGsu();
	_cx4 = console->GetCartridge()->GetCx4();
	_gameboy = console->GetCartridge()->GetGameboy();

	for(int i = (int)SnesMemoryType::PrgRom; i < (int)SnesMemoryType::Register; i++) {
		uint32_t memSize = _debugger->GetMemoryDumper()->GetMemorySize((SnesMemoryType)i);
		_counters[i].reserve(memSize);
		for(uint32_t j = 0; j < memSize; j++) {
			_counters[i].push_back({ j });
		}
	}
}

bool MemoryAccessCounter::IsAddressUninitialized(AddressInfo& addressInfo)
{
	if(!DebugUtilities::IsRomMemory(addressInfo.Type)) {
		return _counters[(int)addressInfo.Type][addressInfo.Address].WriteCount == 0;
	}
	return false;
}

uint64_t MemoryAccessCounter::GetReadCount(AddressInfo& addressInfo)
{
	return _counters[(int)addressInfo.Type][addressInfo.Address].ReadCount;
}

bool MemoryAccessCounter::ProcessMemoryRead(AddressInfo &addressInfo, uint64_t masterClock)
{
	if(addressInfo.Address < 0) {
		return false;
	}

	AddressCounters& counts = _counters[(int)addressInfo.Type][addressInfo.Address];
	counts.ReadCount++;
	counts.ReadStamp = masterClock;
	if(counts.WriteCount == 0 && IsAddressUninitialized(addressInfo)) {
		//Mark address as read before being written to (if trying to read/execute)
		counts.UninitRead = true;
		return true;
	}
	return false;
}

void MemoryAccessCounter::ProcessMemoryWrite(AddressInfo& addressInfo, uint64_t masterClock)
{
	if(addressInfo.Address < 0) {
		return;
	}

	AddressCounters& counts = _counters[(int)addressInfo.Type][addressInfo.Address];
	counts.WriteCount++;
	counts.WriteStamp = masterClock;
}

void MemoryAccessCounter::ProcessMemoryExec(AddressInfo& addressInfo, uint64_t masterClock)
{
	if(addressInfo.Address < 0) {
		return;
	}

	AddressCounters& counts = _counters[(int)addressInfo.Type][addressInfo.Address];
	counts.ExecCount++;
	counts.ExecStamp = masterClock;
}

void MemoryAccessCounter::ResetCounts()
{
	DebugBreakHelper helper(_debugger);
	for(int i = 0; i < (int)SnesMemoryType::Register; i++) {
		for(uint32_t j = 0; j < _counters[i].size(); j++) {
			_counters[i][j] = { j };
		}
	}
}

void MemoryAccessCounter::GetAccessCounts(uint32_t offset, uint32_t length, SnesMemoryType memoryType, AddressCounters counts[])
{
	switch(memoryType) {
		case SnesMemoryType::CpuMemory:
			for(uint32_t i = 0; i < length; i++) {
				AddressInfo info = _memoryManager->GetMemoryMappings()->GetAbsoluteAddress(offset + i);
				if(info.Address >= 0) {
					counts[i] = _counters[(int)info.Type][info.Address];
				}
			}
			break;

		case SnesMemoryType::SpcMemory:
			for(uint32_t i = 0; i < length; i++) {
				AddressInfo info = _spc->GetAbsoluteAddress(offset + i);
				if(info.Address >= 0) {
					counts[i] = _counters[(int)info.Type][info.Address];
				}
			}
			break;

		case SnesMemoryType::Sa1Memory:
			if(_sa1) {
				for(uint32_t i = 0; i < length; i++) {
					AddressInfo info = _sa1->GetMemoryMappings()->GetAbsoluteAddress(offset + i);
					if(info.Address >= 0) {
						counts[i] = _counters[(int)info.Type][info.Address];
					}
				}
			}
			break;

		case SnesMemoryType::GsuMemory:
			if(_gsu) {
				for(uint32_t i = 0; i < length; i++) {
					AddressInfo info = _gsu->GetMemoryMappings()->GetAbsoluteAddress(offset + i);
					if(info.Address >= 0) {
						counts[i] = _counters[(int)info.Type][info.Address];
					}
				}
			}
			break;

		case SnesMemoryType::Cx4Memory:
			if(_cx4) {
				for(uint32_t i = 0; i < length; i++) {
					AddressInfo info = _cx4->GetMemoryMappings()->GetAbsoluteAddress(offset + i);
					if(info.Address >= 0) {
						counts[i] = _counters[(int)info.Type][info.Address];
					}
				}
			}
			break;
		
		case SnesMemoryType::GameboyMemory:
			if(_gameboy) {
				for(uint32_t i = 0; i < length; i++) {
					AddressInfo info = _gameboy->GetAbsoluteAddress(offset + i);
					if(info.Address >= 0) {
						counts[i] = _counters[(int)info.Type][info.Address];
					}
				}
			}
			break;

		default:
			memcpy(counts, _counters[(int)memoryType].data() + offset, length * sizeof(AddressCounters));
			break;
	}
}
