/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program 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 program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/algorithm.h"
#include "common/str.h"
#include "common/tokenizer.h"
#include "engines/wintermute/debugger.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/scriptables/script.h"
#include "engines/wintermute/base/scriptables/script_value.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "engines/wintermute/debugger/breakpoint.h"
#include "engines/wintermute/debugger/debugger_controller.h"
#include "engines/wintermute/debugger/watch.h"
#include "engines/wintermute/debugger/listing_providers/blank_listing_provider.h"
#include "engines/wintermute/debugger/listing_providers/cached_source_listing_provider.h"
#include "engines/wintermute/debugger/listing_providers/source_listing.h"
#define SCENGINE _engine->_game->_scEngine
#define DEBUGGER _engine->_debugger

namespace Wintermute {

DebuggerController::~DebuggerController() {
	delete _sourceListingProvider;
}

DebuggerController::DebuggerController(WintermuteEngine *vm) : _engine(vm) {
	_sourceListingProvider = new CachedSourceListingProvider();
	clear();
}

bool DebuggerController::bytecodeExists(const Common::String &filename) {
	uint32 compSize;
	byte *compBuffer = SCENGINE->getCompiledScript(filename.c_str(), &compSize);
	if (!compBuffer) {
		return false;
	} else {
		return true;
	}
}

Error DebuggerController::addBreakpoint(const char *filename, int line) {
	assert(SCENGINE);
	if (bytecodeExists(filename)) {
		SCENGINE->_breakpoints.push_back(new Breakpoint(filename, line, this));
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BYTECODE);
	}
}

Error DebuggerController::removeBreakpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_breakpoints.size() > id) {
		SCENGINE->_breakpoints.remove_at(id);
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}
}

Error DebuggerController::disableBreakpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_breakpoints.size() > id) {
		SCENGINE->_breakpoints[id]->disable();
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}
}

Error DebuggerController::enableBreakpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_breakpoints.size() > id) {
		SCENGINE->_breakpoints[id]->enable();
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}
}

Error DebuggerController::removeWatchpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_watches.size() > id) {
		SCENGINE->_watches.remove_at(id);
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}
}


Error DebuggerController::disableWatchpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_watches.size() > id) {
		SCENGINE->_watches[id]->disable();
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}
}

Error DebuggerController::enableWatchpoint(uint id) {
	assert(SCENGINE);
	if (SCENGINE->_watches.size() > id) {
		SCENGINE->_watches[id]->enable();
		return Error(SUCCESS, OK);
	} else {
		return Error(ERROR, NO_SUCH_BREAKPOINT, id);
	}

}

Error DebuggerController::addWatch(const char *filename, const char *symbol) {
	assert(SCENGINE);
	if (!bytecodeExists(filename)) {
		return Error(ERROR, NO_SUCH_BYTECODE, filename);
	}
	SCENGINE->_watches.push_back(new Watch(filename, symbol, this));
	return Error(SUCCESS, OK, "Watchpoint added");
}

void DebuggerController::onBreakpoint(const Breakpoint *breakpoint, DebuggableScript *script) {
	_lastScript = script;
	_lastLine = script->_currentLine;
	DEBUGGER->notifyBreakpoint(script->dbgGetFilename().c_str(), script->_currentLine);
}

void DebuggerController::notifyStep(DebuggableScript *script) {
	_lastScript = script;
	_lastLine = script->_currentLine;
	DEBUGGER->notifyStep(script->dbgGetFilename().c_str(), script->_currentLine);
}

void DebuggerController::onWatch(const Watch *watch, DebuggableScript *script) {
	_lastScript = script; // If script has changed do we still care?
	_lastLine = script->_currentLine;
	Common::String symbol = watch->getSymbol();
	DEBUGGER->notifyWatch(script->dbgGetFilename().c_str(), symbol.c_str(), script->resolveName(symbol)->getString());
}

Error DebuggerController::step() {
	if (!_lastScript) {
		return Error(ERROR, NOT_ALLOWED);
	}
	_lastScript->step();
	clear();
	return Error(SUCCESS, OK);
}

Error DebuggerController::stepContinue() {
	if (!_lastScript) {
		return Error(ERROR, NOT_ALLOWED);
	}
	_lastScript->stepContinue();
	return Error(SUCCESS, OK);
}

Error DebuggerController::stepFinish() {
	if (!_lastScript) {
		return Error(ERROR, NOT_ALLOWED);
	}
	_lastScript->stepFinish();
	clear();
	return Error(SUCCESS, OK);
}

void DebuggerController::clear() {
	_lastScript = nullptr;
	_lastLine = -1;
}

