/* 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.
 *
 * $URL$
 * $Id$
 *
 */

#include "sci/sci.h"
#include "sci/resource.h"
#include "sci/engine/state.h"
#include "common/util.h"

namespace Sci {

// #define SCRIPT_DEBUG

#define END Script_None

opcode_format g_opcode_formats[128][4] = {
	/*00*/
	{Script_None}, {Script_None}, {Script_None}, {Script_None},
	/*04*/
	{Script_None}, {Script_None}, {Script_None}, {Script_None},
	/*08*/
	{Script_None}, {Script_None}, {Script_None}, {Script_None},
	/*0C*/
	{Script_None}, {Script_None}, {Script_None}, {Script_None},
	/*10*/
	{Script_None}, {Script_None}, {Script_None}, {Script_None},
	/*14*/
	{Script_None}, {Script_None}, {Script_None}, {Script_SRelative, END},
	/*18*/
	{Script_SRelative, END}, {Script_SRelative, END}, {Script_SVariable, END}, {Script_None},
	/*1C*/
	{Script_SVariable, END}, {Script_None}, {Script_None}, {Script_Variable, END},
	/*20*/
	{Script_SRelative, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_Byte, END}, {Script_Variable, Script_SVariable, Script_Byte, END},
	/*24 (24=ret)*/
	{Script_End}, {Script_Byte, END}, {Script_Invalid}, {Script_Invalid},
	/*28*/
	{Script_Variable, END}, {Script_Invalid}, {Script_Byte, END}, {Script_Variable, Script_Byte, END},
	/*2C*/
	{Script_SVariable, END}, {Script_SVariable, Script_Variable, END}, {Script_None}, {Script_Invalid},
	/*30*/
	{Script_None}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END},
	/*34*/
	{Script_Property, END}, {Script_Property, END}, {Script_Property, END}, {Script_Property, END},
	/*38*/
	{Script_Property, END}, {Script_SRelative, END}, {Script_SRelative, END}, {Script_None},
	/*3C*/
	{Script_None}, {Script_None}, {Script_None}, {Script_Invalid},
	/*40-4F*/
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	/*50-5F*/
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	/*60-6F*/
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	/*70-7F*/
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END},
	{Script_Global, END}, {Script_Local, END}, {Script_Temp, END}, {Script_Param, END}
};
#undef END

void script_adjust_opcode_formats(int res_version) {
	switch (res_version) {
	case SCI_VERSION_0:
	case SCI_VERSION_01:
		break;
	case SCI_VERSION_01_VGA:
	case SCI_VERSION_01_VGA_ODD:
	case SCI_VERSION_1_EARLY:
	case SCI_VERSION_1_LATE:
	case SCI_VERSION_1_1:
		g_opcode_formats[op_lofsa][0] = Script_Offset;
		g_opcode_formats[op_lofss][0] = Script_Offset;
		break;
	default:
		sciprintf("script_adjust_opcode_formats(): Unknown script version %d\n", res_version);
	}
}

int script_find_selector(EngineState *s, const char *selectorname) {
	for (uint pos = 0; pos < s->_selectorNames.size(); ++pos) {
		if (s->_selectorNames[pos] == selectorname)
			return pos;
	}

	warning("Could not map '%s' to any selector", selectorname);

	return -1;
}

#define FIND_SELECTOR(_slc_) map->_slc_ = script_find_selector(s, #_slc_)
#define FIND_SELECTOR2(_slc_, _slcstr_) map->_slc_ = script_find_selector(s, _slcstr_)

