/* "CodeWorker":	a scripting language for parsing and generating text.

Copyright (C) 1996-1997, 1999-2002 Cédric Lemaire

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

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

To contact the author: codeworker@free.fr
*/

#ifdef WIN32
#pragma warning (disable : 4786)
#endif

#include <fstream>

#include "UtlException.h"
#include "ScpStream.h"
#include "CppCompilerEnvironment.h"

#include "CGRuntime.h"
#include "DtaScriptVariable.h"
#include "DtaProject.h"
#include "DtaScript.h"
#include "ExprScriptVariable.h"
#include "ExprScriptExpression.h"
#include "GrfDebugExecution.h"

namespace CodeWorker {
	class GrfDebugActivation {
	private:
		static GrfDebugExecution* _pActiveDebugger;
		GrfDebugExecution* _pPreviousActiveDebugger;

	public:
		GrfDebugActivation(GrfDebugExecution* pActiveDebugger) {
			_pPreviousActiveDebugger = _pActiveDebugger;
			_pActiveDebugger = pActiveDebugger;
		}
		~GrfDebugActivation() {
			_pActiveDebugger = _pPreviousActiveDebugger;
		}

		static void unactivateCurrentDebugger() { _pActiveDebugger = 0L; }
		static bool isActiveDebugger(GrfDebugExecution* pDebugger) { return (_pActiveDebugger == pDebugger); }
	};


	GrfDebugExecution* GrfDebugActivation::_pActiveDebugger = 0L;


	GrfDebugExecution::~GrfDebugExecution() {}

	bool GrfDebugExecution::executeCommand(GrfCommand* pCommand, DtaScriptVariable& visibility, ScpStream& stream) {
		if (!stream.skipEmpty()) return false;
		bool bContinue = false;
		std::string sIdentifier;
		int iChar = stream.readChar();
		while (((iChar >= (int) 'a') && (iChar <= (int) 'z')) || ((iChar >= (int) 'A') && (iChar <= (int) 'Z'))) {
			if ((iChar >= (int) 'A') && (iChar <= (int) 'Z')) iChar -= (int) ' ';
			sIdentifier += (char) iChar;
			iChar = stream.readChar();
		}
		if (iChar > 0) stream.goBack();
		if (sIdentifier.empty()) {
			if (stream.isEqualTo('?')) {
				help();
			} else if (!stream.isEqualTo('@')) {
				CGRuntime::traceLine("debug: syntax error");
				return false;
			}
		} else if ((sIdentifier == "h") || (sIdentifier == "help")) {
			help();
		} else if ((sIdentifier == "q") || (sIdentifier == "quit")) {
			GrfDebugActivation::unactivateCurrentDebugger();
			bContinue = true;
		} else if ((sIdentifier == "r") || (sIdentifier == "run")) {
			_bStop = false;
			_bRun  = true;
			_pNext = 0L;
			bContinue = true;
		} else if ((sIdentifier == "s") || (sIdentifier == "step")) {
			stream.skipEmpty();
			if (stream.readInt(_iIterations)) {
				if (_iIterations <= 0) {
					CGRuntime::traceLine("debug: 'step' cannot require a negative number of iterations");
					return false;
				}
			} else _iIterations = 1;
			_bStop = true;
			_bRun  = false;
			_pNext = 0L;
			bContinue = true;
		} else if ((sIdentifier == "n") || (sIdentifier == "next")) {
			stream.skipEmpty();
			if (stream.readInt(_iIterations)) {
				if (_iIterations <= 0) {
					CGRuntime::traceLine("debug: 'next' cannot require a negative number of iterations");
					return false;
				}
			} else _iIterations = 1;
			_bStop = false;
			_bRun  = false;
			_pNext = pCommand;
			bContinue = true;
		} else if ((sIdentifier == "b") || (sIdentifier == "breakpoint")) {
			stream.skipEmpty();
			std::string sFilename;
			if (!stream.readString(sFilename)) {
				CGRuntime::traceLine("debug: filename of breakpoint expected");
				return false;
			}
			stream.skipEmpty();
			std::string sIdentifier;
			if ((!stream.readIdentifier(sIdentifier)) || (sIdentifier != "at")) {
				CGRuntime::traceLine("debug: 'at' expected for giving line number");
				return false;
			}
			stream.skipEmpty();
			int iLineNumber;
			if ((!stream.readInt(iLineNumber)) || (iLineNumber <= 0)) {
				CGRuntime::traceLine("debug: positive line number expected after 'at'");
				return false;
			}
			registerBreakpoint(visibility, sFilename.c_str(), iLineNumber);
		} else if ((sIdentifier == "c") || (sIdentifier == "clear")) {
			stream.skipEmpty();
			std::string sFilename;
			int iLineNumber = -1;
			if (stream.readString(sFilename)) {
				stream.skipEmpty();
				std::string sIdentifier;
				if (stream.readIdentifier(sIdentifier)) {
					if ((sIdentifier != "at")) {
						CGRuntime::traceLine("debug: 'at' expected for giving line number");
						return false;
					}
					stream.skipEmpty();
					if ((!stream.readInt(iLineNumber)) || (iLineNumber <= 0)) {
						CGRuntime::traceLine("debug: positive line number expected after 'at'");
						return false;
					}
				}
			}
			clearBreakpoint(visibility, sFilename.c_str(), iLineNumber);
		} else if ((sIdentifier == "d") || (sIdentifier == "display")) {
			stream.skipEmpty();
			int iDisplaySize;
			if (stream.readInt(iDisplaySize) && (iDisplaySize >= 0)) _iDisplaySize = iDisplaySize;
			display(pCommand, visibility, _iDisplaySize);
		} else if ((sIdentifier == "o") || (sIdentifier == "object")) {
			DtaScript* pScript = 0L;
			ExprScriptVariable* pVariableExpr = pScript->parseVariableExpression(*pCommand->getParent(), stream);
			if (pVariableExpr == 0L) CGRuntime::traceLine("debug: syntax error on variable");
			else {
				DtaScriptVariable* pVariable = visibility.getExistingVariable(*pVariableExpr);
				if (pVariable == 0L) {
					CGRuntime::traceLine("(null)");
				} else {
					pVariable->trace(visibility);
				}
				delete pVariableExpr;
			}
		} else if (sIdentifier == "stack") {
			traceStack(pCommand, visibility);
		} else if ((sIdentifier == "l") || (sIdentifier == "local")) {
			DtaScriptVariableList* pAttributes = visibility.getAttributes();
			while (pAttributes != 0L) {
				CGRuntime::traceLine(pAttributes->getNode()->getName());
				pAttributes = pAttributes->getNext();
			}
		} else if ((sIdentifier == "t") || (sIdentifier == "trace")) {
			std::auto_ptr<DtaScript> pScript(new DtaScript(0L));
			std::auto_ptr<ExprScriptExpression> pExpr(pScript->parseExpression(*pCommand->getParent(), stream));
			if (pExpr.get() == 0L) CGRuntime::traceLine("debug: syntax error on expression");
			else {
				std::string sValue = pExpr->getValue(visibility);
				CGRuntime::traceLine(sValue);
			}
		} else {
			CGRuntime::traceLine("debug: unknown command '" + sIdentifier + "'");
			return false;
		}
		return bContinue;
	}

