#include "stdafx.h"

#include "CheatManager.h"
#include "Console.h"
#include "BaseMapper.h"
#include "MessageManager.h"
#include "NotificationManager.h"

CheatManager::CheatManager(shared_ptr<Console> console)
{
	_console = console;

	for(int i = 0; i <= 0xFFFF; i++) {
		_relativeCheatCodes.push_back(nullptr);
	}
}

uint32_t CheatManager::DecodeValue(uint32_t code, uint32_t* bitIndexes, uint32_t bitCount)
{
	uint32_t result = 0;
	for(uint32_t i = 0; i < bitCount; i++) {
		result <<= 1;
		result |= (code >> bitIndexes[i]) & 0x01;
	}
	return result;
}

CodeInfo CheatManager::GetGGCodeInfo(string ggCode)
{
	string ggLetters = "APZLGITYEOXUKSVN";
	
	uint32_t rawCode = 0;
	for(size_t i = 0, len = ggCode.size(); i < len; i++) {
		rawCode |= ggLetters.find(ggCode[i]) << (i * 4);
	}

	CodeInfo code = { };
	code.IsRelativeAddress = true;
	code.CompareValue = -1;
	uint32_t addressBits[15] = { 14, 13, 12, 19, 22, 21, 20, 7, 10, 9, 8, 15, 18, 17, 16 };
	uint32_t valueBits[8] = { 3, 6, 5, 4, 23, 2, 1, 0 };
	if(ggCode.size() == 8) {
		//Bit 5 of the value is stored in a different location for 8-character codes
		valueBits[4] = 31;

		uint32_t compareValueBits[8] = { 27, 30, 29, 28, 23, 26, 25, 24 };
		code.CompareValue = DecodeValue(rawCode, compareValueBits, 8);
	}
	code.Address = DecodeValue(rawCode, addressBits, 15) + 0x8000;
	code.Value = DecodeValue(rawCode, valueBits, 8);

	return code;
}

CodeInfo CheatManager::GetPARCodeInfo(uint32_t parCode)
{
	uint32_t shiftValues[31] = {
		3, 13, 14, 1, 6, 9, 5, 0, 12, 7, 2, 8, 10, 11, 4,	//address
		19, 21, 23, 22, 20, 17, 16, 18,							//compare
		29, 31, 24, 26, 25, 30, 27, 28							//value
	};
	uint32_t key = 0x7E5EE93A;
	uint32_t xorValue = 0x5C184B91;

	//Throw away bit 0, not used.
	parCode >>= 1;

	uint32_t result = 0;
	for(int32_t i = 30; i >= 0; i--) {
		if(((key ^ parCode) >> 30) & 0x01) {
			result |= 0x01 << shiftValues[i];
			key ^= xorValue;
		}
		parCode <<= 1;
		key <<= 1;
	}

	CodeInfo code = { };
	code.IsRelativeAddress = true;
	code.Address = (result & 0x7fff) + 0x8000;
	code.Value = (result >> 24) & 0xFF;
	code.CompareValue = (result >> 16) & 0xFF;
	
	return code;
}

void CheatManager::AddCode(CodeInfo &code)
{
	if(code.IsRelativeAddress) {
		if(code.Address > 0xFFFF) {
			//Invalid cheat, ignore it
			return;
		}

		if(_relativeCheatCodes[code.Address] == nullptr) {
			_relativeCheatCodes[code.Address].reset(new vector<CodeInfo>());
		}
		_relativeCheatCodes[code.Address]->push_back(code);
	} else {
		_absoluteCheatCodes.push_back(code);
	}
	_hasCode = true;
	_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::CheatAdded);
}

void CheatManager::AddGameGenieCode(string code)
{
	CodeInfo info = GetGGCodeInfo(code);
	AddCode(info);
}

void CheatManager::AddProActionRockyCode(uint32_t code)
{
	CodeInfo info = GetPARCodeInfo(code);
	AddCode(info);
}

void CheatManager::AddCustomCode(uint32_t address, uint8_t value, int32_t compareValue, bool isRelativeAddress)
{
	CodeInfo code;
	code.Address = address;
	code.Value = value;
	code.CompareValue = compareValue;
	code.IsRelativeAddress = isRelativeAddress;

	AddCode(code);
}

void CheatManager::ClearCodes()
{
	bool cheatRemoved = false;

	for(int i = 0; i <= 0xFFFF; i++) {
		if(!_relativeCheatCodes[i]) {
			cheatRemoved = true;
		}
		_relativeCheatCodes[i].reset();
	}

	cheatRemoved |= _absoluteCheatCodes.size() > 0;
	_absoluteCheatCodes.clear();
	_hasCode = false;

	if(cheatRemoved) {
		_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::CheatRemoved);
	}
}

void CheatManager::ApplyCodes(uint16_t addr, uint8_t &value)
{
	if(!_hasCode) {
		return;
	}

	if(_relativeCheatCodes[addr] != nullptr) {
		for(uint32_t i = 0, len = i < _relativeCheatCodes[addr]->size(); i < len; i++) {
			CodeInfo code = _relativeCheatCodes[addr]->at(i);
			if(code.CompareValue == -1 || code.CompareValue == value) {
				value = code.Value;
				return;
			}
		}
	} else if(!_absoluteCheatCodes.empty()) {
		int32_t absAddr = _console->GetMapper()->ToAbsoluteAddress(addr);
		if(absAddr >= 0) {
			for(CodeInfo &code : _absoluteCheatCodes) {
				if(code.Address == (uint32_t)absAddr && (code.CompareValue == -1 || code.CompareValue == value)) {
					value = code.Value;
					return;
				}
			}
		}
	}
}

vector<CodeInfo> CheatManager::GetCheats()
{
	//Used by NetPlay
	vector<CodeInfo> cheats;
	for(unique_ptr<vector<CodeInfo>> &codes : _relativeCheatCodes) {
		if(codes) {
			std::copy(codes.get()->begin(), codes.get()->end(), std::back_inserter(cheats));
		}
	}
	std::copy(_absoluteCheatCodes.begin(), _absoluteCheatCodes.end(), std::back_inserter(cheats));
	return cheats;
}

void CheatManager::SetCheats(CheatInfo cheats[], uint32_t length)
{
	_console->Pause();

	ClearCodes();

	for(uint32_t i = 0; i < length; i++) {
		CheatInfo &cheat = cheats[i];
		switch(cheat.Type) {
			case CheatType::Custom: AddCustomCode(cheat.Address, cheat.Value, cheat.UseCompareValue ? cheat.CompareValue : -1, cheat.IsRelativeAddress); break;
			case CheatType::GameGenie: AddGameGenieCode(cheat.GameGenieCode);	break;
			case CheatType::ProActionRocky: AddProActionRockyCode(cheat.ProActionRockyCode); break;
		}
	}

	_console->Resume();
}

void CheatManager::SetCheats(vector<CodeInfo> &cheats)
{
	//Used by NetPlay
	ClearCodes();

	if(cheats.size() > 0) {
		MessageManager::DisplayMessage("Cheats", cheats.size() > 1 ? "CheatsApplied" : "CheatApplied", std::to_string(cheats.size()));
		for(CodeInfo &cheat : cheats) {
			AddCode(cheat);
		}
	}
}