#include "stdafx.h"
#include <algorithm>
#include "Disassembler.h"
#include "DisassemblyInfo.h"
#include "BaseMapper.h"
#include "MemoryManager.h"
#include "CPU.h"
#include "LabelManager.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/StringUtilities.h"
#include "Debugger.h"
#include "CodeDataLogger.h"

Disassembler::Disassembler(MemoryManager* memoryManager, BaseMapper* mapper, Debugger* debugger)
{
	_debugger = debugger;
	_memoryManager = memoryManager;
	_mapper = mapper;

	BuildOpCodeTables(false);
}

Disassembler::~Disassembler()
{
}

void Disassembler::Reset()
{
	_disassembleCache.clear();
	_disassembleWorkRamCache.clear();
	_disassembleSaveRamCache.clear();
	_disassembleMemoryCache.clear();

	_disassembleCache.insert(_disassembleCache.end(), _mapper->GetMemorySize(DebugMemoryType::PrgRom), shared_ptr<DisassemblyInfo>(nullptr));
	_disassembleWorkRamCache.insert(_disassembleWorkRamCache.end(), _mapper->GetMemorySize(DebugMemoryType::WorkRam), shared_ptr<DisassemblyInfo>(nullptr));
	_disassembleSaveRamCache.insert(_disassembleSaveRamCache.end(), _mapper->GetMemorySize(DebugMemoryType::SaveRam), shared_ptr<DisassemblyInfo>(nullptr));
	_disassembleMemoryCache.insert(_disassembleMemoryCache.end(), 0x800, shared_ptr<DisassemblyInfo>(nullptr));
}