	void GrfDebugExecution::help() {
		CGRuntime::traceLine("Debugger");
		CGRuntime::traceLine("  Commands available:");
		CGRuntime::traceLine("    - 'b' <filename> 'at' <line> (or 'breakpoint'):");
		CGRuntime::traceLine("        sets a breakpoint into file <filename> at line <line>");
		CGRuntime::traceLine("    - 'c' [<filename> ['at' <line>]] (or 'clear'):");
		CGRuntime::traceLine("        1. clears a breakpoint into file <filename> at line <line>");
		CGRuntime::traceLine("        2. or clears all breakpoints into file <filename>");
		CGRuntime::traceLine("        3. or clears all breakpoints");
		CGRuntime::traceLine("    - 'd' [<size>] or 'display' [<size>]:");
		CGRuntime::traceLine("        current line is displayed with <size> lines above and below");
		CGRuntime::traceLine("        If size isn't specified, the last known is taken");
		CGRuntime::traceLine("    - 'h' or 'help' or '?': help");
		CGRuntime::traceLine("    - 'l' or 'local': displays local variables on the stack");
		CGRuntime::traceLine("    - 'n' or 'next': next");
		CGRuntime::traceLine("    - 'o' or 'object' <variable>: displays a variable");
		CGRuntime::traceLine("    - 'q' or 'quit' quits this debugging session");
		CGRuntime::traceLine("    - 'r' or 'run': run up to next breakpoint");
		CGRuntime::traceLine("    - 's' or 'step': go step by step");
		CGRuntime::traceLine("    - 'stack': displays stack call");
		CGRuntime::traceLine("    - 't' or 'trace' <expression>: displays result of an expression");
		CGRuntime::traceLine("    - '//' or '/*'...'*/': comment");
	}

