#include "Contention.h"
#include "Timing.h"
#include "Memory.h"
#include "Cpu.h"
#include "Io.h"

static volatile Uint8 _isInContext = 0;
static volatile Uint64 _tstatesElapsedAtContextStart;
static volatile Uint64 _contextCount;

void _contention_try_enter_context(Uint64 tstatesElapsedInScanline) {
	// given how zxian counts tstates relative to scan lines, this gives the best 
	// match to 14335 as the first tstate since interrupt occurrence when a contention
	// context can be entered
	if (tstatesElapsedInScanline >= TIMING_PAPER_TSTATES) {
		return;
	}

	// a context is one CPU instruction long
	
	// we set ourselves and dependencies up to capture all information needed 
	// to later compute contention delay (in tstates)
	_tstatesElapsedAtContextStart = tstatesElapsedInScanline;

	memory_enter_access_counting_context(0x4000, 0x4000);
	io_enter_access_capture_context();

	_isInContext = 1;
	_contextCount++;
}

enum contentionIoPortAccessType {
	IoPortContentionNoAccess,
	IoPortContentionByAddress,
	IoPortContentionByLowestBit,
};

struct contentionData {
	Uint32 contendedMemoryAccessCount;
	enum contentionIoPortAccessType contendedIoPortAccess;
};

struct contentionData _contention_get_current() {
	struct contentionData result;

	// memory
	result.contendedMemoryAccessCount = memory_peek_access_counting_context();

	// IO
	result.contendedIoPortAccess = IoPortContentionNoAccess;
	Uint16 ioAccessedPortAddress = 0;
	if (io_peek_access_capture_context(&ioAccessedPortAddress)) {
		if (ioAccessedPortAddress >= 0x4000 && ioAccessedPortAddress <= 0x7fff) {
			result.contendedIoPortAccess = IoPortContentionByAddress;
		}
		else if ((ioAccessedPortAddress & 1) == 0) {
			result.contendedIoPortAccess = IoPortContentionByLowestBit;
		}
	}

	return result;
}

void _contention_leave_context() {
	_isInContext = 0;

	io_leave_access_capture_context();
	memory_leave_access_counting_context();
}

void contention_before_cpu_instruction_executed(Uint64 tstatesElapsedInScanline) {
	_contention_try_enter_context(tstatesElapsedInScanline);	
}

Uint32 _contention_compute_current(const struct instruction* instruction) {
	if (!_isInContext) {
		return 0;
	}

	struct contentionData data = _contention_get_current();

	// ---------------------------- MEMORY-BASED CONTENTION ----------------------------
	// this section computes delay tstates which are a result of memory contention
	// ---------------------------------------------------------------------------------

	// DETERMINED EXPERIMENTALLY
	// this is the default multiplier (delay tstates/contended tstate) for memory contention
	Uint16 defaultMemoryMultiplier = 4;

	Uint32 memoryContendedTstates = data.contendedMemoryAccessCount * defaultMemoryMultiplier;
	// some specific instructions use other multipliers, handled below

	Uint32 stringOperationDivisor = 5;
	if (instruction->prefix.type == ED) {
		switch (instruction->opcodeValue)
		{
		case 0xa0:
			// ldi - adjustment value obtained empirically from Mask III menu
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xa1:
			// cpi
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xa2:
			// ini
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xa3:
			// outi
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xa8:
			// ldd
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xa9:
			// cpd
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xaa:
			// ind
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;
		case 0xab:
			// outd
			memoryContendedTstates = data.contendedMemoryAccessCount * 8 + (_contextCount % stringOperationDivisor == 0 ? 1 : 0);
			break;

		case 0xb0:
			// ldir - adjustment value obtained empirically from Mask III menu, Manic Miner
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xb1:
			// cpir
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xb2:
			// inir
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xb3:
			// otir
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xb8:
			// lddr
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xb9:
			// cpdr
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xba:
			// indr
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;
		case 0xbb:
			// otdr
			memoryContendedTstates = data.contendedMemoryAccessCount * 9;
			break;

		default:
			break;
		}
	}

	if (instruction->prefix.type == NoPrefix) {
		switch (instruction->opcodeValue)
		{
		case 0x10:
			// djnz - adjustment value obtained empirically from Skool Daze bell sound
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;
		case 0x18:
			// jr
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;
		case 0x20:
			// jr nz
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;
		case 0x28:
			// jr z
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;
		case 0x30:
			// jr nc
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;
		case 0x34:
			// inc (HL) - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0x35:
			// dec (HL) - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0x38:
			// jr c
			memoryContendedTstates = data.contendedMemoryAccessCount * 8;
			break;

		case 0xc1:
			// pop BC - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xc5:
			// push BC - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xd1:
			// pop DE - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xd5:
			// push DE - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xe1:
			// pop HL - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xe5:
			// push HL - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xf1:
			// pop AF - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;
		case 0xf5:
			// push AF - adjustment value obtained empirically from Shock 3 demo, where it was shown that
			//          when it's used repeatedly to draw a screen, a low value is closer to the truth
			memoryContendedTstates = data.contendedMemoryAccessCount * 2;
			break;

		default:
			break;
		}
	}

	// ------------------------------ IO-BASED CONTENTION ------------------------------
	// this section computes delay tstates which are a result of IO port contention
	// ---------------------------------------------------------------------------------

	/*
		Address between 0x4000
		and 0x7fff?				Low bit 	Contention pattern	our enum					notes
		----------------------	-------		------------------	--------					--------
		No 						Reset 		N:1, C:3			IoPortContentionByLowestBit	most likely, because of speaker output
		No 						Set 		N:4					IoPortContentionNoAccess
		Yes 					Reset 		C:1, C:3			IoPortContentionByAddress
		Yes 					Set 		C:1, C:1, C:1, C:1	IoPortContentionByAddress

		Contention pattern: N=number of uncontended tstates, C:x= ONE contestable tstate, followed by x uncontestable tstates
	*/

	// DETERMINED EXPERIMENTALLY
	// this is the default multiplier (delay tstates/contended tstate) for IO contention
	Uint16 defaultIOMultiplier = 1;

	Uint32 ioContendedTstates = 0;
	switch (data.contendedIoPortAccess)
	{
	case IoPortContentionByAddress:
		ioContendedTstates = 4 * defaultIOMultiplier;
		break;
	case IoPortContentionByLowestBit:
		ioContendedTstates = 1 * defaultIOMultiplier;
		break;
	default:
		break;
	}

	Uint32 totalContendedTstates = memoryContendedTstates + ioContendedTstates;
	
	if (totalContendedTstates > 0) {
		//cpu_log_instruction(instruction);
	}

	return totalContendedTstates;
}

Uint32 contention_finalize_instruction(const struct instruction* instruction) {
	Uint32 tstates = _contention_compute_current(instruction);
	_contention_leave_context();
	return tstates;
}

Uint32 contention_get_contention_for_inflight_instruction(const struct instruction* instruction) {
	Uint32 tstates = _contention_compute_current(instruction);
	return tstates;
}

void contention_start() {
	_isInContext = 0;
	_contextCount = 0;
}

void contention_destroy() {
	_isInContext = 0;
}