void Disassembler::BuildOpCodeTables(bool useLowerCase)
{
	string opName[256] = {
	//	0			1			2			3			4			5			6			7			8			9			A			B			C			D			E			F
		"BRK",	"ORA",	"STP",	"SLO",	"NOP",	"ORA",	"ASL",	"SLO",	"PHP",	"ORA",	"ASL",	"ANC",	"NOP",	"ORA",	"ASL",	"SLO", //0
		"BPL",	"ORA",	"STP",	"SLO",	"NOP",	"ORA",	"ASL",	"SLO",	"CLC",	"ORA",	"NOP",	"SLO",	"NOP",	"ORA",	"ASL",	"SLO", //1
		"JSR",	"AND",	"STP",	"RLA",	"BIT",	"AND",	"ROL",	"RLA",	"PLP",	"AND",	"ROL",	"ANC",	"BIT",	"AND",	"ROL",	"RLA", //2
		"BMI",	"AND",	"STP",	"RLA",	"NOP",	"AND",	"ROL",	"RLA",	"SEC",	"AND",	"NOP",	"RLA",	"NOP",	"AND",	"ROL",	"RLA", //3
		"RTI",	"EOR",	"STP",	"SRE",	"NOP",	"EOR",	"LSR",	"SRE",	"PHA",	"EOR",	"LSR",	"ALR",	"JMP",	"EOR",	"LSR",	"SRE", //4
		"BVC",	"EOR",	"STP",	"SRE",	"NOP",	"EOR",	"LSR",	"SRE",	"CLI",	"EOR",	"NOP",	"SRE",	"NOP",	"EOR",	"LSR",	"SRE", //5
		"RTS",	"ADC",	"STP",	"RRA",	"NOP",	"ADC",	"ROR",	"RRA",	"PLA",	"ADC",	"ROR",	"ARR",	"JMP",	"ADC",	"ROR",	"RRA", //6
		"BVS",	"ADC",	"STP",	"RRA",	"NOP",	"ADC",	"ROR",	"RRA",	"SEI",	"ADC",	"NOP",	"RRA",	"NOP",	"ADC",	"ROR",	"RRA", //7
		"NOP",	"STA",	"NOP",	"SAX",	"STY",	"STA",	"STX",	"SAX",	"DEY",	"NOP",	"TXA",	"XAA",	"STY",	"STA",	"STX",	"SAX", //8
		"BCC",	"STA",	"STP",	"AHX",	"STY",	"STA",	"STX",	"SAX",	"TYA",	"STA",	"TXS",	"TAS",	"SHY",	"STA",	"SHX",	"AXA", //9
		"LDY",	"LDA",	"LDX",	"LAX",	"LDY",	"LDA",	"LDX",	"LAX",	"TAY",	"LDA",	"TAX",	"LAX",	"LDY",	"LDA",	"LDX",	"LAX", //A
		"BCS",	"LDA",	"STP",	"LAX",	"LDY",	"LDA",	"LDX",	"LAX",	"CLV",	"LDA",	"TSX",	"LAS",	"LDY",	"LDA",	"LDX",	"LAX", //B
		"CPY",	"CMP",	"NOP",	"DCP",	"CPY",	"CMP",	"DEC",	"DCP",	"INY",	"CMP",	"DEX",	"AXS",	"CPY",	"CMP",	"DEC",	"DCP", //C
		"BNE",	"CMP",	"STP",	"DCP",	"NOP",	"CMP",	"DEC",	"DCP",	"CLD",	"CMP",	"NOP",	"DCP",	"NOP",	"CMP",	"DEC",	"DCP", //D
		"CPX",	"SBC",	"NOP",	"ISC",	"CPX",	"SBC",	"INC",	"ISC",	"INX",	"SBC",	"NOP",	"SBC",	"CPX",	"SBC",	"INC",	"ISC", //E
		"BEQ",	"SBC",	"STP",	"ISC",	"NOP",	"SBC",	"INC",	"ISC",	"SED",	"SBC",	"NOP",	"ISC",	"NOP",	"SBC",	"INC",	"ISC"  //F
	};

	bool unofficial[256] = {
		//	0		1		2		3		4		5		6		7		8		9		A		B		C		D		E		F
			false,false,true, true, true, false,false,true, false,false,false,true, true, false,false,true, //0
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true, //1
			false,false,true, true, false,false,false,true, false,false,false,true, false,false,false,true, //2
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true, //3
			false,false,true, true, true, false,false,true, false,false,false,true, false,false,false,true, //4
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true, //5
			false,false,true, true, true, false,false,true, false,false,false,true, false,false,false,true, //6
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true, //7
			true, false,true, true, false,false,false,true, false,true, false,true, false,false,false,true, //8
			false,false,true, true, false,false,false,true, false,false,false,true, true, false,true, true, //9
			false,false,false,true, false,false,false,true, false,false,false,true, false,false,false,true, //A
			false,false,true, true, false,false,false,true, false,false,false,true, false,false,false,true, //B
			false,false,true, true, false,false,false,true, false,false,false,true, false,false,false,true, //C
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true, //D
			false,false,true, true, false,false,false,true, false,false,false,true, false,false,false,true, //E
			false,false,true, true, true, false,false,true, false,false,true, true, true, false,false,true  //F
	};

	typedef AddrMode M;
	AddrMode opMode[] = {
		//	0			1				2			3				4				5				6				7				8			9			A			B			C			D			E			F
		M::Imp,	M::IndX,		M::None,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Acc,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//0
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//1
		M::Abs,	M::IndX,		M::None,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Acc,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//2
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//3
		M::Imp,	M::IndX,		M::None,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Acc,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//4
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//5
		M::Imp,	M::IndX,		M::None,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Acc,	M::Imm,	M::Ind,	M::Abs,	M::Abs,	M::Abs,	//6
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//7
		M::Imm,	M::IndX,		M::Imm,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Imp,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//8
		M::Rel,	M::IndYW,	M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroY,	M::ZeroY,	M::Imp,	M::AbsYW,M::Imp,	M::AbsYW,M::AbsXW,M::AbsXW,M::AbsYW,M::AbsYW,//9
		M::Imm,	M::IndX,		M::Imm,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Imp,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//A
		M::Rel,	M::IndY,		M::None,	M::IndY,		M::ZeroX,	M::ZeroX,	M::ZeroY,	M::ZeroY,	M::Imp,	M::AbsY,	M::Imp,	M::AbsY,	M::AbsX,	M::AbsX,	M::AbsY,	M::AbsY,	//B
		M::Imm,	M::IndX,		M::Imm,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Imp,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//C
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//D
		M::Imm,	M::IndX,		M::Imm,	M::IndX,		M::Zero,		M::Zero,		M::Zero,		M::Zero,		M::Imp,	M::Imm,	M::Imp,	M::Imm,	M::Abs,	M::Abs,	M::Abs,	M::Abs,	//E
		M::Rel,	M::IndY,		M::None,	M::IndYW,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::ZeroX,	M::Imp,	M::AbsY,	M::Imp,	M::AbsYW,M::AbsX,	M::AbsX,	M::AbsXW,M::AbsXW,//F
	};

	for(int i = 0; i < 256; i++) {
		if(useLowerCase) {
			string name = opName[i];
			std::transform(name.begin(), name.end(), name.begin(), ::tolower);
			DisassemblyInfo::OPName[i] = name + (unofficial[i] ? "* " : " ");
		} else {
			DisassemblyInfo::OPName[i] = opName[i] + (unofficial[i] ? "* " : " ");
		}

		DisassemblyInfo::IsUnofficialCode[i] = unofficial[i];
		DisassemblyInfo::OPMode[i] = opMode[i];
		switch(DisassemblyInfo::OPMode[i]) {
			case AddrMode::Abs:
			case AddrMode::AbsX:
			case AddrMode::AbsXW:
			case AddrMode::AbsY:
			case AddrMode::AbsYW:
			case AddrMode::Ind:
				DisassemblyInfo::OPSize[i] = 3;
				break;

			case AddrMode::Imm:
			case AddrMode::IndX:
			case AddrMode::IndY:
			case AddrMode::IndYW:
			case AddrMode::Rel:
			case AddrMode::Zero:
			case AddrMode::ZeroX:
			case AddrMode::ZeroY:
				DisassemblyInfo::OPSize[i] = 2;
				break;

			default:
				DisassemblyInfo::OPSize[i] = 1;
				break;
		}
	}
}