	std::string GrfDebugExecution::display(GrfCommand* pCommand, DtaScriptVariable& visibility, int iSize, bool bEchoOn) {
		std::string sDisplayedText;
		std::string sCompleteFileName;
		const char* tcParsingFile = getParsingFilePtr(pCommand);
		if (tcParsingFile == 0L) {
			sDisplayedText  = "no debug information on the current command for displaying";
		} else {
			std::ifstream* pFile = openInputFileFromIncludePath(tcParsingFile, sCompleteFileName);
			if (pFile == 0L) {
				sDisplayedText  = "debug: unable to open file \"";
				sDisplayedText += getParsingFilePtr(pCommand);
				sDisplayedText += "\" for displaying\n";
			} else {
				setLocation(*pFile, getFileLocation(pCommand));
				setLocationAtXLines(*pFile, -iSize);
				for (int i = 0; i < 2*iSize + 1; i++) {
					std::string sLine;
					int iLocation = getLineCount(*pFile);
					if (readLine(*pFile, sLine)) {
						char sLineText[512];
						if (iSize > 0) sprintf(sLineText, "%d: ", iLocation);
						else sprintf(sLineText, "\"%s\" at %d: ", getParsingFilePtr(pCommand), iLocation);
						sLine = sLineText + sLine + "\n";
						if (i == iSize) {
							char* u = sLineText;
							while (*u != '\0') *u++ = ' ';
							sLine += sLineText;
							int iPrecedent = getLocation(*pFile);
							setLocation(*pFile, getFileLocation(pCommand));
							std::string sHeader;
							getColEmptyHeader(*pFile, sHeader);
							setLocation(*pFile, iPrecedent);
							sLine += sHeader + "^\n";
						}
						sDisplayedText += sLine;
					}
				}
				pFile->close();
				delete pFile;
			}
		}
		if (bEchoOn && !sDisplayedText.empty()) CGRuntime::traceText(sDisplayedText);
		return sDisplayedText;
	}

	void GrfDebugExecution::traceStack(GrfCommand* pCommand, DtaScriptVariable& visibility) {
		display(pCommand, visibility, 0);
		std::string sPreviousText;
		for (std::list<GrfCommand*>::iterator i = _stack.begin(); i != _stack.end(); i++) {
			if (*i != 0L) {
				std::string sText = display(*i, visibility, 0, false);
				if (sPreviousText != sText) CGRuntime::traceText(sText);
				sPreviousText = sText;
			}
		}
	}

	void GrfDebugExecution::registerBreakpoint(DtaScriptVariable& visibility, const char* sFilename, int iLineNumber) {
		if (_listOfBreakpoints.find(sFilename) == _listOfBreakpoints.end()) {
			std::string sCompleteFileName;
			std::ifstream* pFile = openInputFileFromIncludePath(sFilename, sCompleteFileName);
			if (pFile == 0L) {
				CGRuntime::traceLine("debug: unable to open file \"" + std::string(sFilename) + "\" for breakpoint");
				return;
			}
			pFile->close();
			delete pFile;
		}
		std::map<int, DtaBreakpoint>& bpList = _listOfBreakpoints[sFilename];
		if (bpList.find(iLineNumber) == bpList.end()) {
			std::string sCompleteFileName;
			std::ifstream* pFile = openInputFileFromIncludePath(sFilename, sCompleteFileName);
			if (pFile == 0L) {
				CGRuntime::traceLine("debug: unable to open file \"" + std::string(sFilename) + "\" for breakpoint");
				return;
			}
			setLocationAtXLines(*pFile, iLineNumber - 1);
			int iBeginning = getLocation(*pFile);
			setLocationAtXLines(*pFile, 1);
			int iEnd = getLocation(*pFile);
			pFile->close();
			delete pFile;
			if (iBeginning >= iEnd) {
				char tcNumber[32];
				sprintf(tcNumber, "%d", iLineNumber);
				CGRuntime::traceLine("debug: unable to set a breakpoint at line " + std::string(tcNumber));
				return;
			}
			DtaBreakpoint& bp = bpList[iLineNumber];
			bp._iBeginning = iBeginning;
			bp._iEnd = iEnd;
		}
	}

	void GrfDebugExecution::clearBreakpoint(DtaScriptVariable& /*visibility*/, const char* sFilename, int iLineNumber) {
		if (*sFilename == '\0') _listOfBreakpoints = std::map<std::string, std::map<int, DtaBreakpoint> >();
		else {
			if (_listOfBreakpoints.find(sFilename) == _listOfBreakpoints.end()) {
				CGRuntime::traceLine("debug: no breakpoint on file \"" + std::string(sFilename) + "\"");
			} else if (iLineNumber <= 0) {
				std::map<int, DtaBreakpoint> bpList;
				_listOfBreakpoints[sFilename] = bpList;
			} else {
				std::map<int, DtaBreakpoint>& bpList = _listOfBreakpoints[sFilename];
				if (bpList.find(iLineNumber) == bpList.end()) {
					char tcNumber[32];
					sprintf(tcNumber, "%d", iLineNumber);
					CGRuntime::traceLine("debug: no breakpoint at line " + std::string(tcNumber) + " into file \"" + std::string(sFilename) + "\"");
				} else {
					bpList.erase(iLineNumber);
				}
			}
		}
	}

