#include "CwCheat.h"
#include "../Core/CoreTiming.h"
#include "../Core/CoreParameter.h"
#include "StringUtils.h"
#include "Common/FileUtil.h"
#include "Config.h"
#include "MIPS/MIPS.h"
#include "Core/Config.h"

const static std::string CHEATS_DIR = "cheats";

static std::string activeCheatFile;
static int CheatEvent = -1;

static CWCheatEngine *cheatEngine;

void hleCheat(u64 userdata, int cyclesLate);
void trim2(std::string& str);

void __CheatInit() {
	//Moved createFullPath to CheatInit from the constructor because it spams the log and constantly checks if exists. In here, only checks once.
	std::string gameTitle = g_paramSFO.GetValueString("DISC_ID") + " - " + g_paramSFO.GetValueString("TITLE");
	//These 3 statements clean up the title, which sometimes contain characters that will cause problems when creating the .ini
	if (gameTitle.find("™")!= std::string::npos) {
		gameTitle.erase (gameTitle.find("™"), std::string::npos);
	}
	while (gameTitle.find("/")!= std::string::npos) {
		gameTitle.replace (gameTitle.find("/"),1," ");
	}
	if (gameTitle.find(":")!= std::string::npos) {
		gameTitle.replace (gameTitle.find(":"),1," ");
	}
	activeCheatFile = CHEATS_DIR + "/" + gameTitle +".ini";

	File::CreateFullPath(CHEATS_DIR);
	if (g_Config.bEnableCheats) {
		if (!File::Exists(activeCheatFile)) {
			File::CreateEmptyFile(activeCheatFile);
		}

		cheatEngine = new CWCheatEngine();

		cheatEngine->CreateCodeList();
		g_Config.bReloadCheats = false;
		CheatEvent = CoreTiming::RegisterEvent("CheatEvent", &hleCheat);
		CoreTiming::ScheduleEvent(msToCycles(77), CheatEvent, 0);
	}
}

void __CheatShutdown() {
	if (cheatEngine != 0) {
		cheatEngine->Exit();
		delete cheatEngine;
		cheatEngine = 0;
	}
}

void hleCheat(u64 userdata, int cyclesLate) {
	CoreTiming::ScheduleEvent(msToCycles(77), CheatEvent, 0);

	if (g_Config.bReloadCheats == true) { //Checks if the "reload cheats" button has been pressed.
		cheatEngine->CreateCodeList();
		g_Config.bReloadCheats = false;
	}
	if (g_Config.bEnableCheats && cheatEngine) {
		cheatEngine->Run();
	}
}

CWCheatEngine::CWCheatEngine() {

}

void CWCheatEngine::Exit() {
	exit2 = true;
}
void CWCheatEngine::CreateCodeList() { //Creates code list to be used in function GetNextCode
	initialCodesList = GetCodesList();
	std::string currentcode, codename;
	std::vector<std::string> codelist;

	for (size_t i = 0; i < initialCodesList.size(); i ++) {

		if (initialCodesList[i].substr(0,3) == "_C1") {
			cheatEnabled = true;
			codename = initialCodesList[i];
			codename.erase (codename.begin(), codename.begin()+4);
			codeNameList.push_back(codename); //Import names for GUI
			continue;
		}
		if (initialCodesList[i].substr(0,2) == "_L") {
			if (cheatEnabled == true) {
				currentcode = initialCodesList[i];
				currentcode.erase(currentcode.begin(), currentcode.begin() + 3);
				codelist.push_back(currentcode);
			}
			continue;
		}
		if (initialCodesList[i].substr(0,3) == "_C0") {
			cheatEnabled = false;
			codename = initialCodesList[i];
			codename.erase (codename.begin(), codename.begin()+4);
			codeNameList.push_back(codename); //Import names for GUI
			continue;
		}
	}
	parts = makeCodeParts(codelist);
}
inline std::vector<std::string> makeCodeParts(std::vector<std::string> CodesList) { //Takes a single code line and creates a two-part vector for all codes. Feeds to CreateCodeList
	std::string currentcode;
	std::vector<std::string> finalList;
	char split_char = '\n';
	char empty = ' ';
	for (size_t i = 0; i < CodesList.size(); i++) {
		currentcode = CodesList[i];
		for (size_t j=0; j < currentcode.length(); j++) {
			if (currentcode[j] == empty) {
				currentcode[j] = '\n';
			}
		}
		trim2(currentcode);
		std::istringstream iss(currentcode);
		std::string each;
		while (std::getline(iss, each, split_char)) {
			finalList.push_back(each);
		}
	}
	return finalList;
}
std::vector<int> CWCheatEngine::GetNextCode() { // Feeds a size-2 vector of ints to Run() which contains the address and value of one cheat.
	std::string code1;
	std::string code2;
	std::vector<std::string> splitCode;
	std::vector<int> finalCode;
	std::string modifier2 = "0";
	while (true)  {
		if (currentCode >= parts.size()) {
			code1.clear();
			code2.clear();
			break;
		}
		code1 = parts[currentCode++];
		trim2(code1);
		code2 = parts[currentCode++];
		trim2(code2);
		splitCode.push_back(code1);
		splitCode.push_back(code2);

		int var1 = (int) parseHexLong(splitCode[0]);
		int var2 = (int) parseHexLong(splitCode[1]);
		finalCode.push_back(var1);
		finalCode.push_back(var2);
		if (splitCode[0].substr(0,1) == modifier2) {
			break;
		}
	}
	return finalCode;
}

