#ifdef SUPERMODEL_DEBUGGER

#include "Supermodel.h"

#include "ConsoleDebugger.h"
#include "CPUDebug.h"
#include "Label.h"

#include <string.h>
#include <stdio.h>

namespace Debugger
{
	CSupermodelDebugger::CSupermodelDebugger(::CModel3 *model3, ::CInputs *inputs, ::CLogger *logger) : 
		CConsoleDebugger(), m_model3(model3), m_inputs(inputs), m_logger(logger), 
		m_loadEmuState(false), m_saveEmuState(false), m_resetEmu(false)
	{
		AddCPU(new CPPCDebug());
	}

	void CSupermodelDebugger::WaitCommand(CCPUDebug *cpu)
	{
		m_inputs->GetInputSystem()->UngrabMouse();

		CConsoleDebugger::WaitCommand(cpu);

		m_inputs->GetInputSystem()->GrabMouse();
	}

	bool CSupermodelDebugger::ProcessToken(const char *token, const char *cmd)
	{
		//
		// Emulator
		//
		if (CheckToken(token, "les", "loademustate"))				// loademustate <filename>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Missing filename.\n");
				return false;
			}

			strncpy(m_stateFile, token, 254);
			m_stateFile[254] = '\0';
			m_loadEmuState = true;
			return true;
		}
		else if (CheckToken(token, "ses", "saveemustate"))			// saveemustate  <filename>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Missing filename.\n");
				return false;
			}

			strncpy(m_stateFile, token, 254);
			m_stateFile[254] = '\0';
			m_saveEmuState = true;
			return true;
		}
		else if (CheckToken(token, "res", "resetemu"))				// resetemu
		{
			m_resetEmu = true;
			return true;
		}
		//
		// Inputs
		//
		else if (CheckToken(token, "lip", "listinputs"))			// listinputs
		{
			ListInputs();
			return false;
		}
		else if (CheckToken(token, "pip", "printinput"))			// printinput (<id>|<label>)
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Mising input name.\n");
				return false;
			}
			::CInput *input = (*m_inputs)[token];
			if (input == NULL)
			{
				Print("No input with id or label '%s'.\n", token);
				return false;
			}
			if (!InputIsValid(input))
			{
				Print("Input '%s' is not valid for current game.\n", token);
				return false;
			}

			if (!input->IsVirtual())
			{
				char mapTrunc[41];
				Truncate(mapTrunc, 40, input->GetMapping());
				Print("Input %s (%s) [%s] = %04X (%d)\n", input->id, input->label, mapTrunc, input->value, input->value);
			}
			else
				Print("Input %s (%s) = %04X (%d)\n", input->id, input->label, input->value, input->value);
			return false;
		}
		else if (CheckToken(token, "sip", "setinput"))				// setinput (<id>|<label>) <mapping>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Mising input id or label.\n");
				return false;
			}
			::CInput *input = (*m_inputs)[token];
			if (input == NULL)
			{
				Print("No input with id or label '%s'.\n", token);
				return false;
			}
			if (!InputIsValid(input))
			{
				Print("Input '%s' is not valid for current game.\n", token);
				return false;
			}
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Missing mapping to set.\n");
				return false;
			}

			input->SetMapping(token);

			Print("Set input %s (%s) to [%s]\n", input->id, input->label, input->GetMapping());
			return false;
		}
		else if (CheckToken(token, "rip", "resetinput"))            // resetinput (<id>|<label>)
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Mising input id or label.\n");
				return false;
			}
			::CInput *input = (*m_inputs)[token];
			if (input == NULL)
			{
				Print("No input with id or label '%s'.\n", token);
				return false;
			}
			if (!InputIsValid(input))
			{
				Print("Input '%s' is not valid for current game.\n", token);
				return false;
			}

			input->ResetToDefaultMapping();

			Print("Reset input %s (%s) to [%s]\n", input->id, input->label, input->GetMapping());
			return false;
		}
		else if (CheckToken(token, "cip", "configinput"))			// configinput (<id>|<label>) [(s)et|(a)ppend]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Print("Mising input id or label.\n");
				return false;
			}
			CInput *input = (*m_inputs)[token];
			if (input == NULL)
			{
				Print("No input with id or label '%s'.\n", token);
				return false;
			}
			if (!InputIsValid(input))
			{
				Print("Input '%s' is not valid for current game.\n", token);
				return false;
			}
			token = strtok(NULL, " ");
			bool append;
			if (token == NULL || CheckToken(token, "s", "set"))
				append = false;
			else if (CheckToken(token, "a", "append"))
				append = true;
			else 
			{
				Print("Enter a valid mode (s)et or (a)ppend.\n");
				return false;
			}

			Print("Configure input %s [%s]: %s...", input->label, input->GetMapping(), (append ? "Appending" : "Setting"));
			fflush(stdout);	// required on terminals that use buffering
	
			// Configure the input
			if (input->Configure(append, "KEY_ESCAPE"))
				Print(" %s\n", input->GetMapping());
			else
				Print(" [Cancelled]\n");
			return false;
		}
		else if (CheckToken(token, "caip", "configallinputs"))		// configallinputs
		{
			m_inputs->ConfigureInputs(m_model3->GetGameInfo());
			return false;
		}
		//
		// Help
		//
		else if (CheckToken(token, "h", "help"))
		{
			CConsoleDebugger::ProcessToken(token, cmd);

			const char *fmt = "  %-6s %-25s %s\n";
			Print(" Emulator:\n");
			Print(fmt, "les",    "loademustate",           "<filename>");
			Print(fmt, "ses",    "saveemustate",           "<filename>");
			Print(fmt, "res",    "resetemu",               "");
			Print(" Inputs:\n");
			Print(fmt, "lip",    "listinputs",             "");
			Print(fmt, "pip",    "printinput",             "(<id>|<label>)");
			Print(fmt, "sip",    "setinput",               "(<id>|<label>) <mapping>");
			Print(fmt, "rip",    "resetinput",             "(<id>|<label>)");
			Print(fmt, "cip",    "configinput",            "(<id>|<label>) [(s)et|(a)ppend]");
			Print(fmt, "caip",   "configallinputs",        "");   
			return false;
		}
		else
			return CConsoleDebugger::ProcessToken(token, cmd);		
	}

	bool CSupermodelDebugger::InputIsValid(::CInput *input)
	{
		return input->IsUIInput() || (input->gameFlags & m_model3->GetGameInfo()->inputFlags);
	}

	void CSupermodelDebugger::ListInputs()
	{
		Print("Inputs:\n");
		if (m_inputs->Count() == 0)
			Print(" None\n");

		// Get maximum id, label and mapping widths
		size_t idAndLabelWidth = 0;
		size_t mappingWidth    = 0;
		for (unsigned i = 0; i < m_inputs->Count(); i++)
		{
			::CInput *input = (*m_inputs)[i];
			if (!InputIsValid(input))
				continue;

			idAndLabelWidth = max<size_t>(idAndLabelWidth, strlen(input->id) + strlen(input->label) + 3);
			if (!input->IsVirtual())
				mappingWidth = max<size_t>(mappingWidth, strlen(input->GetMapping()));
		}
		mappingWidth = min<size_t>(mappingWidth, 20);
		
		// Print labels, mappings and values for each input
		const char *groupLabel = NULL;
		char idAndLabel[255];
		char mapping[21];
		for (unsigned i = 0; i < m_inputs->Count(); i++)
		{
			::CInput *input = (*m_inputs)[i];
			if (!InputIsValid(input))
				continue;

			if (groupLabel == NULL || stricmp(groupLabel, input->GetInputGroup()) != 0)
			{
				groupLabel = input->GetInputGroup();
				Print(" %s:\n", groupLabel);
			}

			sprintf(idAndLabel, "%s (%s)", input->id, input->label);
			Print("  %-*s", (int)idAndLabelWidth, idAndLabel);
			if (!input->IsVirtual())
				Truncate(mapping, 20, input->GetMapping());
			else
				mapping[0] = '\0';
			Print(" %-*s %04X (%d)\n", (int)mappingWidth, mapping, input->value, input->value);
		}
	}

	void CSupermodelDebugger::Attached()
	{
		CConsoleDebugger::Attached();

		char fileName[25];
		sprintf(fileName, "Debug/%s.ds", m_model3->GetGameInfo()->id);
		LoadState(fileName);
	}

	void CSupermodelDebugger::Detaching()
	{
		char fileName[25];
		sprintf(fileName, "Debug/%s.ds", m_model3->GetGameInfo()->id);
		SaveState(fileName);
		
		CConsoleDebugger::Detaching();
	}

	bool CSupermodelDebugger::LoadModel3State(const char *fileName)
	{
		// Open file and find header 
		CBlockFile state;
		if (state.Load(fileName) != OKAY)
			return false;
		if (state.FindBlock("Debugger Model3 State") != OKAY)
		{
			state.Close();
			return false;
		}

		// Check version in header matches
		unsigned version;
		state.Read(&version, sizeof(version));
		if (version != MODEL3_STATEFILE_VERSION)
		{
			state.Close();
			return false;
		}

		// Load Model3 state
		m_model3->LoadState(&state);
		state.Close();

		// Reset debugger
		Reset();
		return true;
	}

	bool CSupermodelDebugger::SaveModel3State(const char *fileName)
	{
		// Create file with header
		CBlockFile state;
		if (state.Create(fileName, "Debugger Model3 State", __FILE__) != OKAY)
			return false;

		// Write out version in header
		unsigned version = MODEL3_STATEFILE_VERSION;
		state.Write(&version, sizeof(version));

		// Save Model3 state
		m_model3->SaveState(&state);
		state.Close();
		return true;
	}

	void CSupermodelDebugger::ResetModel3()
	{
		// Reset Model3
		m_model3->Reset();

		// Reset debugger
		Reset();
	}

	void CSupermodelDebugger::DebugLog(const char *fmt, va_list vl)
	{
		// Use the supplied logger, if any
		if (m_logger != NULL)
			m_logger->DebugLog(fmt, vl);
		else
			CConsoleDebugger::DebugLog(fmt, vl);
	}
		
	void CSupermodelDebugger::InfoLog(const char *fmt, va_list vl)
	{
		// Use the supplied logger, if any
		if (m_logger != NULL)
			m_logger->InfoLog(fmt, vl);
		else
			CConsoleDebugger::InfoLog(fmt, vl);
	}
	
	void CSupermodelDebugger::ErrorLog(const char *fmt, va_list vl)
	{
		// Use the supplied logger, if any
		if (m_logger != NULL)
			m_logger->ErrorLog(fmt, vl);
		else
			CConsoleDebugger::ErrorLog(fmt, vl);
	}

	void CSupermodelDebugger::Poll()
	{
		CConsoleDebugger::Poll();

		// Load/saving of emulator state and resetting emulator must be done here
		if (m_loadEmuState)
		{
			LoadModel3State(m_stateFile);
			m_loadEmuState = false;
			ForceBreak(false);
		}
		else if (m_saveEmuState)
		{
			SaveModel3State(m_stateFile);
			m_saveEmuState = false;
			ForceBreak(false);
		}
		else if (m_resetEmu)
		{
			ResetModel3();
			m_resetEmu = false;
			ForceBreak(false);
		}
	}
}

#endif  // SUPERMODEL_DEBUGGER