/* ScummVM - Scumm Interpreter
 * Copyright (C) 2004-2005 The ScummVM project
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header$
 *
 */

#include "stdafx.h"
#include "kyra.h"
#include "script.h"
#include "resource.h"

#include "common/stream.h"
#include "common/util.h"

#define COMMAND(x) { &VMContext::x, #x }
#define OPCODE(x) { &VMContext::x, #x }

namespace Kyra {
VMContext::VMContext(KyraEngine* engine) {
	_engine = engine;
	_error = false;
		
	// now we create a list of all Command/Opcode procs and so
	static CommandEntry commandProcs[] = {
		// 0x00
		COMMAND(c1_goToLine),
		COMMAND(c1_setReturn),
		COMMAND(c1_pushRetRec),
		COMMAND(c1_push),
		// 0x04
		COMMAND(c1_push),			
		COMMAND(c1_pushVar),
		COMMAND(c1_pushFrameNeg),
		COMMAND(c1_pushFramePos),
		// 0x08
		COMMAND(c1_popRetRec),
		COMMAND(c1_popVar),
		COMMAND(c1_popFrameNeg),
		COMMAND(c1_popFramePos),
		// 0x0C
		COMMAND(c1_addToSP),
		COMMAND(c1_subFromSP),
		COMMAND(c1_execOpcode),
		COMMAND(c1_ifNotGoTo),
		// 0x10
		COMMAND(c1_negate),
		COMMAND(c1_evaluate)
	};
	_numCommands = ARRAYSIZE(commandProcs);
	_commands = commandProcs;
		
	static OpcodeEntry opcodeProcs[] = {
		// 0x00
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x04
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x08
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x0C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x10
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x14
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x18
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x1C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x20
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x24
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x28
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x2C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x30
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x34
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x38
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x3C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x40
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x44
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x48
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x4C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x50
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x54
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x58
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x5C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x60
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x64
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x68
		OPCODE(o1_0x68),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x6C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x70
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x74
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x78
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x7C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x80
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x84
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x88
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x8C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x90
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x94
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x98
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0x9C
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xA0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xA4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xA8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xAC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xB0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xB4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xB8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xBC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xC0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xC4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xC8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xCC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xD0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xD4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xD8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xDC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xE0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xE4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xE8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xEC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xF0
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xF4
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xF8
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		// 0xFC
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode),
		OPCODE(o1_unknownOpcode)
	};
	_numOpcodes = ARRAYSIZE(opcodeProcs);
	_opcodes = opcodeProcs;

	_scriptFile = NULL;
	_scriptFileSize = 0;
}
	
void VMContext::loadScript(const char* file) {
	if (_scriptFile) {
		delete [] _scriptFile;
		_scriptFileSize = 0;
	}
		
	memset(_stack, 0, sizeof(int32) * ARRAYSIZE(_stack));

	// loads the new file
	_scriptFile = _engine->resManager()->fileData(file, &_scriptFileSize);
	
	if (!_scriptFileSize || !_scriptFile) {
		error("couldn't load script file '%s'", file);
	}
	
	Common::MemoryReadStream script(_scriptFile, _scriptFileSize);
	memset(_chunks, 0, sizeof(ScriptChunk) * kCountChunkTypes);
	uint8 chunkName[sizeof("EMC2ORDR") + 1];
	
	// so lets look for our chunks :)
	while(true) {
		if (script.eof()) {
			break;
		}
		// lets read only the first 4 chars
		script.read(chunkName, sizeof(uint8) * 4);
		chunkName[4] = '\0';
			
		// check name of chunk
		if (!scumm_stricmp((const char *)chunkName, "FORM")) {			
			// FreeKyra swaps the size I only read it in BigEndian :)
			_chunks[kForm]._size = script.readUint32BE();
		} else if (!scumm_stricmp((const char *)chunkName, "TEXT")) {
			uint32 text_size = script.readUint32BE();
			text_size += text_size % 2 != 0 ? 1 : 0;
			
			_chunks[kText]._data = _scriptFile + script.pos();
			_chunks[kText]._size = READ_BE_UINT16(_chunks[kText]._data) >> 1;
			_chunks[kText]._additional = _chunks[kText]._data + (_chunks[kText]._size << 1);
			script.seek(script.pos() + text_size);
		} else if (!scumm_stricmp((const char *)chunkName, "DATA")) {
			_chunks[kData]._size = script.readUint32BE();
			_chunks[kData]._data = _scriptFile + script.pos();
			// mostly it will be the end of the file because all files should end with a 'DATA' chunk
			script.seek(script.pos() + _chunks[kData]._size);
		} else {
			// read next 4 chars
			script.read(&chunkName[4], sizeof(uint8) * 4);
			chunkName[8] = '\0';
			
			if (!scumm_stricmp((const char *)chunkName, "EMC2ORDR")) {
				_chunks[kEmc2Ordr]._size = script.readUint32BE() >> 1;
				_chunks[kEmc2Ordr]._data = _scriptFile + script.pos();
				script.seek(script.pos() + _chunks[kEmc2Ordr]._size * 2);
			} else {
				// any unkown chunk or problems with seeking through the file
				error("unknown chunk(%s)", chunkName);
			}
		}
	}
}
	