bool Disassembler::IsUnofficialOpCode(uint8_t opCode)
{
	return DisassemblyInfo::IsUnofficialCode[opCode];
}

bool Disassembler::IsJump(uint8_t opCode)
{
	return (
		opCode == 0x10 || //BPL
		opCode == 0x30 || //BMI
		opCode == 0x50 || //BVC
		opCode == 0x70 || //BVS
		opCode == 0x90 || //BCC
		opCode == 0xB0 || //BCS
		opCode == 0xD0 || //BNE
		opCode == 0xF0 || //BEQ
		opCode == 0x4C || //JMP (Absolute)
		opCode == 0x6C || //JMP (Indirect)
		opCode == 0x20 //JSR
	);
}

bool Disassembler::IsUnconditionalJump(uint8_t opCode)
{
	return (
		opCode == 0x40 || //RTI
		opCode == 0x60 || //RTS
		opCode == 0x6C || //JMP (Indirect)
		opCode == 0x4C || //JMP (Absolute)
		opCode == 0x20 //JSR
	);
}

void Disassembler::GetInfo(AddressTypeInfo &info, uint8_t** source, uint32_t &size, vector<shared_ptr<DisassemblyInfo>> **cache)
{
	switch(info.Type) {
		case AddressType::Register:
			//AddressType::Register should never be used here
			break;

		case AddressType::InternalRam: 
			*source = _memoryManager->GetInternalRAM();
			*cache = &_disassembleMemoryCache;
			size = 0x800;
			break;

		case AddressType::PrgRom: 
			*source = _mapper->GetPrgRom();
			*cache = &_disassembleCache;
			size = _mapper->GetMemorySize(DebugMemoryType::PrgRom);
			break;

		case AddressType::WorkRam: 
			*source = _mapper->GetWorkRam();
			*cache = &_disassembleWorkRamCache;
			size = _mapper->GetMemorySize(DebugMemoryType::WorkRam);
			break;

		case AddressType::SaveRam: 
			*source = _mapper->GetSaveRam();
			*cache = &_disassembleSaveRamCache;
			size = _mapper->GetMemorySize(DebugMemoryType::SaveRam);
			break;
	}
}