Common::String DebuggerController::readValue(const Common::String &name, Error *error) {
	if (!_lastScript) {
		delete error;
		error = new Error(ERROR, NOT_ALLOWED);
		return Common::String();
	}
	char cstr[256]; // TODO not pretty
	Common::strlcpy(cstr, name.c_str(), name.size() + 1);
	cstr[255] = '\0'; // We 0-terminate it just in case it's longer than 255.
	return _lastScript->resolveName(cstr)->getString();
}

Error DebuggerController::setValue(const Common::String &name, const Common::String &value, ScValue *&var) {
	if (!_lastScript) {
		return Error(ERROR, NOT_ALLOWED);
	}

	Common::String trimmed = value;
	trimmed.trim();
	char cstr[256];
	Common::strlcpy(cstr, name.c_str(), name.size() + 1); // TODO not pretty

	var = _lastScript->getVar(cstr);
	if (var->_type == VAL_INT) {
		char *endptr;
		int res = strtol(trimmed.c_str(), &endptr, 10); // TODO: Hex too?
		if (endptr == trimmed.c_str()) {
			return Error(ERROR, PARSE_ERROR);
		} else if (endptr == trimmed.c_str() + trimmed.size()) {
			// We've parsed all of it, have we?
			var->setInt(res);
		} else {
			assert(false);
			return Error(ERROR, PARSE_ERROR);
			// Something funny happened here.
		}
	} else if (var->_type == VAL_FLOAT) {
		char *endptr;
		float res = (float)strtod(trimmed.c_str(), &endptr);
		if (endptr == trimmed.c_str()) {
			return Error(ERROR, PARSE_ERROR);
		} else if (endptr == trimmed.c_str() + trimmed.size()) {
			// We've parsed all of it, have we?
			var->setFloat(res);
		} else {
			return Error(ERROR, PARSE_ERROR);
			assert(false);
			// Something funny happened here.
		}
	} else if (var->_type == VAL_BOOL) {
		Common::String str = Common::String(trimmed);
		bool valAsBool;
		if (Common::parseBool(trimmed, valAsBool)) {
			var->setBool(valAsBool);
		} else {
			return Error(ERROR, PARSE_ERROR);
		}
	} else if (var->_type == VAL_STRING) {
		var->setString(trimmed);
	} else {
		return Error(ERROR, NOT_YET_IMPLEMENTED);
	}
	return Error(SUCCESS, OK);
}

void DebuggerController::showFps(bool show) {
	_engine->_game->setShowFPS(show);
}

Common::Array<BreakpointInfo> DebuggerController::getBreakpoints() const {
	assert(SCENGINE);
	Common::Array<BreakpointInfo> breakpoints;
	for (uint i = 0; i < SCENGINE->_breakpoints.size(); i++) {
		BreakpointInfo bpInfo;
		bpInfo._filename = SCENGINE->_breakpoints[i]->getFilename();
		bpInfo._line = SCENGINE->_breakpoints[i]->getLine();
		bpInfo._hits = SCENGINE->_breakpoints[i]->getHits();
		bpInfo._enabled = SCENGINE->_breakpoints[i]->isEnabled();
		breakpoints.push_back(bpInfo);
	}
	return breakpoints;
}

Common::Array<WatchInfo> DebuggerController::getWatchlist() const {
	Common::Array<WatchInfo> watchlist;
	for (uint i = 0; i < SCENGINE->_watches.size(); i++) {
		WatchInfo watchInfo;
		watchInfo._filename = SCENGINE->_watches[i]->getFilename();
		watchInfo._symbol = SCENGINE->_watches[i]->getSymbol();
		watchlist.push_back(watchInfo);
	}
	return watchlist;
}

uint32 DebuggerController::getLastLine() const {
	return _lastLine;
}

Common::String DebuggerController::getSourcePath() const {
	return _sourceListingProvider->getPath();
}

Error DebuggerController::setSourcePath(const Common::String &sourcePath) {
	ErrorCode err = _sourceListingProvider->setPath(sourcePath);
	return Error((err == OK ? SUCCESS : ERROR), err);
}

Listing* DebuggerController::getListing(Error* &error) {
	delete (error);
	if (_lastScript == nullptr) {
		error = new Error(ERROR, NOT_ALLOWED);
		return nullptr;
	}
	ErrorCode err;
	Listing* res = _sourceListingProvider->getListing(SCENGINE->_currentScript->_filename, err);
	error = new Error(err == OK ? SUCCESS : ERROR, err);
	return res;
}

Common::Array<TopEntry> DebuggerController::getTop() const {
	Common::Array<TopEntry> res;
	assert(SCENGINE);
	for (uint i = 0; i < SCENGINE->_scripts.size(); i++) {
		TopEntry entry;
		entry.filename = SCENGINE->_scripts[i]->_filename;
		entry.current = (SCENGINE->_scripts[i] == SCENGINE->_currentScript);
		res.push_back(entry);
	}
	return res;
}

} // end of namespace Wintermute
