/* 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 "glk/advsys/vm.h"
#include "common/translation.h"

namespace Glk {
namespace AdvSys {

#define TRUE -1

OpcodeMethod VM::_METHODS[0x34] = {
	&VM::opBRT,
	&VM::opBRF,
	&VM::opBR,
	&VM::opT,
	&VM::opNIL,
	&VM::opPUSH,
	&VM::opNOT,
	&VM::opADD,
	&VM::opSUB,
	&VM::opMUL,
	&VM::opDIV,
	&VM::opREM,
	&VM::opBAND,
	&VM::opBOR,
	&VM::opBNOT,
	&VM::opLT,
	&VM::opEQ,
	&VM::opGT,
	&VM::opLIT,
	&VM::opVAR,
	&VM::opGETP,
	&VM::opSETP,
	&VM::opSET,
	&VM::opPRINT,
	&VM::opTERPRI,
	&VM::opPNUMBER,
	&VM::opFINISH,
	&VM::opCHAIN,
	&VM::opABORT,
	&VM::opEXIT,
	&VM::opRETURN,
	&VM::opCALL,
	&VM::opSVAR,
	&VM::opSSET,
	&VM::opSPLIT,
	&VM::opSNLIT,
	&VM::opYORN,
	&VM::opSAVE,
	&VM::opRESTORE,
	&VM::opARG,
	&VM::opASET,
	&VM::opTMP,
	&VM::opTSET,
	&VM::opTSPACE,
	&VM::opCLASS,
	&VM::opMATCH,
	&VM::opPNOUN,
	&VM::opRESTART,
	&VM::opRAND,
	&VM::opRNDMIZE,
	&VM::opSEND,
	&VM::opVOWEL
};

VM::VM(OSystem *syst, const GlkGameDescription &gameDesc) : GlkInterface(syst, gameDesc), Game(),
		_fp(_stack), _pc(0), _status(IN_PROGRESS), _actor(-1), _action(-1), _dObject(-1),
		_ndObjects(-1), _iObject(-1), _wordPtr(nullptr) {
}

ExecutionResult VM::execute(int offset) {
	// Set the code pointer
	_pc = offset;

	// Clear the stack
	_fp.clear();
	_stack.clear();

	// Iterate through the script
	for (_status = IN_PROGRESS; !shouldQuit() && _status == IN_PROGRESS;)
		executeOpcode();

	return _status;
}

void VM::executeOpcode() {
	// Get next opcode
	uint opcode = readCodeByte();

	if (gDebugLevel > 0) {
		Common::String s;
		for (int idx = (int)_stack.size() - 1; idx >= 0; --idx) s += Common::String::format("  %d", _stack[idx]);
		debugC(kDebugScripts, "%.4x - %.2x - %d%s", _pc - 1, opcode, _stack.size(), s.c_str());
	}

	if (opcode >= OP_BRT && opcode <= OP_VOWEL) {
		(this->*_METHODS[(int)opcode - 1])();
	} else if (opcode >= OP_XVAR && opcode < OP_XSET) {
		_stack.top() = getVariable((int)opcode - OP_XVAR);
	} else if (opcode >= OP_XSET && opcode < OP_XPLIT) {
		setVariable((int)opcode - OP_XSET, _stack.top());
	} else if (opcode >= OP_XPLIT && opcode < OP_XNLIT) {
		_stack.top() = (int)opcode - OP_XPLIT;
	} else if (opcode >= OP_XNLIT && (int)opcode < 256) {
		_stack.top() = OP_XNLIT - opcode;
	} else {
		error("Unknown opcode %x at offset %d", opcode, _pc);
	}
}
void VM::opBRT() {
	_pc = _stack.top() ? readCodeWord() : _pc + 2;
}

void VM::opBRF() {
	_pc = !_stack.top() ? readCodeWord() : _pc + 2;
}

void VM::opBR() {
	_pc = readCodeWord();
}

void VM::opT() {
	_stack.top() = TRUE;
}

void VM::opNIL() {
	_stack.top() = NIL;
}

void VM::opPUSH() {
	_stack.push(NIL);
}

void VM::opNOT() {
	_stack.top() = _stack.top() ? NIL : TRUE;
}

void VM::opADD() {
	int v = _stack.pop();
	_stack.top() += v;
}

void VM::opSUB() {
	int v = _stack.pop();
	_stack.top() -= v;
}

void VM::opMUL() {
	int v = _stack.pop();
	_stack.top() *= v;
}

void VM::opDIV() {
	int v = _stack.pop();
	_stack.top() = (v == 0) ? 0 : _stack.top() / v;
}

void VM::opREM() {
	int v = _stack.pop();
	_stack.top() = (v == 0) ? 0 : _stack.top() % v;
}

void VM::opBAND() {
	int v = _stack.pop();
	_stack.top() &= v;
}

void VM::opBOR() {
	int v = _stack.pop();
	_stack.top() |= v;
}

void VM::opBNOT() {
	_stack.top() = ~_stack.top();
}

void VM::opLT() {
	int v = _stack.pop();
	_stack.top() = (_stack.top() < v) ? TRUE : NIL;
}

void VM::opEQ() {
	int v = _stack.pop();
	_stack.top() = (_stack.top() == v) ? TRUE : NIL;
}

void VM::opGT() {
	int v = _stack.pop();
	_stack.top() = (_stack.top() > v) ? TRUE : NIL;
}

void VM::opLIT() {
	_stack.top() = readCodeWord();
}

void VM::opVAR() {
	_stack.top() = getVariable(readCodeWord());
}

void VM::opGETP() {
	int v = _stack.pop();
	_stack.top() = getObjectProperty(_stack.top(), v);
}

void VM::opSETP() {
	int v3 = _stack.pop();
	int v2 = _stack.pop();
	_stack.top() = setObjectProperty(_stack.top(), v2, v3);
}

void VM::opSET() {
	setVariable(readCodeWord(), _stack.top());
}

void VM::opPRINT() {
	Common::String msg = readString(_stack.top());
	print(msg);
}

void VM::opTERPRI() {
	print("\n");
}

void VM::opPNUMBER() {
	print(_stack.top());
}

void VM::opFINISH() {
	_status = FINISH;
}

void VM::opCHAIN() {
	_status = CHAIN;
}

void VM::opABORT() {
	_status = ABORT;
}

void VM::opEXIT() {
	quitGame();
	_status = ABORT;
}

void VM::opRETURN() {
	if (_fp == 0) {
		_status = CHAIN;
	} else {
		int val = _stack.top();
		_stack.resize(_fp);
		_fp = _stack.pop();
		_pc = _stack.pop();

		int argsSize = _stack.pop();
		_stack.resize(_stack.size() - argsSize);
		_stack.top() = val;
	}
}

void VM::opCALL() {
	int argsSize = readCodeByte();

	_stack.push(argsSize);
	_stack.push(_pc);
	_stack.push(_fp);

	_fp.set();
	_pc = getActionField(_fp[_fp[FP_ARGS_SIZE] + FP_ARGS], A_CODE);
}

void VM::opSVAR() {
	_stack.top() = getVariable(readCodeByte());
}

void VM::opSSET() {
	setVariable(readCodeByte(), _stack.top());
}

void VM::opSPLIT() {
	_stack.top() = readCodeByte();
}

void VM::opSNLIT() {
	_stack.top() = readCodeByte();
}

void VM::opYORN() {
	Common::String line = readLine();
	_stack.top() = !line.empty() && (line[0] == 'Y' || line[0] == 'y') ? TRUE : NIL;
}

void VM::opSAVE() {
	if (saveGame().getCode() != Common::kNoError)
		print(_("Sorry, the savegame couldn't be created"));
}

void VM::opRESTORE() {
	if (loadGame().getCode() != Common::kNoError)
		print(_("Sorry, the savegame couldn't be restored"));
}

void VM::opARG() {
	int argNum = readCodeByte();
	if (argNum >= _fp[FP_ARGS_SIZE])
		error("Invalid argument number");
	_stack.top() = _fp[argNum + FP_ARGS];
}

void VM::opASET() {
	int argNum = readCodeByte();
	if (argNum >= _fp[FP_ARGS_SIZE])
		error("Invalid argument number");
	_fp[argNum + FP_ARGS] = _stack.top();
}

void VM::opTMP() {
	int val = readCodeByte();
	_stack.top() = _fp[-val - 1];
}

void VM::opTSET() {
	int val = readCodeByte();
	_fp[-val - 1] = _stack.top();
}

void VM::opTSPACE() {
	_stack.allocate(readCodeByte());
}

void VM::opCLASS() {
	_stack.top() = getObjectField(_stack.top(), O_CLASS);
}

void VM::opMATCH() {
	int idx = _stack.pop() - 1;
	_stack.top() = match(_stack.top(), _nouns[idx]._noun, _nouns[idx]._adjective) ? TRUE : NIL;
}

void VM::opPNOUN() {
	int noun = _stack.top();
	Common::String str;

	// Add the adjectives
	bool space = false;
	for (const AdjectiveEntry *aPtr = &_adjectiveList[noun - 1]; aPtr->_list; ++aPtr, space = true) {
		if (space)
			str += " ";
		str += _words[aPtr->_word]._text;
	}

	// Add the noun
	if (space)
		str += " ";
	str += _words[_nouns[noun - 1]._num]._text;

	print(str);
}

void VM::opRESTART() {
	restart();
}

void VM::opRAND() {
	_stack.top() = getRandomNumber(_stack.top());
}

void VM::opRNDMIZE() {
	// No implementation
}

void VM::opSEND() {
	int argsSize = readCodeByte();
	_stack.push(argsSize);
	_stack.push(_pc);
	_stack.push(_fp);
	_fp.set();

	int val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS];
	if (val)
		val = getObjectField(val, O_CLASS);
	else
		val = _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 1];

	if (val && (val = getObjectProperty(val, _fp[_fp[FP_ARGS_SIZE] + FP_ARGS - 2])) != 0) {
		_pc = getActionField(val, A_CODE);
	} else {
		// Return NIL if there's no action for the given message
		opRETURN();
	}
}

void VM::opVOWEL() {
	// No implementation
}

bool VM::getInput() {
	if (!parseInput())
		return false;

	setVariable(V_ACTOR, _actor);
	setVariable(V_ACTION, _action);
	setVariable(V_DOBJECT, _dObject);
	setVariable(V_NDOBJECTS, _ndObjects);
	setVariable(V_IOBJECT, _iObject);
	return true;
}

bool VM::nextCommand() {
	if (getVariable(V_NDOBJECTS) > 1) {
		setVariable(V_ACTOR, _actor);
		setVariable(V_ACTION, _action);
		setVariable(V_DOBJECT, getVariable(V_DOBJECT) + 1);
		setVariable(V_NDOBJECTS, getVariable(V_NDOBJECTS) - 1);
		setVariable(V_IOBJECT, _iObject);
		return true;
	} else {
		return false;
	}
}

bool VM::parseInput() {
	int noun1 = 0, cnt1 = 0, noun2 = 0, cnt2 = 0;
	int preposition = 0, flags = 0;

	// Initialize the parser result fields
	_actor = _action = _dObject = _iObject = 0;
	_ndObjects = 0;
	_nouns.clear();
	_adjectiveList.clear();
	_adjectiveList.reserve(20);

	// Get the input line
	if (!getLine())
		return false;

	// Check for actor
	WordType wordType = getWordType(*_wordPtr);
	if (wordType == WT_ADJECTIVE || wordType == WT_NOUN) {
		if (!(_actor = getNoun()))
			return false;
		flags |= A_ACTOR;
	}

	// Check for a verb
	if (!getVerb())
		return false;

	// Get direct object, preposition, and/or indirect object
	if (_wordPtr != _words.end()) {
		// Get any direct objects
		noun1 = _adjectiveList.size() + 1;
		for (;;) {
			// Get the next direct object
			if (!getNoun())
				return false;
			++cnt1;

			// Check for more direct objects
			if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
				break;
			++_wordPtr;
		}

		// Get any reposition and indirect object
		if (_wordPtr != _words.end()) {
			// Get the preposition
			if (getWordType(*_wordPtr) == WT_PREPOSITION)
				preposition = *_wordPtr++;

			// Get the indirect object
			noun2 = _adjectiveList.size() + 1;
			for (;;) {
				// Get the indirect object
				if (!getNoun())
					return false;
				++cnt2;

				// Check for more objects
				if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_CONJUNCTION)
					break;
				++_wordPtr;
			}
		}

		// Ensure we're at the end of the input line
		if (_wordPtr != _words.end()) {
			parseError();
			return false;
		}
	}

	// Setup resulting properties
	if (preposition) {
		if (cnt2 > 1) {
			parseError();
			return false;
		}

		_dObject = noun1;
		_ndObjects = cnt1;
		_iObject = noun2;
	} else if (noun2) {
		if (cnt1 > 1) {
			parseError();
			return false;
		}

		preposition = findWord("to");
		_dObject = noun2;
		_ndObjects = cnt2;
		_iObject = noun1;
	} else {
		_dObject = noun1;
		_ndObjects = cnt1;
	}

	// Setup the flags for the action lookup
	if (_dObject)
		flags |= A_DOBJECT;
	if (_iObject)
		flags |= A_IOBJECT;

	// Find the action
	if (!(_action = findAction(_verbs, preposition, flags))) {
		parseError();
		return false;
	}

	return true;
}

bool VM::getLine() {
	// Let the user type in an input line
	Common::String line = readLine();
	if (shouldQuit())
		return false;

	skipSpaces(line);
	if (line.empty()) {
		print(_("Speak up! I can't hear you!\n"));
		return false;
	}

	// Get the words of the line
	_words.clear();
	while (!line.empty()) {
		if (!getWord(line))
			return false;
	}

	_wordPtr = _words.begin();
	return true;
}

bool VM::getWord(Common::String &line) {
	// Find the end of the word
	const char *wordP = line.c_str();
	for (; *wordP && !isWhitespace(*wordP); ++wordP) {}

	// Copy out the next word
	InputWord iw;
	iw._text = Common::String(line.c_str(), wordP);
	iw._text.toLowercase();

	// Remove the word from the line
	line = Common::String(wordP);
	skipSpaces(line);

	// Look up the word
	iw._number = findWord(iw._text);

	if (iw._number) {
		_words.push_back(iw);
		return true;
	} else {
		Common::String msg = Common::String::format(_("I don't know the word \"%s\".\n"), iw._text.c_str());
		print(msg);
		return false;
	}
}

uint VM::getNoun() {
	// Skip over optional article if present
	if (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ARTICLE)
		++_wordPtr;

	// Get optional adjectives
	uint alStart = _adjectiveList.size();
	while (_wordPtr != _words.end() && getWordType(*_wordPtr) == WT_ADJECTIVE) {
		AdjectiveEntry ae;
		ae._list = *_wordPtr++;
		ae._word = _wordPtr - _words.begin() - 1;
		_adjectiveList.push_back(ae);
	}
	_adjectiveList.push_back(AdjectiveEntry());
	assert(_adjectiveList.size() <= 20);

	if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_NOUN) {
		parseError();
		return NIL;
	}

	// Add a noun entry to the list
	Noun n;
	n._adjective = &_adjectiveList[alStart];
	n._noun = *_wordPtr++;
	n._num = _wordPtr - _words.begin() - 1;
	_nouns.push_back(n);

	return _nouns.size();
}

bool VM::getVerb() {
	_verbs.clear();

	if (_wordPtr == _words.end() || getWordType(*_wordPtr) != WT_VERB) {
		parseError();
		return false;
	}

	_verbs.push_back(*_wordPtr++);

	// Check for a word following the verb
	if (_wordPtr < _words.end()) {
		_verbs.push_back(*_wordPtr);

		if (checkVerb(_verbs)) {
			++_wordPtr;
		} else {
			_verbs.pop_back();
			_verbs.push_back(_words.back());

			if (checkVerb(_verbs)) {
				_words.pop_back();
			} else {
				_verbs.pop_back();

				if (!checkVerb(_verbs)) {
					parseError();
					return false;
				}
			}
		}
	}

	return true;
}

bool VM::match(int obj, int noun, const VM::AdjectiveEntry *adjectives) {
	if (!hasNoun(obj, noun))
		return false;

	for (const VM::AdjectiveEntry *adjPtr = adjectives; adjPtr->_list; ++adjPtr) {
		if (!hasAdjective(obj, adjPtr->_list))
			return false;
	}

	return true;
}

void VM::parseError() {
	print(_("I don't understand.\n"));
}

bool VM::isWhitespace(char c) {
	return c == ' ' || c == ',' || c == '.';
}

bool VM::skipSpaces(Common::String &str) {
	while (!str.empty() && isWhitespace(str[0]))
		str.deleteChar(0);

	return !str.empty();
}

} // End of namespace AdvSys
} // End of namespace Glk