	bool GrfDebugExecution::stopOnBreakpoint(const char* sFilename, int iCharLocation) {
		std::map<int, DtaBreakpoint>& bpList = _listOfBreakpoints[sFilename];
		for (std::map<int, DtaBreakpoint>::const_iterator i = bpList.begin(); i != bpList.end(); i++) {
			if (i->second._iEnd > iCharLocation) return (iCharLocation >= i->second._iBeginning);
		}
		return false;
	}

	void GrfDebugExecution::handleBeforeExecutionCBK(GrfCommand* pCommand, DtaScriptVariable& visibility) {
		if (!GrfDebugActivation::isActiveDebugger(this)) return;
		if (getParsingFilePtr(pCommand) != 0L) {
			GrfExecutionContext::handleBeforeExecutionCBK(pCommand, visibility);
			bool bStopOnBP = stopOnBreakpoint(getParsingFilePtr(pCommand), getFileLocation(pCommand));
			if (bStopOnBP) _iIterations = 0;
			else if (_iIterations > 0) _iIterations--;
			if (_bStop || bStopOnBP) {
				if (_iIterations > 0) {
					if (_pNext != 0L) {
						_pNext = pCommand;
						_bStop = false;
					}
				} else {
					_pNext = 0L;
					std::string sText;
					bool bContinue;
					display(pCommand, visibility, 0);
					do {
						CGRuntime::traceText(_sCursor);
						sText = CGRuntime::inputLine(true);
						ScpStream stream;
						stream << sText << "@";
						bContinue = !executeCommand(pCommand, visibility, stream);
					} while (bContinue);
				}
			}
		}
	}

	void GrfDebugExecution::handleAfterExecutionCBK(GrfCommand* pCommand, DtaScriptVariable& /*visibility*/) {
		if (_pNext == pCommand) {
			_bStop = true;
		}
	}

	void GrfDebugExecution::handleAfterExceptionCBK(GrfCommand* pCommand, DtaScriptVariable& visibility, UtlException& exception) {
		if (!GrfDebugActivation::isActiveDebugger(this)) return;
		GrfDebugActivation::unactivateCurrentDebugger();
		if (getParsingFilePtr(pCommand) != 0L) {
			std::string sText;
			bool bContinue;
			CGRuntime::traceLine(exception.getMessage());
			CGRuntime::traceLine(exception.getTraceStack());
			do {
				CGRuntime::traceText(_sCursor);
				sText = CGRuntime::inputLine(true);
				ScpStream stream;
				stream << sText << "@";
				bContinue = !executeCommand(pCommand, visibility, stream);
			} while (bContinue);
		}
	}

	SEQUENCE_INTERRUPTION_LIST GrfDebugExecution::openSession(DtaScriptVariable& visibility) {
		SEQUENCE_INTERRUPTION_LIST result;
		int iDepth = 1;
		GrfExecutionContext* pContext = getLastExecutionContext();
		while (pContext != 0L) {
			pContext = pContext->getLastExecutionContext();
			iDepth++;
		}
		char tcDepth[16];
		sprintf(tcDepth, "%d", iDepth);
		CGRuntime::traceLine("-- debug session 'D" + std::string(tcDepth) + "' --");
		GrfDebugActivation debugActivation(this);
		char sBuffer[16];
		sprintf(sBuffer, "D%d> ", iDepth);
		_sCursor = sBuffer;
		try {
			_listOfBreakpoints = std::map<std::string, std::map<int, DtaBreakpoint> >();
			result = GrfBlock::executeInternal(visibility);
		} catch(UtlException&/* exception*/) {
			CGRuntime::traceLine("-- debug session 'D" + std::string(tcDepth) + "' interrupted by an exception --");
			throw/* UtlException(exception)*/;
		}
		CGRuntime::traceLine("-- end of debug session 'D" + std::string(tcDepth) + "' --");
		return result;
	}

	void GrfDebugExecution::compileCpp(CppCompilerEnvironment& theCompilerEnvironment) const {
		CW_BODY_INDENT << "// DEBUG execution required into the source code generation script: not translated";
		CW_BODY_ENDL;
	}
}
