//------------------------------------------------------------------------------
// Revenge Of The Cats: Ethernet
// Copyright notices are in the file named COPYING.
//------------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
// Remote scripting debugger

// To use the debugger, use dbgSetParameters(port, password); in the console
// of the server to enable debugger connections.  Then on some other system,
// start up the app (you don't have to start a game or connect to the
// server) and exec("common/debugger/debugger.cs"); in the console.  Then use
// the debugger GUI to connect to the server with the right port and password.



// Create the GUIs.
exec("./DebuggerBreakConditionDlg.gui");
exec("./DebuggerConnectDlg.gui");
exec("./DebuggerEditWatchDlg.gui");
exec("./DebuggerFindDlg.gui");
exec("./DebuggerGui.gui");
exec("./DebuggerWatchDlg.gui");

// Create a TCP object named TCPDebugger.
new TCPObject(TCPDebugger);


// Used to get unique IDs for breakpoints and watch expressions.
$DbgBreakId = 0;
$DbgWatchSeq = 1;


// Functions for the TCPDebugger object:

// onLine is invoked whenever the TCP object receives a line from the server.
// Treat the first word as a "command" and dispatch to an appropriate
// handler.
function TCPDebugger::onLine(%this, %line)
{
	echo("Got line=>" @ %line);
	%cmd = firstWord(%line);
	%rest = restWords(%line);
	
	if (%cmd $= "PASS") {
		%this.handlePass(%rest);
	}
	else if(%cmd $= "COUT") {
		%this.handleLineOut(%rest);
	}
	else if(%cmd $= "FILELISTOUT") {
		%this.handleFileList(%rest);
	}
	else if(%cmd $= "BREAKLISTOUT") {
		%this.handleBreakList(%rest);
	}
	else if(%cmd $= "BREAK") {
		%this.handleBreak(%rest);
	}
	else if(%cmd $= "RUNNING") {
		%this.handleRunning();
	}
	else if(%cmd $= "EVALOUT") {
		%this.handleEvalOut(%rest);
	}
	else {
		%this.handleError(%line);
	}
}

// Handler for PASS response.
function TCPDebugger::handlePass(%this, %message)
{
	if (%message $= "WrongPass") {
		DebuggerConsoleView.print("Disconnected - wrong password.");	
		%this.disconnect();
	}
	else if(%message $= "Connected.") {
		DebuggerConsoleView.print("Connected.");
		DebuggerStatus.setValue("CONNECTED");
		%this.send("FILELIST\r\n");
	}
}

// Handler for COUT response.
function TCPDebugger::handleLineOut(%this, %line)
{
	DebuggerConsoleView.print(%line);
}

// Handler for FILELISTOUT response.
function TCPDebugger::handleFileList(%this, %line)
{
	DebuggerFilePopup.clear();
	%word = 0;
	while ((%file = getWord(%line, %word)) !$= "") {
		%word++;
		DebuggerFilePopup.add(%file, %word);
	}
}

// Handler for BREAKLISTOUT response.
function TCPDebugger::handleBreakList(%this, %line)
{
	%file = getWord(%line, 0);
	if (%file != $DebuggerFile) {
		return;
	}
	%pairs = getWord(%line, 1);
	%curLine = 1;
	DebuggerFileView.clearBreakPositions();
	
	// Set the possible break positions.
	for (%i = 0; %i < %pairs; %i++) {
		%skip = getWord(%line, %i * 2 + 2);
		%breaks = getWord(%line, %i * 2 + 3);
		%curLine += %skip;
		for (%j = 0; %j < %breaks; %j++) {
			DebuggerFileView.setBreakPosition(%curLine);
			%curLine++;
		}
	}

	// Now set the actual break points.
	for (%i = 0; %i < DebuggerBreakPoints.rowCount(); %i++) {
		%breakText = DebuggerBreakPoints.getRowText(%i);
		%breakLine = getField(%breakText, 0);
		%breakFile = getField(%breakText, 1);
		if (%breakFile == $DebuggerFile) {
			DebuggerFileView.setBreak(%breakLine);
		}
	}
}