uint32_t Disassembler::BuildCache(AddressTypeInfo &info, uint16_t cpuAddress, bool isSubEntryPoint, bool processJumps, bool forceDisassemble)
{
	uint32_t mask = info.Type == AddressType::InternalRam ? 0x7FF : 0xFFFFFFFF;

	vector<shared_ptr<DisassemblyInfo>> *cache;
	uint8_t *source;
	uint32_t size;
	GetInfo(info, &source, size, &cache);
	int32_t absoluteAddr = info.Address & mask;

	if(info.Address >= 0) {
		DisassemblyInfo *disInfo = (*cache)[absoluteAddr].get();
		if(!disInfo || forceDisassemble) {
			while(absoluteAddr < (int32_t)size && (forceDisassemble || !(*cache)[absoluteAddr])) {
				bool isJump = IsUnconditionalJump(source[absoluteAddr]);
				
				if(!forceDisassemble) {
					disInfo = new DisassemblyInfo(source+absoluteAddr, isSubEntryPoint);
					isSubEntryPoint = false;
					(*cache)[absoluteAddr] = shared_ptr<DisassemblyInfo>(disInfo);
				}
				forceDisassemble = false;

				absoluteAddr += disInfo->GetSize();
				if(isJump) {
					//Hit a jump/return instruction, can't assume that what follows is actual code, stop disassembling
					break;
				}
			}
		} else {
			if(isSubEntryPoint) {
				disInfo->SetSubEntryPoint();
			}

			uint8_t opCode = source[absoluteAddr];
			if(processJumps && IsJump(opCode)) {
				uint16_t jumpDest = disInfo->GetJumpDestination(cpuAddress, _memoryManager);
				if(jumpDest != cpuAddress) {
					AddressTypeInfo addressInfo;
					_debugger->GetAbsoluteAddressAndType(jumpDest, &addressInfo);

					constexpr uint8_t jsrCode = 0x20;
					if(addressInfo.Address >= 0) {
						BuildCache(addressInfo, jumpDest, opCode == jsrCode, false, false);
					}
				}
			}

			absoluteAddr += disInfo->GetSize();
		}
	}
	return absoluteAddr;
}

void Disassembler::InvalidateCache(AddressTypeInfo &info)
{
	if(info.Address < 0) {
		return;
	}

	int32_t addr;
	vector<shared_ptr<DisassemblyInfo>> *cache = nullptr;

	switch(info.Type) {
		case AddressType::InternalRam:
			addr = info.Address & 0x7FF;
			cache = &_disassembleMemoryCache;
			break;

		case AddressType::WorkRam:
			addr = info.Address;
			cache = &_disassembleWorkRamCache;
			break;

		case AddressType::SaveRam:
			addr = info.Address;
			cache = &_disassembleSaveRamCache;
			break;

		default:
			//No need to invalidate PRG ROM cache (since it's not RAM)
			break;
	}

	if(cache && addr >= 0) {
		for(int i = 1; i <= 2; i++) {
			int offsetAddr = (int)addr - i;
			if(offsetAddr >= 0) {
				if((*cache)[offsetAddr] != nullptr) {
					if((*cache)[offsetAddr]->GetSize() >= (uint32_t)i + 1) {
						//Invalidate any instruction that overlapped this address
						(*cache)[offsetAddr] = nullptr;
					}
				}
			}
		}
		(*cache)[addr] = nullptr;
	}
}