int32 VMContext::param(int32 index) {
	if (_stackPos - index - 1 >= ARRAYSIZE(_stack) || _stackPos - index - 1 < 0)
		return -0xFFFF;
	return _stack[_stackPos - index - 1];
}
	
const char* VMContext::stringAtIndex(int32 index) {
	if (index < 0 || (uint32)index >= _chunks[kText]._size)
		return 0;
	
	return (const char *)(_chunks[kText]._additional + _chunks[kText]._data[index]);
}
	
bool VMContext::startScript(int32 func) {
	if ((uint32)func >= _chunks[kEmc2Ordr]._size || func < 0) {
		debug("script doesn't support function %d", func);
		return false;
	}
			
	_instructionPos = READ_BE_UINT16(&_chunks[kEmc2Ordr]._data[func]) << 1;
	_stackPos = 0;
	_tempPos = 0;
	_delay = 0;
	_error = false;
	_scriptState = kScriptRunning;
	
	uint32 pos = 0xFFFFFFFE;
		
	// get start of next script
	for (uint32 tmp = 0; tmp < _chunks[kEmc2Ordr]._size; ++tmp) {
		if ((uint32)((READ_BE_UINT16(&_chunks[kEmc2Ordr]._data[tmp]) << 1)) > (uint32)_instructionPos &&
			(uint32)((READ_BE_UINT16(&_chunks[kEmc2Ordr]._data[tmp]) << 1)) < pos) {
			pos = ((READ_BE_UINT16(&_chunks[kEmc2Ordr]._data[tmp]) << 1));
		}
	}
		
	if (pos > _scriptFileSize) {
		pos = _scriptFileSize;
	}
		
	_nextScriptPos = pos;

	return true;
}
	
uint32 VMContext::contScript(void) {
	uint8* script_start = _chunks[kData]._data;
	assert(script_start);
	
	uint32 scriptStateAtStart = _scriptState;
		
	// runs the script
	while(true) {
		if ((uint32)_instructionPos > _chunks[kData]._size) {
			debug("_instructionPos( = %d) > _chunks[kData]._size( = %d)", _instructionPos, _chunks[kData]._size);
			_error = true;
			break;
		} else if(_instructionPos >= _nextScriptPos) {
			_scriptState = kScriptStopped;
			break;
		}
			
		_currentCommand = *(script_start + _instructionPos++);
		
		// gets out 
		if (_currentCommand & 0x80) {
			_argument = ((_currentCommand & 0x0F) << 8) | *(script_start + _instructionPos++);
			_currentCommand &= 0xF0;
		} else if (_currentCommand & 0x40) {
			_argument = *(script_start + _instructionPos++);
		} else if (_currentCommand & 0x20) {
			_instructionPos++;
			
			uint16 tmp = *(uint16*)(script_start + _instructionPos);
			tmp &= 0xFF7F;
			
			_argument = READ_BE_UINT16(&tmp);
			_instructionPos += 2;
		} else {
			debug("unknown way of getting the command (0x%X)", _currentCommand);
			// next thing
			continue;
		}
		
		_currentCommand &= 0x1f;
			
		if (_currentCommand < _numCommands) {
			CommandProc currentProc = _commands[_currentCommand].proc;
			(this->*currentProc)();
		} else {
			c1_unknownCommand();
		}
		
		if (_error) {
			_scriptState = kScriptError;
			break;
		}
			
		if (scriptStateAtStart != _scriptState) {
			break;
		}
	}

	return _scriptState;
}
} // end of namespace Kyra