// Handler for BREAK response.
function TCPDebugger::handleBreak(%this, %line)
{
	DebuggerStatus.setValue("BREAK");
	
	// Query all the watches.
	for (%i = 0; %i < DebuggerWatchView.rowCount(); %i++) {
		%id = DebuggerWatchView.getRowId(%i);
		%row = DebuggerWatchView.getRowTextById(%id);
		%expr = getField(%row, 0);
		%this.send("EVAL " @ %id @ " 0 " @ %expr @ "\r\n");
	}

	// Update the call stack window.
	DebuggerCallStack.clear();

	%file = getWord(%line, 0);
	%lineNumber = getWord(%line, 1);
	%funcName = getWord(%line, 2);
	
	DbgOpenFile(%file, %lineNumber, true);

	%nextWord = 3;
	%rowId = 0;
	%id = 0;
	while(1) {
		DebuggerCallStack.setRowById(%id, %file @ "\t" @ %lineNumber @ "\t" @ %funcName);
		%id++;
		%file = getWord(%line, %nextWord);
		%lineNumber = getWord(%line, %nextWord + 1);
		%funcName = getWord(%line, %nextWord + 2);
		%nextWord += 3;
		if (%file $= "") {
			break;
		}
	}
}

// Handler for RUNNING response.
function TCPDebugger::handleRunning(%this)
{
	DebuggerFileView.setCurrentLine(-1, true);
	DebuggerCallStack.clear();
	DebuggerStatus.setValue("RUNNING...");
}

// Handler for EVALOUT response.
function TCPDebugger::handleEvalOut(%this, %line)
{
	%id = firstWord(%line);
	%value = restWords(%line);

	// See if it's the cursor watch, or from the watch window.
	if (%id < 0) {
		DebuggerCursorWatch.setText(DebuggerCursorWatch.expr SPC "=" SPC %value);
	}
	else {
		%row = DebuggerWatchView.getRowTextById(%id);
		if (%row $= "") {
			return;
		}
		%expr = getField(%row, 0);
		DebuggerWatchView.setRowById(%id, %expr @ "\t" @ %value);
	}
}

// Handler for unrecognized response.
function TCPDebugger::handleError(%this, %line)
{
	DebuggerConsoleView.print("ERROR - bogus message: " @ %line);
}



// XXX Do we want/need to define any of these?

function TCPDebugger::onDNSResolve(%this)
{
}

function TCPDebugger::onConnecting(%this)
{
}

function TCPDebugger::onConnected(%this)
{
}

function TCPDebugger::onConnectFailed(%this)
{
}

function TCPDebugger::onDisconnect(%this)
{
}



// Functions for the GUI objects:

// Print a line of response from the server.
function DebuggerConsoleView::print(%this, %line)
{
	%row = %this.addRow(0, %line);
	%this.scrollVisible(%row);
}

// When entry in file list selected, open the file.
function DebuggerFilePopup::onSelect(%this, %id, %text)
{
	DbgOpenFile(%text, 0, false);
}

// When entry on call stack selected, open the file and go to the line.
function DebuggerCallStack::onAction(%this)
{
	%id = %this.getSelectedId();
	if (%id == -1) {
		return;
	}
	%text = %this.getRowTextById(%id);
	%file = getField(%text, 0);
	%line = getField(%text, 1);
	
	DbgOpenFile(%file, %line, %id == 0);
}

// Add a breakpoint at the selected spot, if it doesn't already exist.
function DebuggerBreakPoints::addBreak(%this, %file, %line, %clear, %passct, %expr)
{
	// columns 0 = line, 1 = file, 2 = expr
	%textLine = %line @ "\t" @ %file @ "\t" @ %expr @ "\t" @ %passct @ "\t" @ %clear;
	%selId = %this.getSelectedId();
	%selText = %this.getRowTextById(%selId);
	if ((getField(%selText, 0) $= %line) && (getField(%selText, 1) $= %file)) {
		%this.setRowById(%selId, %textLine);
	}
	else {
		%this.addRow($DbgBreakId, %textLine);
		$DbgBreakId++;
	}
}

// Remove the selected breakpoint.
function DebuggerBreakPoints::removeBreak(%this, %file, %line)
{
	for (%i = 0; %i < %this.rowCount(); %i++) {
		%id = %this.getRowId(%i);
		%text = %this.getRowTextById(%id);
		if ((getField(%text, 0) $= %line) && (getField(%text, 1) $= %file)) {
			%this.removeRowById(%id);
			return;
		}
	}
}