void script_map_selectors(EngineState *s, selector_map_t *map) {
	FIND_SELECTOR(init);
	FIND_SELECTOR(play);
	FIND_SELECTOR(replay);
	FIND_SELECTOR(x);
	FIND_SELECTOR(y);
	FIND_SELECTOR(z);
	FIND_SELECTOR(priority);
	FIND_SELECTOR(view);
	FIND_SELECTOR(loop);
	FIND_SELECTOR(cel);
	FIND_SELECTOR(brLeft);
	FIND_SELECTOR(brRight);
	FIND_SELECTOR(brTop);
	FIND_SELECTOR(brBottom);
	FIND_SELECTOR(xStep);
	FIND_SELECTOR(yStep);
	FIND_SELECTOR(nsBottom);
	FIND_SELECTOR(nsTop);
	FIND_SELECTOR(nsLeft);
	FIND_SELECTOR(nsRight);
	FIND_SELECTOR(font);
	FIND_SELECTOR(text);
	FIND_SELECTOR(type);
	FIND_SELECTOR(state);
	FIND_SELECTOR(doit);
	FIND_SELECTOR2(delete_, "delete");
	FIND_SELECTOR(signal);
	FIND_SELECTOR(underBits);
	FIND_SELECTOR(canBeHere);
	FIND_SELECTOR(client);
	FIND_SELECTOR(dx);
	FIND_SELECTOR(dy);
	FIND_SELECTOR(xStep);
	FIND_SELECTOR(yStep);
	FIND_SELECTOR2(b_movCnt, "b-moveCnt");
	FIND_SELECTOR2(b_i1, "b-i1");
	FIND_SELECTOR2(b_i2, "b-i2");
	FIND_SELECTOR2(b_di, "b-di");
	FIND_SELECTOR2(b_xAxis, "b-xAxis");
	FIND_SELECTOR2(b_incr, "b-incr");
	FIND_SELECTOR(completed);
	FIND_SELECTOR(illegalBits);
	FIND_SELECTOR(dispose);
	FIND_SELECTOR(prevSignal);
	FIND_SELECTOR(message);
	FIND_SELECTOR(modifiers);
	FIND_SELECTOR(cue);
	FIND_SELECTOR(owner);
	FIND_SELECTOR(handle);
	FIND_SELECTOR(number);
	FIND_SELECTOR(max);
	FIND_SELECTOR(cursor);
	FIND_SELECTOR(claimed);
	FIND_SELECTOR(edgeHit);
	FIND_SELECTOR(wordFail);
	FIND_SELECTOR(syntaxFail);
	FIND_SELECTOR(semanticFail);
	FIND_SELECTOR(cycler);
	FIND_SELECTOR(elements);
	FIND_SELECTOR(lsTop);
	FIND_SELECTOR(lsBottom);
	FIND_SELECTOR(lsLeft);
	FIND_SELECTOR(lsRight);
	FIND_SELECTOR(baseSetter);
	FIND_SELECTOR(who);
	FIND_SELECTOR(distance);
	FIND_SELECTOR(mover);
	FIND_SELECTOR(looper);
	FIND_SELECTOR(isBlocked);
	FIND_SELECTOR(heading);
	FIND_SELECTOR(mode);
	FIND_SELECTOR(caller);
	FIND_SELECTOR(moveDone);
	FIND_SELECTOR(vol);
	FIND_SELECTOR(pri);
	FIND_SELECTOR(min);
	FIND_SELECTOR(sec);
	FIND_SELECTOR(frame);
	FIND_SELECTOR(dataInc);
	FIND_SELECTOR(size);
	FIND_SELECTOR(palette);
	FIND_SELECTOR(moveSpeed);
	FIND_SELECTOR(cantBeHere);
	FIND_SELECTOR(nodePtr);
	FIND_SELECTOR(flags);
	FIND_SELECTOR(points);
	FIND_SELECTOR(syncCue);
	FIND_SELECTOR(syncTime);
}

