/* ScummVM Tools
 *
 * ScummVM Tools 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 3 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include <sciresource.h>
#include <console.h>
#include <script.h>
#include <vocabulary.h>
#include <old_objects.h>
#include <stdio.h>
#include <stdlib.h>
#include <util.h>
#include <vm.h>
#include <assert.h>

#ifdef SCI_CONSOLE
#define printf sciprintf
/* Yeah, I shouldn't be doing this ;-) [CJR] */
#endif

FLEXARRAY_NOEXTRA(object*) fobjects;

static int knames_count;
static char** knames;
static char** snames;
static opcode* opcodes;

object **object_map, *object_root;
int max_object;

const char* globals[] = {
	/*00*/
	"ego",
	"GAMEID",
	"roomXX",
	"speed",
	/*04*/
	"quitFlag",
	"cast",
	"regions",
	"timer",
	/*08*/
	"sounds",
	"inv",
	"eventHandler",
	"roomNumberExit",
	/*0C*/
	"previousRoomNumber",
	"roomNumber",
	"enterDebugModeOnRoomExit",
	"score",
	/*10*/
	"maximumScore",
	"11",
	"speed",
	"13",
	/*14*/
	"14",
	"loadCursor",
	"normalFont",
	"restoreSaveFont", /*dialogFont*/
	/*18*/
	"18",
	"19",
	"defaultFont",
	"1B",
	/*1C*/
	"pointerToVersionNumber",
	"locales",
	"pointerToSaveGameDirectory",
	"1F"
};

static int add_object(object* obj) {
	FLEXARRAY_APPEND(object*, fobjects, obj, return 1);
	return 0;
}

static void dump(byte* data, int len) {
	int i = 0;
	while (i < len) {
		printf("%02X ", data[i++]);
		if (i % 8 == 0) printf("   ");
		if (i % 16 == 0) printf("\n");
	}
	if (i % 16) printf("\n");
}

static void printMethod(object* obj, int meth, int indent) {
	script_method* m = obj->methods[meth];
	int i, j;

	for (j = 0; j < indent*2 - 1; j++) printf(" ");
	printf("Method %s\n", snames[m->number]);

	for (i = 0; i < m->used; i++) {
		script_opcode op = m->data[i];

		for (j = 0; j < indent; j++) printf("  ");
		printf("%s ", opcodes[op.opcode].name);

		switch (op.opcode) {
		case 0x21: { /*callk*/
			if (op.arg1 > knames_count) printf("<no such kernel function %02X> ", op.arg1);
			else printf("%s ", knames[op.arg1]);
			printf("%02X", op.arg2);
		}
		break;
		case 0x28: { /*class*/
			if (op.arg1 > max_object) printf("<no such class %02X>", op.arg1);
			else {
				/* [DJ] op.arg1+1 adjusts for the <root> object */
				if (fobjects.data[op.arg1+1] == 0) printf("<null object>");
				else printf("%s", fobjects.data[op.arg1+1]->name);
			}
		}
		break;
		case 0x44: {
			if (op.arg1 > 0x20) printf("<no such global %02X> ", op.arg1);
			else printf("%s ", globals[op.arg1]);
		}
		break;
		default: {
			int args[3];
			args[0] = op.arg1;
			args[1] = op.arg2;
			args[2] = op.arg3;
			for (j = 0; j < 3; j++) {
				switch (formats[op.opcode][j]) {
				case Script_Invalid: {
					printf("<invalid> ");
				}
				break;
				case Script_None: {
					j = 3;
				}
				break;
				case Script_SByte:
				case Script_Byte: {
					printf("%02X ", args[j]);
				}
				break;
				case Script_Word:
				case Script_SVariable:
				case Script_Variable:
				case Script_SRelative:
				case Script_Property:
				case Script_Global:
				case Script_Local:
				case Script_Temp:
				case Script_Param: {
					printf("%04X ", args[j]);
				}
				break;
				case Script_SWord: {
					if (args[j] < 0) printf("-%04X", -args[j]);
					else printf("%04X", args[j]);
				}
				break;
				case Script_End: {
					printf("\n");
					return;
				}
				break;
				default: {
					printf("<unknown arg type %d> ", formats[op.opcode][j]);
				}
				}
			}
		}
		break;
		}
		printf("\n");
	}
}