void Disassembler::RebuildPrgRomCache(uint32_t absoluteAddr, int32_t length)
{
	for(int i = 0; i <= 2; i++) {
		int offsetAddr = (int)absoluteAddr - i;
		if(offsetAddr >= 0) {
			if(_disassembleCache[offsetAddr] != nullptr) {
				if(_disassembleCache[offsetAddr]->GetSize() >= (uint32_t)i + 1) {
					//Invalidate any instruction that overlapped this address
					_disassembleCache[offsetAddr] = nullptr;
				}
			}
		}
	}

	for(int i = absoluteAddr, end = absoluteAddr + length; i < end; i++) {
		_disassembleCache[i] = nullptr;
	}

	//Re-disassemble based on the previous instruction (if one exists)
	for(int i = 0; i < 6; i++) {
		int offsetAddr = (int)absoluteAddr - i;
		if(offsetAddr >= 0 && _disassembleCache[offsetAddr] != nullptr) {
			int32_t memoryAddr = _debugger->GetRelativeAddress(offsetAddr, AddressType::PrgRom);
			if(memoryAddr >= 0) {
				AddressTypeInfo info = { offsetAddr, AddressType::PrgRom };
				BuildCache(info, memoryAddr, false, false, true);
			}
			break;
		}
	}
}

static const char* hexTable[256] = {
	"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
	"10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
	"20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
	"30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
	"40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
	"50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
	"60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
	"70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
	"80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
	"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
	"A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
	"B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
	"C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
	"D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
	"E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
	"F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"
};

static string emptyString;
void Disassembler::GetLine(string &out, string code, string comment, int32_t cpuAddress, int32_t absoluteAddress, DataType dataType, char memoryType)
{
	GetCodeLine(out, code, comment, cpuAddress, absoluteAddress, emptyString, emptyString, dataType, false, memoryType);
}

void Disassembler::GetCodeLine(string &out, string &code, string &comment, int32_t cpuAddress, int32_t absoluteAddress, string &byteCode, string &addressing, DataType dataType, bool isIndented, char memoryType)
{
	char buffer[1000];
	int pos = 0;
	char* ptrBuf = buffer;
	int* ptrPos = &pos;

	auto writeChar = [=](char c) -> void {
		if(*ptrPos < 999) {
			ptrBuf[(*ptrPos)++] = c;
		}
	};

	auto writeHex = [=](const char* hex) -> void {
		if(*ptrPos < 950) {
			ptrBuf[(*ptrPos)++] = hex[0];
			ptrBuf[(*ptrPos)++] = hex[1];
		}
	};

	auto writeStr = [=](string &str) -> void {
		uint32_t len = (uint32_t)str.size();
		if(*ptrPos + len < 950) {
			memcpy(ptrBuf + (*ptrPos), str.c_str(), len);
			(*ptrPos) += len;
		} else {
			len = 950 - *ptrPos;
			memcpy(ptrBuf + (*ptrPos), str.c_str(), len);
			(*ptrPos) += len;
		}
	};
	
	//Fields:
	//Flags | CpuAddress | AbsAddr | ByteCode | Code | Addressing | Comment
	//
	//Flags:
	//1: Executed code
	//2: Unidentified code/data
	//4: Indented line
	//8: Verified data
	if(cpuAddress >= 0) {
		if(dataType == DataType::UnidentifiedData) {
			writeChar(isIndented ? '6' : '2');
			writeChar('\x1');
		} else if(dataType == DataType::VerifiedData) {
			writeChar(isIndented ? '9' : '8');
			writeChar('\x1');
		} else {
			writeChar((_debugger->IsMarkedAsCode(cpuAddress) || absoluteAddress == -1) ? (isIndented ? '5' : '1') : (isIndented ? '4' : '0'));
			writeChar('\x1');
		}
		writeHex(hexTable[(cpuAddress >> 8) & 0xFF]);
		writeHex(hexTable[cpuAddress & 0xFF]);
		writeChar('\x1');
	} else {
		if(dataType == DataType::VerifiedData) {
			writeChar('8');
		} else {
			writeChar(dataType == DataType::UnidentifiedData ? '2' : '0');
		}
		writeChar('\x1');
		writeChar('\x1');
	}

	writeChar(memoryType);
	writeChar('\x1');

	if(absoluteAddress >= 0) {
		if(absoluteAddress > 0xFFFFFF) {
			writeHex(hexTable[(absoluteAddress >> 24) & 0xFF]);
		}
		if(absoluteAddress > 0xFFFF) {
			writeHex(hexTable[(absoluteAddress >> 16) & 0xFF]);
		}			
		writeHex(hexTable[(absoluteAddress >> 8) & 0xFF]);
		writeHex(hexTable[absoluteAddress & 0xFF]);
	}

	writeChar('\x1');
	writeStr(byteCode);
	writeChar('\x1');
	writeStr(code);
	writeChar('\x1');
	writeStr(addressing);
	writeChar('\x1');
	if(!comment.empty()) {
		writeChar(';');
		writeStr(comment);
	}
	writeChar('\x1');
	ptrBuf[(*ptrPos)++] = 0;

	out.append(buffer, pos - 1);
}