void CWCheatEngine::SkipCodes(int count) {
	for (int i = 0; i < count; i ++) {
		if (GetNextCode()[0] == 0) {
			break;
		}
	}
}

void CWCheatEngine::SkipAllCodes() {
	currentCode = codes.size();
}

int CWCheatEngine::GetAddress(int value) { //Returns static address used by ppsspp. Some games may not like this, and causes cheats to not work without offset
	return (value + 0x08800000) & 0x3FFFFFFF;
}


inline void trim2(std::string& str) {
	size_t pos = str.find_last_not_of(' ');
	if(pos != std::string::npos) {
		str.erase(pos + 1);
		pos = str.find_first_not_of(' ');
		if(pos != std::string::npos) str.erase(0, pos);
	}
	else str.erase(str.begin(), str.end());
}

std::vector<std::string> CWCheatEngine::GetCodesList() { //Reads the entire cheat list from the appropriate .ini.
	std::string line;
	std::vector<std::string> codesList;  // Read from INI here
	std::ifstream list(activeCheatFile.c_str());
	for (int i = 0; !list.eof(); i ++) {
		getline(list, line, '\n');
		codesList.push_back(line);
	}
	for(size_t i = 0; i < codesList.size(); i++) {
		trim2(codesList[i]);
	}
	return codesList;
}