static void printObject_r(object* obj, int flags, int level) {
	int i;
	for (i = 0; i < level; i++) printf("  ");
	if (obj == 0) printf("(null)\n");
	else {
		printf("%s\n", obj->name);
		if (flags&SCRIPT_PRINT_METHODS) {
			for (i = 0; i < obj->method_count; i++) {
				printMethod(obj, i, level + 1);
			}
		}
		if (flags&SCRIPT_PRINT_CHILDREN) {
			for (i = 0; i < obj->children.used; i++) {
				printObject_r(obj->children.data[i], flags, level + 1);
			}
		}
	}
}

void printObject(object* obj, int flags) {
	printf("pO(%p, %d)\n", obj, flags);
	printObject_r(obj, flags, 0);
}

static object* object_new() {
	object* obj = (object*)sci_malloc(sizeof(object));
	if (obj == 0) return 0;

	obj->parent = 0;
	FLEXARRAY_INIT(object*, obj->children);
	obj->name = 0;
	obj->selector_count = 0;
	obj->selector_numbers = 0;
	obj->methods = 0;
	obj->method_count = 0;

	return obj;
}

static int add_child(object* parent, object* child) {
	FLEXARRAY_APPEND(object*, parent->children, child, return 1);
	return 0;
}

static object* fake_object(const char* reason) {
	object* obj = object_new();
	if (obj == 0) {
#ifdef SCRIPT_DEBUG
		printf("object_new failed during fake for %s\n", reason);
#endif
		free(obj);
		return 0;
	}
	if (add_child(object_root, obj)) {
#ifdef SCRIPT_DEBUG
		printf("add_child failed during fake for %s\n", reason);
#endif
		free(obj);
		return 0;
	}
	obj->name = reason;
	if (add_object(obj)) {
#ifdef SCRIPT_DEBUG
		printf("add_object failed during fake for %s\n", reason);
#endif
		/*FIXME: clean up parent*/
		return 0;
	}
	return obj;
}

static script_method* decode_method(byte* data) {
	script_method* m;
	int done = 0;
	int pos = 0;
	static int count = 0;

	count++;

	if ((m = (script_method*)sci_malloc(sizeof(script_method))) == 0) return 0;
	FLEXARRAY_INIT(script_opcode, *m);

	while (!done) {
		int op = data[pos] >> 1;
		int size = 2 - (data[pos] & 1);
		int* args[3];
		int arg;
		int old_pos;

		FLEXARRAY_ADD_SPACE(script_opcode, *m, 1, return 0);
		old_pos = pos;
		m->data[m->used-1].pos = pos;
		m->data[m->used-1].opcode = op;

		/*Copy the adresses of the args to an array for convenience*/
		args[0] = &m->data[m->used-1].arg1;
		args[1] = &m->data[m->used-1].arg2;
		args[2] = &m->data[m->used-1].arg3;

		/*Skip past the opcode*/
		pos++;

		for (arg = 0; arg < 4; arg++) {
			switch (formats[op][arg]) {
			case Script_Invalid: { /*Can't happen(tm)*/
				int i;
				printf("Invalid opcode %02X at %04X in method %d\n", op, pos, count);
				for (i = m->used - 9; i < m->used - 1; i++) {
					printf("%s[%02X] ", opcodes[m->data[i].opcode].name, m->data[i].opcode);
					dump(data + m->data[i].pos, m->data[i].size);
				}
				printf("Dump from %04X-%04X\n", pos - 16, pos + 16);
				dump(data + pos - 16, 32);
			}
			break;
			case Script_None: { /*No more args*/
				arg = 4;
			}
			break;
			case Script_Byte: /*Just a one byte arg*/
			case Script_SByte: {
				*args[arg] = data[pos++];
			}
			break;
			case Script_Word: { /*A two byte arg*/
				*args[arg] = getInt16(data + pos);
				pos += 2;
			}
			break;
			case Script_SWord: { /*A signed two-byte arg*/
				int t = getInt16(data + pos);
				if (t&0x8000) *args[arg] = -(t & 0x7FFF);
				else *args[arg] = t;
				pos += 2;
			}
			break;
			case Script_Variable: /*Size of arg depends on LSB in opcode*/
			case Script_SVariable:
			case Script_SRelative:
			case Script_Property:
			case Script_Global:
			case Script_Local:
			case Script_Temp:
			case Script_Param: {
				if (size == 1) *args[arg] = data[pos++];
				else {
					*args[arg] = getInt16(data + pos);
					pos += 2;
				}
			}
			break;
			case Script_End: { /*Special tag for ret*/
				done = 1;
				arg = 4;
			}
			break;
			default: { /*Can't happen(tm)*/
				printf("Unknown argument format %d for op %02X\n", formats[op][arg], op);
			}
			break;
			}
		}
		fflush(stdout);
		if (m->used) m->data[m->used-1].size = pos - old_pos;
	}

	return m;
}