void Disassembler::GetSubHeader(string &out, DisassemblyInfo *info, string &label, uint16_t relativeAddr, uint16_t resetVector, uint16_t nmiVector, uint16_t irqVector)
{
	if(info->IsSubEntryPoint()) {
		if(label.empty()) {
			GetLine(out, "__sub start__");
		} else {
			GetLine(out, "__" + label + "()__");
		}
	} else if(relativeAddr == resetVector) {
		GetLine(out, "__reset__");
	} else if(relativeAddr == irqVector) {
		GetLine(out, "__irq__");
	} else if(relativeAddr == nmiVector) {
		GetLine(out, "__nmi__");
	}
}

string Disassembler::GetCode(AddressTypeInfo &addressInfo, uint32_t endAddr, uint16_t memoryAddr, State& cpuState, shared_ptr<MemoryManager> memoryManager, shared_ptr<LabelManager> labelManager) 
{
	string output;
	output.reserve(10000000);

	int32_t dbRelativeAddr = 0;
	int32_t dbAbsoluteAddr = 0;
	string dbBuffer;

	bool showEffectiveAddresses = _debugger->CheckFlag(DebuggerFlags::ShowEffectiveAddresses);
	bool showUnidentifiedData = _debugger->CheckFlag(DebuggerFlags::ShowUnidentifiedData);
	bool showVerifiedData = _debugger->CheckFlag(DebuggerFlags::ShowVerifiedData);
	bool disassembleVerifiedData = showVerifiedData && _debugger->CheckFlag(DebuggerFlags::DisassembleVerifiedData);
	bool disassembleUnidentifiedData = showUnidentifiedData && _debugger->CheckFlag(DebuggerFlags::DisassembleUnidentifiedData);

	uint16_t resetVector = memoryManager->DebugReadWord(CPU::ResetVector);
	uint16_t nmiVector = memoryManager->DebugReadWord(CPU::NMIVector);
	uint16_t irqVector = memoryManager->DebugReadWord(CPU::IRQVector);

	vector<shared_ptr<DisassemblyInfo>> *cache;
	uint8_t *source;
	char memoryType = 'P';
	switch(addressInfo.Type) {
		case AddressType::Register: break; //Should never happen
		case AddressType::InternalRam: memoryType = 'N'; break;
		case AddressType::PrgRom: memoryType = 'P'; break;
		case AddressType::WorkRam: memoryType = 'W'; break;
		case AddressType::SaveRam: memoryType = 'S'; break;
	}
	uint32_t mask = addressInfo.Type == AddressType::InternalRam ? 0x7FF : 0xFFFFFFFF;
	uint32_t size;

	GetInfo(addressInfo, &source, size, &cache);

	uint32_t addr = addressInfo.Address;
	uint32_t byteCount = 0;
	bool insideDataBlock = false;
	shared_ptr<CodeDataLogger> cdl = _debugger->GetCodeDataLogger();
	string label;
	string commentString;
	string commentLines;
	shared_ptr<DisassemblyInfo> tmpInfo(new DisassemblyInfo());
	shared_ptr<DisassemblyInfo> info;
	DataType dataType = DataType::UnidentifiedData;
	string spaces = "  ";
	string effAddress;
	string code;
	string byteCode;
	bool isVerifiedData;
	bool inVerifiedDataBlock = false;
	bool emptyBlock = false;
	bool showEitherDataType = showVerifiedData || showUnidentifiedData;
	
	auto outputBytes = [this, &showEitherDataType, &addressInfo, &inVerifiedDataBlock, &output, &dbBuffer, &dbRelativeAddr, &dbAbsoluteAddr, &byteCount, memoryType]() {
		DataType type = addressInfo.Type == AddressType::PrgRom ? (showEitherDataType && inVerifiedDataBlock ? DataType::VerifiedData : DataType::UnidentifiedData) : DataType::VerifiedCode;
		if(byteCount > 0) {
			GetLine(output, dbBuffer, "", dbRelativeAddr, dbAbsoluteAddr, type, memoryType);
			byteCount = 0;
		}
	};

	auto endDataBlock = [this, outputBytes, &addressInfo, &showEitherDataType, &inVerifiedDataBlock, &emptyBlock, &output, &addr, &insideDataBlock, &memoryAddr, memoryType]() {
		outputBytes();
		DataType type = addressInfo.Type == AddressType::PrgRom ? (showEitherDataType && inVerifiedDataBlock ? DataType::VerifiedData : DataType::UnidentifiedData) : DataType::VerifiedCode;
		if(emptyBlock) {
			GetLine(output, "", "", -1, -1, type, memoryType);
		}
		GetLine(output, "----", "", emptyBlock ? (uint16_t)(memoryAddr - 1) : -1, emptyBlock ? addr - 1 : -1, type, memoryType);
		insideDataBlock = false;
	};

	while(addr <= endAddr) {
		labelManager->GetLabelAndComment(memoryAddr, label, commentString);
		commentLines.clear();
		
		if(commentString.find_first_of('\n') != string::npos) {
			for(string &str : StringUtilities::Split(commentString, '\n')) {
				GetLine(commentLines, "", str, -1, -1, dataType, memoryType);
			}
			commentString.clear();
		}
		
		info = (*cache)[addr&mask];

		isVerifiedData = addressInfo.Type == AddressType::PrgRom && cdl->IsData(addr&mask);
		if(!info && ((disassembleUnidentifiedData && !isVerifiedData) || (disassembleVerifiedData && isVerifiedData))) {
			dataType = isVerifiedData ? DataType::VerifiedData : DataType::UnidentifiedData;
			tmpInfo->Initialize(source + (addr & mask), false);
			info = tmpInfo;
		} else if(info) {
			dataType = DataType::VerifiedCode;
		}

		if(!label.empty()) {
			size_t arrayNotationIndex = label.find_first_of('+');
			if(arrayNotationIndex != string::npos && label.size() > arrayNotationIndex + 1) {
				if(label[arrayNotationIndex + 1] != '0') {
					//Ignore array labels except the +0 variation
					label = "";
				} else {
					label = label.substr(0, label.size() - 2);
				}
			}
		}
		
		if(info && addr + info->GetSize() <= endAddr + 1) {
			if(insideDataBlock) {
				endDataBlock();
			}

			GetSubHeader(output, info.get(), label, memoryAddr, resetVector, nmiVector, irqVector);
			output += commentLines;
			if(!label.empty()) {
				GetLine(output, label + ":", emptyString, -1, -1, dataType, memoryType);
			}
			
			byteCode.clear();
			code.clear();
			effAddress.clear();
			if(showEffectiveAddresses) {
				info->GetEffectiveAddressString(effAddress, cpuState, memoryManager.get(), labelManager.get());
			}
			info->ToString(code, memoryAddr, memoryManager.get(), labelManager.get(), false);
			info->GetByteCode(byteCode);

			GetCodeLine(output, code, commentString, memoryAddr, addr & mask, byteCode, effAddress, dataType, true, memoryType);

			if(info->IsSubExitPoint()) {
				GetLine(output, "----");
			}

			if(memoryAddr < cpuState.DebugPC && memoryAddr + info->GetSize() > cpuState.DebugPC) {
				//The current instruction started between the current and next operation.
				//This can happen when sharing bytes between instructions (and executing one or the other depending on a branch, etc.)
				//In this case, realign the disassembly with the PC at the start of the instruction
				addr += cpuState.DebugPC - memoryAddr;
				memoryAddr = cpuState.DebugPC;
			} else if(dataType != DataType::VerifiedCode) {
				//For unverified code, check if a verified instruction starts between the start of this instruction and its end.
				//If so, we need to realign the disassembler to the start of the next verified instruction
				for(uint32_t i = 0; i < info->GetSize(); i++) {
					addr++;
					memoryAddr++;
					if(addr > endAddr || (*cache)[addr&mask] || (!disassembleVerifiedData && addressInfo.Type == AddressType::PrgRom && cdl->IsData(addr))) {
						//Verified code or verified data found, stop incrementing address counters
						break;
					}
				}		
			} else {
				addr += info->GetSize();
				memoryAddr += info->GetSize();
			}
		} else {
			//This byte should be interpreted as data
			bool showData = (isVerifiedData && showVerifiedData) || (!isVerifiedData && showUnidentifiedData);

			if(inVerifiedDataBlock != isVerifiedData && insideDataBlock && showEitherDataType) {
				//End of block (switching between verified data & unidentified data, while only either of them is set to be displayed)
				endDataBlock();
			} else if(inVerifiedDataBlock != isVerifiedData) {
				outputBytes();
			}

			inVerifiedDataBlock = isVerifiedData;
			dataType = addressInfo.Type == AddressType::PrgRom ? (showEitherDataType && inVerifiedDataBlock ? DataType::VerifiedData : DataType::UnidentifiedData) : DataType::VerifiedCode;

			if(!insideDataBlock) {
				//Output block header 
				if(label.empty()) {
					string blockName;
					if(addressInfo.Type == AddressType::InternalRam) {
						blockName = "__NES RAM__";
					} else if(addressInfo.Type == AddressType::SaveRam) {
						blockName = "__Save RAM__";
					} else if(addressInfo.Type == AddressType::WorkRam) {
						blockName = "__Work RAM__";
					} else {
						blockName = showEitherDataType && inVerifiedDataBlock ? "__data block__" : "__unidentified block__";
					}

					GetLine(output, blockName, "", showData ? -1 : memoryAddr, showData ? -1 : addr, dataType, memoryType);
				} else {
					GetLine(output, "__" + label + "__", "", showData ? -1 : memoryAddr, showData ? -1 : addr, dataType, memoryType);
				}
				insideDataBlock = true;
				emptyBlock = true;
			}

			if(showData) {
				//Output bytes in ".db" statements
				if(byteCount >= 8 || ((!label.empty() || !commentString.empty()) && byteCount > 0)) {
					outputBytes();
				}

				if(byteCount == 0) {
					dbBuffer = ".db";
					output += commentLines;
					if(!label.empty()) {
						GetLine(output, label + ":", "", -1, -1, dataType, memoryType);
					}

					dbRelativeAddr = memoryAddr;
					dbAbsoluteAddr = addr;
				}

				dbBuffer += " $" + HexUtilities::ToHex(source[addr&mask]);

				if(!commentString.empty()) {
					GetLine(output, dbBuffer, commentString, dbRelativeAddr, dbAbsoluteAddr, dataType, memoryType);
					byteCount = 0;
				} else {
					byteCount++;
				}

				emptyBlock = false;
			}
			addr++;
			memoryAddr++;
		}
	}

	if(insideDataBlock) {
		//End the current data block if needed
		endDataBlock();
	}
		
	return output;
}

DisassemblyInfo Disassembler::GetDisassemblyInfo(AddressTypeInfo &info)
{
	DisassemblyInfo* disassemblyInfo = nullptr;
	switch(info.Type) {
		case AddressType::Register: break; //Should never happen
		case AddressType::InternalRam: disassemblyInfo = _disassembleMemoryCache[info.Address & 0x7FF].get(); break;
		case AddressType::PrgRom: disassemblyInfo = _disassembleCache[info.Address].get(); break;
		case AddressType::WorkRam: disassemblyInfo = _disassembleWorkRamCache[info.Address].get(); break;
		case AddressType::SaveRam: disassemblyInfo = _disassembleSaveRamCache[info.Address].get(); break;
	}

	if(disassemblyInfo) {
		return *disassemblyInfo;
	} else {
		return DisassemblyInfo();
	}
}