void CWCheatEngine::Run() {
	exit2 = false;
	while (!exit2) {
		currentCode = 0;

		while (true) {
			std::vector<int> code = GetNextCode();
			if (code.size() < 2) {
				Exit();
				break;
			}

			int value;
			unsigned int comm = code[0];
			int arg = code[1];
			int addr = GetAddress(comm & 0x0FFFFFFF);

			switch (comm >> 28) {
			case 0: // 8-bit write.
				if (Memory::IsValidAddress(addr)){
					Memory::Write_U8((u8) arg, addr);
				}
				break;
			case 0x1: // 16-bit write
				if (Memory::IsValidAddress(addr)){
					Memory::Write_U16((u16) arg, addr);
				}
				break;
			case 0x2: // 32-bit write
				if (Memory::IsValidAddress(addr)){
					Memory::Write_U32((u32) arg, addr);
				}
				break;
			case 0x3: // Increment/Decrement
				{
					addr = GetAddress(arg);
					value = 0;
					int increment = 0;
					// Read value from memory
					switch ((comm >> 20) & 0xF) {
					case 1:
					case 2: // 8-bit
						value = Memory::Read_U8(addr);
						increment = comm & 0xFF;
						break;
					case 3:
					case 4: // 16-bit
						value = Memory::Read_U16(addr);
						increment = comm & 0xFFFF;
						break;
					case 5:
					case 6: // 32-bit
						value = Memory::Read_U32(addr);
						code = GetNextCode();
						if ( code[0] != NULL) {
							increment = code[0];
						}
						break;
					}
					// Increment/Decrement value
					switch ((comm >> 20) & 0xF) {
					case 1:
					case 3:
					case 5: // increment
						value += increment;
						break;
					case 2:
					case 4:
					case 6: // Decrement
						value -= increment;
						break;
					}
					// Write value back to memory
					switch ((comm >> 20) & 0xF) {
					case 1:
					case 2: // 8-bit
						Memory::Write_U8((u8) value, addr);
						break;
					case 3:
					case 4: // 16-bit
						Memory::Write_U16((u16) value, addr);
						break;
					case 5:
					case 6: // 32-bit
						Memory::Write_U32((u32) value, addr);
						break;
					}
					break;
				}
			case 0x4: // 32-bit patch code
				code = GetNextCode();
				if (code[0] != 0) {
					int data = code[0];
					int dataAdd = code[1];

					int maxAddr = (arg >> 16) & 0xFFFF;
					int stepAddr = (arg & 0xFFFF) * 4;
					for (int a  = 0; a < maxAddr; a++) {
						if (Memory::IsValidAddress(addr)) {
							Memory::Write_U32((u32) data, addr);
						}
						addr += stepAddr;
						data += dataAdd;
					}
				}
				break;
			case 0x5: // Memcpy command
				code = GetNextCode();
				if (code[0] != NULL) {
					int destAddr = code[0];
					if (Memory::IsValidAddress(addr) && Memory::IsValidAddress(destAddr)) {
						Memory::Memcpy(destAddr, Memory::GetPointer(addr), arg);
					}
				}
				break;
			case 0x6: // Pointer commands
				code = GetNextCode();
				if (code[0] != NULL) {
					int arg2 = code[0];
					int offset = code[1];
					int baseOffset = (arg2 >> 20) * 4;
					int base = Memory::Read_U32(addr + baseOffset);
					int count = arg2 & 0xFFFF;
					int type = (arg2 >> 16) & 0xF;
					for (int i = 1; i < count; i ++ ) {
						if (i+1 < count) {
							code = GetNextCode();
							int arg3 = code[0];
							int arg4 = code[1];
							int comm3 = arg3 >> 28;
							switch (comm3) {
							case 0x1: // type copy byte
								{
									int srcAddr = Memory::Read_U32(addr) + offset;
									int dstAddr = Memory::Read_U16(addr + baseOffset) + (arg3 & 0x0FFFFFFF);
									Memory::Memcpy(dstAddr, Memory::GetPointer(srcAddr), arg);
									type = -1; //Done
									break; }
							case 0x2:
							case 0x3: // type pointer walk
								{
									int walkOffset = arg3 & 0x0FFFFFFF;
									if (comm3 == 0x3) {
										walkOffset = -walkOffset;
									}
									base = Memory::Read_U32(base + walkOffset);
									int comm4 = arg4 >> 28;
									switch (comm4) {
									case 0x2:
									case 0x3: // type pointer walk
										walkOffset = arg4 & 0x0FFFFFFF;
										if (comm4 == 0x3) {
											walkOffset = -walkOffset;
										}
										base = Memory::Read_U32(base + walkOffset);
										break;
									}
									break; }
							case 0x9: // type multi address write
								base += arg3 & 0x0FFFFFFF;
								arg += arg4;
								break;
							}
						}
					}

					switch (type) {
					case 0: // 8 bit write
						Memory::Write_U8((u8) arg, base + offset);
						break;
					case 1: // 16-bit write
						Memory::Write_U16((u16) arg, base + offset);
						break;
					case 2: // 32-bit write
						Memory::Write_U32((u32) arg, base + offset);
						break;
					case 3: // 8 bit inverse write
						Memory::Write_U8((u8) arg, base - offset);
						break;
					case 4: // 16-bit inverse write
						Memory::Write_U16((u16) arg, base - offset);
						break;
					case 5: // 32-bit inverse write
						Memory::Write_U32((u32) arg, base - offset);
						break;
					case -1: // Operation already performed, nothing to do
						break;
					}
				}
				break;
			case 0x7: // Boolean commands.
				switch (arg >> 16) {
				case 0x0000: // 8-bit OR.
					if (Memory::IsValidAddress(addr)) {
						int val1 = (int) (arg & 0xFF);
						int val2 = (int) Memory::Read_U8(addr);
						Memory::Write_U8((u8) (val1 | val2), addr);
					}
					break;
				case 0x0002: // 8-bit AND.
					if (Memory::IsValidAddress(addr)) {
						int val1 = (int) (arg & 0xFF);
						int val2 = (int) Memory::Read_U8(addr);
						Memory::Write_U8((u8) (val1 & val2), addr);
					}
					break;
				case 0x0004: // 8-bit XOR.
					if (Memory::IsValidAddress(addr)) {
						int val1 = (int) (arg & 0xFF);
						int val2 = (int) Memory::Read_U8(addr);
						Memory::Write_U8((u8) (val1 ^ val2), addr);
					}
					break;
				case 0x0001: // 16-bit OR.
					if (Memory::IsValidAddress(addr)) {
						short val1 = (short) (arg & 0xFFFF);
						short val2 = (short) Memory::Read_U16(addr);
						Memory::Write_U16((u16) (val1 | val2), addr);
					}
					break;
				case 0x0003: // 16-bit AND.
					if (Memory::IsValidAddress(addr)) {
						short val1 = (short) (arg & 0xFFFF);
						short val2 = (short) Memory::Read_U16(addr);
						Memory::Write_U16((u16) (val1 & val2), addr);
					}
					break;
				case 0x0005: // 16-bit OR.
					if (Memory::IsValidAddress(addr)) {
						short val1 = (short) (arg & 0xFFFF);
						short val2 = (short) Memory::Read_U16(addr);
						Memory::Write_U16((u16) (val1 ^ val2), addr);
					}
					break;
				}
				break;
			case 0x8: // 8-bit and 16-bit patch code
				code = GetNextCode();
				if (code[0] != NULL) {
					int data = code[0];
					int dataAdd = code[1];

					bool is8Bit = (data >> 16) == 0x0000;
					int maxAddr = (arg >> 16) & 0xFFFF;
					int stepAddr = (arg & 0xFFFF) * (is8Bit ? 1 : 2);
					for (int a = 0; a < maxAddr; a++) {
						if (Memory::IsValidAddress(addr)) {
							if (is8Bit) {
								Memory::Write_U8((u8) (data & 0xFF), addr);
							}
							else {
								Memory::Write_U16((u16) (data & 0xFFFF), addr);
							}
						}
						addr += stepAddr;
						data += dataAdd;
					}
				}
				break;
			case 0xB: // Time command (not sure what to do?)
				break;
			case 0xC: // Code stopper
				if (Memory::IsValidAddress(addr)) { 
					value = Memory::Read_U32(addr);
					if (value != arg) {
						SkipAllCodes();
					}
				}
				break;
			case 0xD: // Test commands & Jocker codes ( Someone will have to help me with these)
				break;
			case 0xE: // Test commands, multiple skip
				{
					bool is8Bit = (comm >> 24) == 0x1;
					addr = GetAddress(arg & 0x0FFFFFFF);
					if (Memory::IsValidAddress(addr)) {
						int memoryValue = is8Bit ? Memory::Read_U8(addr) : Memory::Read_U16(addr);
						int testValue = comm & (is8Bit ? 0xFF : 0xFFFF);
						bool executeNextLines = false;
						switch ( arg >> 28) {
						case 0x0: // Equal
							executeNextLines = memoryValue == testValue;
							break;
						case 0x1: // Not Equal
							executeNextLines = memoryValue != testValue;
							break;
						case 0x2: // Less Than
							executeNextLines = memoryValue < testValue;
							break;
						case 0x3: // Greater Than
							executeNextLines = memoryValue > testValue;
							break;
						}
						if (!executeNextLines) {
							int skip = (comm >> 16) & (is8Bit ? 0xFF : 0xFFF);
							SkipCodes(skip);
						}
					}
					break;
				}
			}
		}
	}
	// exiting...
	Exit();
}