// Remove all breakpoints.
function DebuggerBreakPoints::clearBreaks(%this)
{
	while (%this.rowCount()) {
		%id = %this.getRowId(0);
		%text = %this.getRowTextById(%id);
		%file = getField(%text, 1);
		%line = getField(%text, 0);
		DbgRemoveBreakPoint(%file, %line);
	}
}

// Go to file & line for the selected breakpoint.
function DebuggerBreakPoints::onAction(%this)
{
	%id = %this.getSelectedId();
	if (%id == -1) {
		return;
	}
	%text = %this.getRowTextById(%id);
	%line = getField(%text, 0);
	%file = getField(%text, 1);
	
	DbgOpenFile(%file, %line, false);
}

// Handle breakpoint removal executed from the file-view GUI.
function DebuggerFileView::onRemoveBreakPoint(%this, %line)
{
	%file = $DebuggerFile;
	DbgRemoveBreakPoint(%file, %line);
}

// Handle breakpoint addition executed from the file-view GUI.
function DebuggerFileView::onSetBreakPoint(%this, %line)
{
	%file = $DebuggerFile;
	DbgSetBreakPoint(%file, %line, 0, 0, true);
}


// Support functions:

// Add a watch expression.
function DbgWatchDialogAdd()
{
	%expr = WatchDialogExpression.getValue();
	if (%expr !$= "") {
		DebuggerWatchView.setRowById($DbgWatchSeq, %expr @"\t(unknown)");
		TCPDebugger.send("EVAL " @ $DbgWatchSeq @ " 0 " @ %expr @ "\r\n");
		$DbgWatchSeq++;
	}
	Canvas.popDialog(DebuggerWatchDlg);
}

// Edit a watch expression.
function DbgWatchDialogEdit()
{
	%newValue = EditWatchDialogValue.getValue();
	%id = DebuggerWatchView.getSelectedId();
	if (%id >= 0) {
		%row = DebuggerWatchView.getRowTextById(%id);
		%expr = getField(%row, 0);
		if (%newValue $= "") {
			%assignment = %expr @ " = \"\"";
		}
		else {
			%assignment = %expr @ " = " @ %newValue;
		}
		TCPDebugger.send("EVAL " @ %id  @ " 0 " @ %assignment @ "\r\n");
	}
	Canvas.popDialog(DebuggerEditWatchDlg);
}

// Set/change the singular "cursor watch" expression.
function DbgSetCursorWatch(%expr)
{
	DebuggerCursorWatch.expr = %expr;
	if (DebuggerCursorWatch.expr $= "") {
		DebuggerCursorWatch.setText("");
	}
	else {
		TCPDebugger.send("EVAL -1 0 " @ DebuggerCursorWatch.expr @ "\r\n");
	}
}

// Connect to the server with the given addr/port/password.
function DbgConnect()
{
	%address = DebuggerConnectAddress.getValue();
	%port = DebuggerConnectPort.getValue();
	%password = DebuggerConnectPassword.getValue();

	if ((%address !$= "" ) && (%port !$= "" ) && (%password !$= "" )) {
		TCPDebugger.connect(%address @ ":" @ %port);
		TCPDebugger.schedule(5000, send, %password @ "\r\n");
		TCPDebugger.password = %password;
	}

	Canvas.popDialog(DebuggerConnectDlg);
}

// Put a condition on a breakpoint.
function DbgBreakConditionSet()
{
	// Read the condition.
	%condition = BreakCondition.getValue();
	%passct = BreakPassCount.getValue();
	%clear = BreakClear.getValue();
	if (%condition $= "") {
		%condition = "true";
	}
	if (%passct $= "") {
		%passct = "0";
	}
	if (%clear $= "") {
		%clear = "false";
	}
	
	// Set the condition.
	%id = DebuggerBreakPoints.getSelectedId();
	if (%id != -1) {
		%bkp = DebuggerBreakPoints.getRowTextById(%id);
		DbgSetBreakPoint(getField(%bkp, 1), getField(%bkp, 0), %clear, %passct, %condition);
	}

	Canvas.popDialog(DebuggerBreakConditionDlg);
}