static void script_dump_object(char *data, int seeker, int objsize, const Common::StringList &selectorNames) {
	int selectors, overloads, selectorsize;
	int species = (int16)READ_LE_UINT16((unsigned char *) data + 8 + seeker);
	int superclass = (int16)READ_LE_UINT16((unsigned char *) data + 10 + seeker);
	int namepos = (int16)READ_LE_UINT16((unsigned char *) data + 14 + seeker);
	int i = 0;

	sciprintf("Object\n");

	Common::hexdump((unsigned char *) data + seeker, objsize - 4, 16, seeker);
	//-4 because the size includes the two-word header

	sciprintf("Name: %s\n", namepos ? ((char *)(data + namepos)) : "<unknown>");
	sciprintf("Superclass: %x\n", superclass);
	sciprintf("Species: %x\n", species);
	sciprintf("-info-:%x\n", (int16)READ_LE_UINT16((unsigned char *) data + 12 + seeker) & 0xffff);

	sciprintf("Function area offset: %x\n", (int16)READ_LE_UINT16((unsigned char *) data + seeker + 4));
	sciprintf("Selectors [%x]:\n", selectors = (selectorsize = (int16)READ_LE_UINT16((unsigned char *) data + seeker + 6)));

	seeker += 8;

	while (selectors--) {
		sciprintf("  [#%03x] = 0x%x\n", i++, (int16)READ_LE_UINT16((unsigned char *)data + seeker) & 0xffff);

		seeker += 2;
	}

	sciprintf("Overridden functions: %x\n", selectors = overloads = (int16)READ_LE_UINT16((unsigned char *)data + seeker));

	seeker += 2;

	if (overloads < 100)
		while (overloads--) {
			int selector = (int16)READ_LE_UINT16((unsigned char *) data + (seeker));

			sciprintf("  [%03x] %s: @", selector & 0xffff, (selector >= 0 && selector < (int)selectorNames.size()) ? selectorNames[selector].c_str() : "<?>");
			sciprintf("%04x\n", (int16)READ_LE_UINT16((unsigned char *)data + seeker + selectors*2 + 2) & 0xffff);

			seeker += 2;
		}
}

static void script_dump_class(char *data, int seeker, int objsize, const Common::StringList &selectorNames) {
	int selectors, overloads, selectorsize;
	int species = (int16)READ_LE_UINT16((unsigned char *) data + 8 + seeker);
	int superclass = (int16)READ_LE_UINT16((unsigned char *) data + 10 + seeker);
	int namepos = (int16)READ_LE_UINT16((unsigned char *) data + 14 + seeker);

	sciprintf("Class\n");

	Common::hexdump((unsigned char *) data + seeker, objsize - 4, 16, seeker);

	sciprintf("Name: %s\n", namepos ? ((char *)data + namepos) : "<unknown>");
	sciprintf("Superclass: %x\n", superclass);
	sciprintf("Species: %x\n", species);
	sciprintf("-info-:%x\n", (int16)READ_LE_UINT16((unsigned char *)data + 12 + seeker) & 0xffff);

	sciprintf("Function area offset: %x\n", (int16)READ_LE_UINT16((unsigned char *)data + seeker + 4));
	sciprintf("Selectors [%x]:\n", selectors = (selectorsize = (int16)READ_LE_UINT16((unsigned char *)data + seeker + 6)));

	seeker += 8;
	selectorsize <<= 1;

	while (selectors--) {
		int selector = (int16)READ_LE_UINT16((unsigned char *) data + (seeker) + selectorsize);

		sciprintf("  [%03x] %s = 0x%x\n", 0xffff & selector, (selector >= 0 && selector < (int)selectorNames.size()) ? selectorNames[selector].c_str() : "<?>",
		          (int16)READ_LE_UINT16((unsigned char *)data + seeker) & 0xffff);

		seeker += 2;
	}

	seeker += selectorsize;

	sciprintf("Overloaded functions: %x\n", selectors = overloads = (int16)READ_LE_UINT16((unsigned char *)data + seeker));

	seeker += 2;

	while (overloads--) {
		int selector = (int16)READ_LE_UINT16((unsigned char *)data + (seeker));
		fprintf(stderr, "selector=%d; selectorNames.size() =%d\n", selector, selectorNames.size());
		sciprintf("  [%03x] %s: @", selector & 0xffff, (selector >= 0 && selector < (int)selectorNames.size()) ?
		          selectorNames[selector].c_str() : "<?>");
		sciprintf("%04x\n", (int16)READ_LE_UINT16((unsigned char *)data + seeker + selectors * 2 + 2) & 0xffff);

		seeker += 2;
	}
}