#ifdef SCRIPT_DEBUG
void list_code_blocks(resource_t* r) {
	int pos = getInt16(r->data + 2);
	while (pos < r->size - 2) {
		int type = getInt16(r->data + pos);
		int len = getInt16(r->data + pos + 2);
		if (type == 2) printf("%X-%X\n", pos, pos + len);
		pos += len;
	}
}
#endif


/*These expect the frame, the whole frame, and, well, other stuff too,
 *I guess, as long as it looks like a frame*/
static int get_type(unsigned char* obj) {
	return getInt16(obj);
}

static int get_length(unsigned char* obj) {
	return getInt16(obj + 2);
}

static int get_selector_count(unsigned char* obj) {
	return getInt16(obj + 10);
}

static int get_selector_value(unsigned char* obj, int sel) {
	assert(sel < get_selector_count(obj));
	return getInt16(obj + 12 + sel*2);
}

/*Bas things happen if the method offset value is wrong*/
static unsigned char* get_method_area(unsigned char* obj) {
	return obj + getInt16(obj + 8) + 10;
}

static int get_method_count(unsigned char* obj) {
	return getInt16(get_method_area(obj));
}

static int get_method_number(unsigned char* obj, int i) {
	assert(i < get_method_count(obj));
	return getInt16(get_method_area(obj) + 2 + 2*i);
}

static int get_method_location(unsigned char* obj, int i) {
	assert(i < get_method_count(obj));
	return getInt16(get_method_area(obj) + 4 + 2*get_method_count(obj) + 2*i);
}


/*Returns the position of the first frame of type 'type' in resource 'r',
 *starting from the frame starting at 'start', or -1 on failure.
 */
static int find_frame(resource_t* r, int type, unsigned int start) {
	int t = -1;
	unsigned int pos = start;
	unsigned char* frame;

	assert(start <= r->size - 4);

#ifdef SCRIPT_DEBUG
	printf("Searching for frame of type %d in script %03d, starting at %#x\n", type, r->number, start);
	dump(r->data + start, 32);
#endif

	/*Some times there's an extra byte at the beginning. Christoph?*/
#if 1
	if (pos == 0 && r->size >= 6 && \
	        !((0 < getInt16(r->data)) && (10 > getInt16(r->data)))) pos = 2;
#else
	if (pos == 0)
		pos = 2;
#endif
	frame = r->data + pos;
	while (1) {
#ifdef SCRIPT_DEBUG
		printf("offset = %#x\n", pos);
		dump(frame, 32);
#endif
		t = get_type(frame);
		if (t == type)
			break;

		if (t == 0)
			return -1;

		pos += get_length(frame);
		if (pos > (r->size - 2))
			return -1;
		frame += get_length(frame);
	}

	return pos;
}



/*FIXME: lots of things are identical to read_object and read_class. Some of
 *these would benefit from being put in separate functions.*/

static object* read_object(resource_mgr_t *resmgr, int script, int positions[1000]) {
	resource_t* r = scir_find_resource(resmgr, sci_script, script, 0);
	unsigned char* raw;
	int pos;
	object* obj;

	printf("Searching for object in script %03d\n", script);

	if (r == 0) return 0;

	/*Skip to the next object*/
#ifdef SCRIPT_DEBUG
	printf("pre skip: pos=%#x\n", positions[script]);
#endif
	pos = find_frame(r, 1, positions[script]);
#ifdef SCRIPT_DEBUG
	printf("post skip: pos=%#x\n", pos);
#endif
	if (pos == -1) return 0;
	else positions[script] = pos + get_length(r->data + pos);
#ifdef SCRIPT_DEBUG
	printf("post post: pos=%#x (len=%#x)\n", positions[script], get_length(r->data + pos));
#endif

	/*Construct the object*/
	obj = object_new();
	raw = r->data + pos;

	/*Fill in the name*/
	if (get_selector_count(raw) < 4) obj->name = "<anonymous>";
	else {
		if (get_selector_value(raw, 3))
			obj->name = (char *) r->data + get_selector_value(raw, 3);
		else obj->name = "<null>";
	}

	/*Fill in the class*/
	if (get_selector_count(raw) == 0) obj->parent = object_root;
	else {
		int parent_id = get_selector_value(raw, 1);
		if (parent_id >= fobjects.used) {
			free(obj);
			return 0;
		}
		if (parent_id < 1) obj->parent = object_root;
		else obj->parent = fobjects.data[parent_id];
	}

