/*
	Copyright (C) 2006 Guillaume Duhamel
	Copyright (C) 2006-2021 DeSmuME team

	This file is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This file is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with the this software.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "debug.h"

#include <algorithm>
#include <stdarg.h>
#include <stdio.h>
#include "MMU.h"
#include "armcpu.h"
#include "instructions.h"
#include "cp15.h"
#include "NDSSystem.h"
#include "utils/xstring.h"
#include "movie.h"

#ifdef HAVE_LUA
#include "lua-engine.h"
#endif

armcpu_t* TDebugEventData::cpu() { return procnum==0?&NDS_ARM9:&NDS_ARM7; }

TDebugEventData DebugEventData;
u32 debugFlag;

// PACKET HACK VARS
FILE *log_ptr;	// File to store the dumped data
const u32 rc4_addr[2] = { 0x020986A8, 0x02098710 };

//DEBUG CONFIGURATION
const bool debug_acl = false;
const bool debug_cacheMiss = false;

static bool acl_check_access(u32 adr, u32 access) {

	//non-user modes get separate access handling, so check that here
	if(NDS_ARM9.CPSR.bits.mode != USR)
		access |= 1;
	
	if (armcp15_isAccessAllowed(&cp15, adr,access)==FALSE) {
		HandleDebugEvent(DEBUG_EVENT_ACL_EXCEPTION);
	}
	return true;
}

void HandleDebugEvent_ACL_Exception()
{
	printf("ACL EXCEPTION!\n");
	if(DebugEventData.memAccessType == MMU_AT_CODE)
		armcpu_exception(DebugEventData.cpu(),EXCEPTION_PREFETCH_ABORT);
	else if(DebugEventData.memAccessType == MMU_AT_DATA)
		armcpu_exception(DebugEventData.cpu(),EXCEPTION_DATA_ABORT);
}


static bool CheckRange(u32 adr, u32 min, u32 len)
{
	return (adr>=min && adr<min+len);
}

void HandleDebugEvent_Read()
{
	if(!debug_acl) return;
	if(DebugEventData.procnum != ARMCPU_ARM9) return; //acl only valid on arm9
	acl_check_access(DebugEventData.addr,CP15_ACCESS_READ);
}

void HandleDebugEvent_Write()
{
	// Disabled by default.
	// If you want to enable first you must know and update the address of the
	// RC4 algoritm function.
	return;

	// This method is called twice, so ommit one call.
	extern bool nds_debug_continuing[2];
	if (nds_debug_continuing[DebugEventData.procnum]) {
		nds_debug_continuing[DebugEventData.procnum] = false;
		return;
	}

	// RC4 encrypt / decrypt function
	//   R1: Pointer to data to operate (to decrypt or encrypt)
	//   R2: Size of this data
	if (DebugEventData.addr == rc4_addr[0] || DebugEventData.addr == rc4_addr[1]) {
		nds_debug_continuing[DebugEventData.procnum] = true;

		u32 addr = DebugEventData.addr;
		printf("WIFI: Call to RC4_ALGORITM\n");

		// Write log. Append current data
		// TODO: It needs to open the file each time, it could be slow.
		// An improvement could be opening the file at the start of the function
		// and closing at the end of the function.
		log_ptr = fopen("wifi_log.txt", "a");
		if (log_ptr != NULL)
		{
			// Create header
			time_t ti;
			time(&ti);
			tm* t = localtime(&ti);

			fprintf(log_ptr, "\n[%02d-%02d-%02d-%02d-%02d] %s of RC4_ALGORITM -----------\n", 
				t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec,
				(addr == rc4_addr[0]) ? "Start" : "End"
			);

			// Dump data
			int length   = DebugEventData.cpu()->R[2];
			int position = DebugEventData.cpu()->R[1] - 0x02000000;	// Relative to memory array
			fwrite(MMU.MAIN_MEM + position, sizeof(char), length, log_ptr);
			
			// End
			fprintf(log_ptr, "\n- THE END -----------------------------------------------\n");

			// Flush and close by the moment
			fflush(log_ptr);
			fclose(log_ptr);
		}
		else {
			printf("Error opening log file\n");
		}
	}
}

void HandleDebugEvent_Execute()
{
	//HACKY BREAKPOINTS!
	//extern bool nds_debug_continuing[2];
	//if(!nds_debug_continuing[DebugEventData.procnum]) //dont keep hitting the same breakpoint
	//{
	//	if((DebugEventData.addr & 0xFFFFFFF0) == 0x02000000)
	//	{
	//		void NDS_debug_break();
	//		NDS_debug_break();
	//	}
	//}
	if(!debug_acl) return;
	if(DebugEventData.procnum != ARMCPU_ARM9) return; //acl only valid on arm9
	acl_check_access(DebugEventData.addr,CP15_ACCESS_EXECUTE);
}

void HandleDebugEvent_CacheMiss()
{
	if(!debug_cacheMiss) return;
	extern int currFrameCounter;
	if(currFrameCounter<200) return;
	static FILE* outf = NULL;
	if(!outf) outf = fopen("c:\\miss.txt","wb");
	fprintf(outf,"%05d,%08X,%d\n",currFrameCounter,DebugEventData.addr,DebugEventData.size);
}

//------------------------------------------------
DebugStatistics DEBUG_statistics;

DebugStatistics::DebugStatistics()
{
}

DebugStatistics::InstructionHits::InstructionHits()
{
	memset(&arm,0,sizeof(arm));
	memset(&thumb,0,sizeof(thumb));
}


static DebugStatistics::InstructionHits combinedHits[2];

template<int proc, int which>
static bool debugStatsSort(int num1, int num2) {
	if(which==0) {
		if(combinedHits[proc].arm[num2] == combinedHits[proc].arm[num1]) return false;
		if(combinedHits[proc].arm[num1] == 0xFFFFFFFF) return false;
		if(combinedHits[proc].arm[num2] == 0xFFFFFFFF) return true;
		return combinedHits[proc].arm[num2] < combinedHits[proc].arm[num1];
	}
	else {
		if(combinedHits[proc].thumb[num2] == combinedHits[proc].thumb[num1]) return false;
		if(combinedHits[proc].thumb[num1] == 0xFFFFFFFF) return false;
		if(combinedHits[proc].thumb[num2] == 0xFFFFFFFF) return true;
		return combinedHits[proc].thumb[num2] < combinedHits[proc].thumb[num1];
	}
}

void DebugStatistics::print()
{
	//consolidate opcodes with the same names
	for(int i=0;i<2;i++) { 
		combinedHits[i] = DEBUG_statistics.instructionHits[i];

		for(int j=0;j<4096;j++) {
			if(combinedHits[i].arm[j] == 0xFFFFFFFF) 
				continue;
			std::string name = arm_instruction_names[j];
			for(int k=j+1;k<4096;k++) {
				if(combinedHits[i].arm[k] == 0xFFFFFFFF) 
					continue;
				if(name == arm_instruction_names[k]) {
					//printf("combining %s with %d and %d\n",name.c_str(),combinedHits[i].arm[j],combinedHits[i].arm[k]);
					combinedHits[i].arm[j] += combinedHits[i].arm[k];
					combinedHits[i].arm[k] = 0xFFFFFFFF;
				}
				
			}
		}

		for(int j=0;j<1024;j++) {
			if(combinedHits[i].thumb[j] == 0xFFFFFFFF) 
				continue;
			std::string name = thumb_instruction_names[j];
			for(int k=j+1;k<1024;k++) {
				if(combinedHits[i].thumb[k] == 0xFFFFFFFF) 
					continue;
				if(name == thumb_instruction_names[k]) {
					//printf("combining %s with %d and %d\n",name.c_str(),combinedHits[i].arm[j],combinedHits[i].arm[k]);
					combinedHits[i].thumb[j] += combinedHits[i].thumb[k];
					combinedHits[i].thumb[k] = 0xFFFFFFFF;
				}
				
			}
		}
	}

	InstructionHits sorts[2];
	for(int i=0;i<2;i++) {
		for(int j=0;j<4096;j++) sorts[i].arm[j] = j;
		for(int j=0;j<1024;j++) sorts[i].thumb[j] = j;
	}
	std::sort(sorts[0].arm, sorts[0].arm+4096, debugStatsSort<0,0>);
	std::sort(sorts[0].thumb, sorts[0].thumb+1024, debugStatsSort<0,1>);
	std::sort(sorts[1].arm, sorts[1].arm+4096, debugStatsSort<1,0>);
	std::sort(sorts[1].thumb, sorts[1].thumb+1024, debugStatsSort<1,1>);

	for(int i=0;i<2;i++) {
		printf("Top arm instructions for ARM%d:\n",7+i*2);
		for(int j=0;j<10;j++) {
			int val = sorts[i].arm[j];
			printf("%08d: %s\n", combinedHits[i].arm[val], arm_instruction_names[val]);
		}
		printf("Top thumb instructions for ARM%d:\n",7+i*2);
		for(int j=0;j<10;j++) {
			int val = sorts[i].thumb[j];
			printf("%08d: %s\n", combinedHits[i].thumb[val], thumb_instruction_names[val]);
		}
	}
}

void DebugStatistics::printSequencerExecutionCounters()
{
	for(int i=0;i<21;i++) printf("%06d ",sequencerExecutionCounters[i]);
	printf("\n");
}

void DEBUG_reset()
{
	//for now, just enable all debugging in developer builds
#ifdef DEVELOPER
	debugFlag = 1;
#endif

	DEBUG_Notify = DebugNotify();
	DEBUG_statistics = DebugStatistics();
	printf("DEBUG_reset: %p\n",&DebugStatistics::print); //force a reference to this function
}

static void DEBUG_dumpMemory_fill(EMUFILE &fp, u32 size)
{
	static std::vector<u8> buf;
	buf.resize(size);
	memset(&buf[0],0,size);
	fp.fwrite(&buf[0],size);
}

void DEBUG_dumpMemory(EMUFILE &fp)
{
	fp.fseek(0x000000,SEEK_SET); fp.fwrite(MMU.MAIN_MEM,0x800000); //arm9 main mem (8192K)
	fp.fseek(0x900000,SEEK_SET); fp.fwrite(MMU.ARM9_DTCM,0x4000); //arm9 DTCM (16K)
	fp.fseek(0xA00000,SEEK_SET); fp.fwrite(MMU.ARM9_ITCM,0x8000); //arm9 ITCM (32K)
	fp.fseek(0xB00000,SEEK_SET); fp.fwrite(MMU.ARM9_LCD,0xA4000); //LCD mem 656K
	fp.fseek(0xC00000,SEEK_SET); fp.fwrite(MMU.ARM9_VMEM,0x800); //OAM
	fp.fseek(0xD00000,SEEK_SET); fp.fwrite(MMU.ARM7_ERAM,0x10000); //arm7 WRAM (64K)
	fp.fseek(0xE00000,SEEK_SET); fp.fwrite(MMU.ARM7_WIRAM,0x10000); //arm7 wifi RAM ?
	fp.fseek(0xF00000,SEEK_SET); fp.fwrite(MMU.SWIRAM,0x8000); //arm9/arm7 shared WRAM (32KB)
}

//----------------------------------------------------

std::vector<Logger *> Logger::channels;


static void defaultCallback(const Logger& logger, const char * message) {
	logger.getOutput() << message;
}

Logger::Logger() {
	out = &std::cout;
	callback = defaultCallback;
	flags = 0;
}

Logger::~Logger() {
	for(int i=0;i<(int)channels.size();i++)
		delete channels[i];
}

void Logger::vprintf(const char * format, va_list l, const char * file, unsigned int line) {
	char buffer[1024];
	char * cur = buffer;

	if (flags & Logger::FILE) cur += sprintf(cur, "%s:", file);
	if (flags & Logger::LINE) cur += sprintf(cur, "%d:", line);
	if (flags) cur += sprintf(cur, " ");

	::vsnprintf(cur, 1024, format, l);
	callback(*this, buffer);
}

void Logger::setOutput(std::ostream * o) {
	out = o;
}

void Logger::setCallback(void (*cback)(const Logger& logger, const char * message)) {
	callback = cback;
}

void Logger::setFlag(unsigned int flag) {
	this->flags = flag;
}

void Logger::fixSize(unsigned int channel) {
	while(channel >= channels.size()) {
		channels.push_back(new Logger());
	}
}

std::ostream& Logger::getOutput() const {
	return *out;
}

void Logger::log(unsigned int channel, const char * file, unsigned int line, const char * format, ...) {
	fixSize(channel);

	va_list l;
	va_start(l, format);
	channels[channel]->vprintf(format, l, file, line);
	va_end(l);
}

void Logger::log(unsigned int channel, const char * file, unsigned int line, std::ostream& os) {
	fixSize(channel);

	channels[channel]->setOutput(&os);
}

void Logger::log(unsigned int channel, const char * file, unsigned int line, unsigned int flag) {
	fixSize(channel);

	channels[channel]->setFlag(flag);
}

void Logger::log(unsigned int channel, const char * file, unsigned int line, void (*callback)(const Logger& logger, const char * message)) {
	fixSize(channel);

	channels[channel]->setCallback(callback);
}

void IdeasLog(armcpu_t* cpu)
{
	u32 adr = cpu->R[0];
	for(;;) {
		u8 c = _MMU_read08(cpu->proc_ID, MMU_AT_DEBUG, adr);
		adr++;
		if(!c) break;
		printf("%c",c);
	}
	//don't emit a newline. that is a pain in the butt.
}

void NocashMessage(armcpu_t* cpu, int offset)
{
	u32 adr = cpu->instruct_adr + offset;

	std::string todo;
	for(;;) {
		u8 c = _MMU_read08(cpu->proc_ID, MMU_AT_DEBUG, adr);
		adr++;
		if(!c) break;
		todo.push_back(c);
	}

	//r0,r1,r2,...,r15  show register content (displayed as 32bit Hex number)
	//sp,lr,pc          alias for r13,r14,r15
	//scanline          show current scanline number
	//frame             show total number of frames since coldboot
	//totalclks         show total number of clock cycles since coldboot
	//lastclks          show number of cycles since previous lastclks (or zeroclks)
	//zeroclks          resets the 'lastclks' counter

	//this is very inefficiently coded!
	char tmp[100];
	todo = mass_replace(todo,"%sp%","%r13%");
	todo = mass_replace(todo,"%lr%","%r14%");
	todo = mass_replace(todo,"%pc%","%r15%");
	sprintf(tmp,"%08X",cpu->R[0]); todo = mass_replace(todo,"%r0%",tmp);
	sprintf(tmp,"%08X",cpu->R[1]); todo = mass_replace(todo,"%r1%",tmp);
	sprintf(tmp,"%08X",cpu->R[2]); todo = mass_replace(todo,"%r2%",tmp);
	sprintf(tmp,"%08X",cpu->R[3]); todo = mass_replace(todo,"%r3%",tmp);
	sprintf(tmp,"%08X",cpu->R[4]); todo = mass_replace(todo,"%r4%",tmp);
	sprintf(tmp,"%08X",cpu->R[5]); todo = mass_replace(todo,"%r5%",tmp);
	sprintf(tmp,"%08X",cpu->R[6]); todo = mass_replace(todo,"%r6%",tmp);
	sprintf(tmp,"%08X",cpu->R[7]); todo = mass_replace(todo,"%r7%",tmp);
	sprintf(tmp,"%08X",cpu->R[8]); todo = mass_replace(todo,"%r8%",tmp);
	sprintf(tmp,"%08X",cpu->R[9]); todo = mass_replace(todo,"%r9%",tmp);
	sprintf(tmp,"%08X",cpu->R[10]); todo = mass_replace(todo,"%r10%",tmp);
	sprintf(tmp,"%08X",cpu->R[11]); todo = mass_replace(todo,"%r11%",tmp);
	sprintf(tmp,"%08X",cpu->R[12]); todo = mass_replace(todo,"%r12%",tmp);
	sprintf(tmp,"%08X",cpu->R[13]); todo = mass_replace(todo,"%r13%",tmp);
	sprintf(tmp,"%08X",cpu->R[14]); todo = mass_replace(todo,"%r14%",tmp);
	sprintf(tmp,"%08X",cpu->R[15]); todo = mass_replace(todo,"%r15%",tmp);
	sprintf(tmp,"%d",nds.VCount); todo = mass_replace(todo,"%scanline%",tmp);
	sprintf(tmp,"%d",currFrameCounter); todo = mass_replace(todo,"%frame%",tmp);
	sprintf(tmp,"%lld",nds_timer); todo = mass_replace(todo,"%totalclks%",tmp);

	printf("%s",todo.c_str());
}

//-------
DebugNotify DEBUG_Notify;

//enable bits arent being used right now.
//if you want exhaustive logging, move the print before the early return (or comment the early return)

//the intent of this system is to provide a compact dialog box showing which debug notifies have been
//triggered in this frame (with a glowing LED!) and which debug notifies have been triggered EVER
//which can be cleared, like a clip indicator in an audio tool.
//obviously all this isnt implemented yet.

void DebugNotify::NextFrame()
{
#ifdef DEVELOPER
	pingBits.reset();
#endif
}

void DebugNotify::ReadBeyondEndOfCart(u32 addr, u32 romsize)
{
#ifdef DEVELOPER
	if(!ping(DEBUG_NOTIFY_READ_BEYOND_END_OF_CART)) return;
	INFO("Reading beyond end of cart! ... %08X >= %08X\n",addr,romsize);
#endif
}

bool DebugNotify::ping(EDEBUG_NOTIFY which)
{
	bool wasPinged = pingBits[(int)which];
	pingBits[(int)which] = true;
	return !wasPinged;
}