void script_dissect(ResourceManager *resmgr, int res_no, const Common::StringList &selectorNames) {
	int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	unsigned int _seeker = 0;
	Resource *script = resmgr->findResource(kResourceTypeScript, res_no, 0);
	WordMap words;

	if (!script) {
		sciprintf("Script not found!\n");
		return;
	}

	vocab_get_words(resmgr, words);

	while (_seeker < script->size) {
		int objtype = (int16)READ_LE_UINT16(script->data + _seeker);
		int objsize;
		unsigned int seeker = _seeker + 4;

		if (!objtype) {
			sciprintf("End of script object (#0) encountered.\n");
			sciprintf("Classes: %i, Objects: %i, Export: %i,\n Var: %i (all base 10)",
			          objectctr[6], objectctr[1], objectctr[7], objectctr[10]);
			return;
		}

		sciprintf("\n");

		objsize = (int16)READ_LE_UINT16(script->data + _seeker + 2);

		sciprintf("Obj type #%x, size 0x%x: ", objtype, objsize);

		_seeker += objsize;

		objectctr[objtype]++;

		switch (objtype) {
		case SCI_OBJ_OBJECT:
			script_dump_object((char *)script->data, seeker, objsize, selectorNames);
			break;

		case SCI_OBJ_CODE: {
			sciprintf("Code\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		case 3: {
			sciprintf("<unknown>\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		case SCI_OBJ_SAID: {
			sciprintf("Said\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);

			sciprintf("%04x: ", seeker);
			while (seeker < _seeker) {
				unsigned char nextitem = script->data [seeker++];
				if (nextitem == 0xFF)
					sciprintf("\n%04x: ", seeker);
				else if (nextitem >= 0xF0) {
					switch (nextitem) {
					case 0xf0:
						sciprintf(", ");
						break;
					case 0xf1:
						sciprintf("& ");
						break;
					case 0xf2:
						sciprintf("/ ");
						break;
					case 0xf3:
						sciprintf("( ");
						break;
					case 0xf4:
						sciprintf(") ");
						break;
					case 0xf5:
						sciprintf("[ ");
						break;
					case 0xf6:
						sciprintf("] ");
						break;
					case 0xf7:
						sciprintf("# ");
						break;
					case 0xf8:
						sciprintf("< ");
						break;
					case 0xf9:
						sciprintf("> ");
						break;
					}
				} else {
					nextitem = nextitem << 8 | script->data [seeker++];
					sciprintf("%s[%03x] ", vocab_get_any_group_word(nextitem, words), nextitem);
				}
			}
			sciprintf("\n");
		}
		break;

		case SCI_OBJ_STRINGS: {
			sciprintf("Strings\n");
			while (script->data [seeker]) {
				sciprintf("%04x: %s\n", seeker, script->data + seeker);
				seeker += strlen((char *)script->data + seeker) + 1;
			}
			seeker++; // the ending zero byte
		};
		break;

		case SCI_OBJ_CLASS:
			script_dump_class((char *)script->data, seeker, objsize, selectorNames);
			break;

		case SCI_OBJ_EXPORTS: {
			sciprintf("Exports\n");
			Common::hexdump((unsigned char *)script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		case SCI_OBJ_POINTERS: {
			sciprintf("Pointers\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		case 9: {
			sciprintf("<unknown>\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		case SCI_OBJ_LOCALVARS: {
			sciprintf("Local vars\n");
			Common::hexdump(script->data + seeker, objsize - 4, 16, seeker);
		};
		break;

		default:
			sciprintf("Unsupported!\n");
			return;
		}

	}

	sciprintf("Script ends without terminator\n");
}

} // End of namespace Sci