#include <stdlib.h>

#include "Constants.h"
#include "DataBus.h"
#include "Memory.h"

static Uint8 _memoryIsInAccessCountingContext = 0;
static Uint32 _memoryAccessCounterRead8;
static Uint32 _memoryAccessCounterWrite8;
static Uint16 _memoryAccessStartAddressInclusive;
static Uint16 _memoryAccessSize;

static Uint8* _memory = NULL;
static func_on_memory_write_callback* _onMemoryWriteCallback;

void on_memory_write_NOOP_callback(Uint16 address) {
}

void memory_start(func_on_memory_write_callback onMemoryWriteCallback) {
	if (_memory != NULL) {
		memory_destroy();
	}
	_memory = (Uint8*)malloc(SPECTRUM_ROM_SIZE + SPECTRUM_RAM_SIZE);
	if (_memory == NULL) {
		return;
	}

	_onMemoryWriteCallback = on_memory_write_NOOP_callback;

	if (onMemoryWriteCallback != NULL) {
		_onMemoryWriteCallback = onMemoryWriteCallback;
	}

	// initialize memory bytes
	for (int i = 0; i < SPECTRUM_ROM_SIZE + SPECTRUM_RAM_SIZE; i++) {
		_memory[i] = 0x00;
	}
}

void memory_destroy() {
	if (_memory != NULL) {
		free(_memory);
		_memory = NULL;
	}

	_memoryIsInAccessCountingContext = 0;
	_onMemoryWriteCallback = NULL;
}

void memory_load(Uint8* source, Uint16 offset, Uint16 size) {
	if (offset + size > SPECTRUM_ROM_SIZE + SPECTRUM_RAM_SIZE) {
		return;
	}
	_memoryIsInAccessCountingContext = 0;
	SDL_memcpy(_memory + offset, source, size);
}

inline void _memory_access_record_read8(Uint16 address) {
	if (!_memoryIsInAccessCountingContext) {
		return;
	}

	if (address >= _memoryAccessStartAddressInclusive && address < _memoryAccessStartAddressInclusive + _memoryAccessSize) {
		_memoryAccessCounterRead8++;
	}
}

inline void _memory_access_record_write8(Uint16 address) {
	if (!_memoryIsInAccessCountingContext) {
		return;
	}

	if (address >= _memoryAccessStartAddressInclusive && address < _memoryAccessStartAddressInclusive + _memoryAccessSize) {
		_memoryAccessCounterWrite8++;
	}
}

Uint8 memory_read8(Uint16 address) {
	Uint8 value = _memory[address & 0xffff];
	_memory_access_record_read8(address);
	return value;
}

Uint16 memory_read16(Uint16 address) {
	Uint8 msb = _memory[(address + 1) & 0xffff];
	data_bus_write8(msb);

	_memory_access_record_read8(address);
	_memory_access_record_read8(address+1);
	return _memory[address & 0xffff] + (msb << 8);
}

Uint8 memory_peek8(Uint16 address) {
	// this is a peek, and INTENDED to not record a memory access
	Uint8 value = _memory[address & 0xffff];
	return value;
}

void memory_write8(Uint16 address, Uint8 value) {
	data_bus_write8(value);

	_memory_access_record_write8(address);

	if (address < SPECTRUM_ROM_SIZE) {
		return;
	}

	_memory[address] = value;
	_onMemoryWriteCallback(address);
}

void memory_write16(Uint16 address, Uint16 value) {
	memory_write8(address, (Uint8)value);			// LSB
	memory_write8(address+1, (Uint8)(value >> 8));	// MSB
}

void memory_enter_access_counting_context(Uint16 startAddressInclusive, Uint16 size) {
	_memoryAccessStartAddressInclusive = startAddressInclusive;
	_memoryAccessSize = size;

	_memoryAccessCounterRead8 = 0;
	_memoryAccessCounterWrite8 = 0;

	_memoryIsInAccessCountingContext = 1;
}

void memory_leave_access_counting_context() {
	_memoryIsInAccessCountingContext = 0;
}

int memory_compute_hash(Uint16 startAddress, Uint16 size) {
	int result = 0;
	for (int i = startAddress; i < startAddress + size; i++) {
		result = 31 * result + _memory[i];
	}

	return result;
}

Uint32 memory_peek_access_counting_context() {
	if (!_memoryIsInAccessCountingContext) {
		return 0;
	}

	Uint32 total = _memoryAccessCounterRead8 + _memoryAccessCounterWrite8;
	return total;
}

void memory_dump(Uint8* destination, Uint16 startAddress, Uint16 size)
{
	_memoryIsInAccessCountingContext = 0;
	while (size--) {
		*destination++ = _memory[startAddress++];
	}
}