// Open a file, go to the indicated line, and optionally select the line.
function DbgOpenFile(%file, %line, %selectLine)
{
	if (%file !$= "") {
		// Open the file in the file view.
		if (DebuggerFileView.open(%file)) {
			// Go to the line.
			DebuggerFileView.setCurrentLine(%line, %selectLine);
			// Get the breakpoints for this file.
			if (%file !$= $DebuggerFile) {
				TCPDebugger.send("BREAKLIST " @ %file @ "\r\n");
				$DebuggerFile = %file;
			}
		}
	}
}

// Search in the fileview GUI.
function DbgFileViewFind()
{
	%searchString = DebuggerFindStringText.getValue();
	DebuggerFileView.findString(%searchString);

	Canvas.popDialog(DebuggerFindDlg);
}

// Set a breakpoint, optionally with condition.
function DbgSetBreakPoint(%file, %line, %clear, %passct, %expr)
{
	if (!%clear) {
		if (%file == $DebuggerFile) {
			DebuggerFileView.setBreak(%line);
		}
	}
	DebuggerBreakPoints.addBreak(%file, %line, %clear, %passct, %expr);
	TCPDebugger.send("BRKSET " @ %file @ " " @ %line @ " " @ %clear @ " " @ %passct @ " " @ %expr @ "\r\n");
}

// Remove a breakpoint.
function DbgRemoveBreakPoint(%file, %line)
{
	if (%file == $DebuggerFile) {
		DebuggerFileView.removeBreak(%line);
	}
	TCPDebugger.send("BRKCLR " @ %file @ " " @ %line @ "\r\n");
	DebuggerBreakPoints.removeBreak(%file, %line);
}

// Remove whatever breakpoint is selected in the breakpoints GUI.
function DbgDeleteSelectedBreak()
{
	%selectedBreak = DebuggerBreakPoints.getSelectedId();
	%rowNum = DebuggerBreakPoints.getRowNumById(%selectedWatch);
	if (%rowNum >= 0) {
		%breakText = DebuggerBreakPoints.getRowText(%rowNum);
		%breakLine = getField(%breakText, 0);
		%breakFile = getField(%breakText, 1);
		DbgRemoveBreakPoint(%breakFile, %breakLine);
	}
}

// Send an expression to the server for evaluation.
function DbgConsoleEntryReturn()
{
	%msg = DbgConsoleEntry.getValue();
	if (%msg !$= "") {
		DebuggerConsoleView.print("%" @ %msg);
		if (DebuggerStatus.getValue() $= "NOT CONNECTED") {
			DebuggerConsoleView.print("*** Not connected.");
		}
		else if (DebuggerStatus.getValue() $= "BREAK") {
			DebuggerConsoleView.print("*** Target is in BREAK mode.");
		}
		else {
			TCPDebugger.send("CEVAL " @ %msg @ "\r\n");
		}
	}
	DbgConsoleEntry.setValue("");
}

// Print a line from the server.
function DbgConsolePrint(%status)
{
	DebuggerConsoleView.print(%status);
}

// Delete the currently selected watch expression.
function DbgDeleteSelectedWatch()
{
	%selectedWatch = DebuggerWatchView.getSelectedId();
	%rowNum = DebuggerWatchView.getRowNumById(%selectedWatch);
	DebuggerWatchView.removeRow(%rowNum);
}

// Evaluate all the watch expressions.
function DbgRefreshWatches()
{
	for (%i = 0; %i < DebuggerWatchView.rowCount(); %i++) {
		%id = DebuggerWatchView.getRowId(%i);
		%row = DebuggerWatchView.getRowTextById(%id);
		%expr = getField(%row, 0);
		TCPDebugger.send("EVAL " @ %id @ " 0 " @ %expr @ "\r\n");
	}
}

// Various functions for doing incremental execution.

function dbgStepIn()
{
	TCPDebugger.send("STEPIN\r\n");
}

function dbgStepOut()
{
	TCPDebugger.send("STEPOUT\r\n");
}

function dbgStepOver()
{
	TCPDebugger.send("STEPOVER\r\n");
}

function dbgContinue()
{
	TCPDebugger.send("CONTINUE\r\n");
}


// Start up the GUI.
DebuggerConsoleView.setActive(false);
Canvas.pushDialog(DebuggerGui);