	/*Add the object to the class*/
	if (!obj->parent) {
		free(obj);
		return 0;
	}
	if (add_child(obj->parent, obj)) {
		free(obj);
		return 0;
	}
	if (add_object(obj)) {
		free(obj);
		return 0;
	}

	/*FIXME: decode selectors here*/

	obj->method_count = get_method_count(raw);
	obj->methods = (script_method**)sci_malloc(obj->method_count * sizeof(script_method));
	if (obj->methods == 0) {
		free(obj);
		return 0;
	} else {
		int i;
		for (i = 0; i < obj->method_count; i++) {
			int number = get_method_number(raw, i);
			int position = get_method_location(raw, i);

			if ((obj->methods[i] = decode_method(r->data + position)) == 0) {
				obj->method_count = i - 1;
				break;
			}
			obj->methods[i]->number = number;
		}
	}

	return obj;
}

static object* read_class(resource_mgr_t *resmgr, int script, int positions[1000]) {
	resource_t* r = scir_find_resource(resmgr, sci_script, script, 0);
	unsigned char* raw;
	int pos;
	object* obj;

	printf("Searching for class in script %03d\n", script);

	if (r == 0) return fake_object("<resource not found>");

	/*Skip to the next class*/
#ifdef SCRIPT_DEBUG
	printf("pre skip: pos=%#x\n", positions[script]);
#endif
	pos = find_frame(r, 6, positions[script]);
#ifdef SCRIPT_DEBUG
	printf("post skip: pos=%#x\n", pos);
#endif
	if (pos == -1) return fake_object("<no more classes in script>");
	else positions[script] = pos + get_length(r->data + pos);
#ifdef SCRIPT_DEBUG
	printf("post post: pos=%#x (len=%#x)\n", positions[script], get_length(r->data + pos));
#endif

	/*Construct the object*/
	obj = object_new();
	raw = r->data + pos;

	/*Fill in the name*/
	if (get_selector_count(raw) < 4) obj->name = "<anonymous>";
	else {
		if (get_selector_value(raw, 3))
			obj->name = (char *) r->data + get_selector_value(raw, 3);
		else obj->name = "<null>";
	}

	/*Fill in the parent*/
	if (get_selector_count(raw) == 0) obj->parent = object_root;
	else {
		int superclass_id = get_selector_value(raw, 1);
		printf("superclass==%d\n", superclass_id);
		if (superclass_id >= fobjects.used) {
			free(obj);
			return fake_object("<no such superclass>");
		}
		if (superclass_id < 1) obj->parent = object_root;
		else obj->parent = fobjects.data[superclass_id];
	}

	/*Add the class to the hierarchy*/
	if (!obj->parent) {
		free(obj);
		return fake_object("<null parent>");
	}
	if (add_child(obj->parent, obj)) {
		free(obj);
		return fake_object("<add_child failed>");
	}
	if (add_object(obj)) {
		free(obj);
		return fake_object("<add_object failed>");
	}

	/*FIXME: decode selectors and methods here*/

	return obj;
}

void freeObject(object* obj) {
	int i;
	for (i = 0; i < obj->children.used; i++) freeObject(obj->children.data[i]);
	free(obj);
}

static int objects_init(resource_mgr_t *resmgr) {
	FLEXARRAY_INIT(object*, fobjects);
	max_object = 0;

	if ((object_root = object_new()) == 0) return 1;
	object_root->name = "<root>";
	add_object(object_root);

	opcodes = vocabulary_get_opcodes(resmgr);
	knames = vocabulary_get_knames(resmgr, &knames_count);
	snames = vocabulary_get_snames(resmgr, NULL, 0);

	return 0;
}

int loadObjects(resource_mgr_t *resmgr) {
	int i;
	int *classes, class_count;
	int positions[1000];

	if (objects_init(resmgr)) {
#ifdef SCRIPT_DEBUG
		perror("objects_init");
#endif
		return 1;
	}
	classes = vocabulary_get_classes(resmgr, &class_count);

	for (i = 0; i < 1000; i++) positions[i] = 0;
	for (i = 0; i < class_count; i++) {
#ifdef SCRIPT_DEBUG
		printf("\n\nReading class 0x%02X\n", i);
#endif
		if (read_class(resmgr, classes[i], positions) == 0) {
#ifdef SCRIPT_DEBUG
			fprintf(stderr, "Failed to load class %d, which is a parent.\n", i);
#endif
			return 1;
		}
	}

	for (i = 0; i < 1000; i++) positions[i] = 0;
	for (i = 0; i < 1000; i++) while (read_object(resmgr, i, positions));

	object_map = fobjects.data;
	max_object = fobjects.used;

	return 0;
}


