/* 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/engine/state.h"

namespace Sci {

reg_t read_selector(SegManager *segMan, reg_t object, Selector selector_id) {
	ObjVarRef address;

	if (lookup_selector(segMan, object, selector_id, &address, NULL) != kSelectorVariable)
		return NULL_REG;
	else
		return *address.getPointer(segMan);
}

void write_selector(SegManager *segMan, reg_t object, Selector selector_id, reg_t value) {
	ObjVarRef address;

	if ((selector_id < 0) || (selector_id > (int)((SciEngine*)g_engine)->getKernel()->getSelectorNamesSize())) {
		warning("Attempt to write to invalid selector %d of"
		         " object at %04x:%04x.", selector_id, PRINT_REG(object));
		return;
	}

	if (lookup_selector(segMan, object, selector_id, &address, NULL) != kSelectorVariable)
		warning("Selector '%s' of object at %04x:%04x could not be"
		         " written to", ((SciEngine*)g_engine)->getKernel()->getSelectorName(selector_id).c_str(), PRINT_REG(object));
	else
		*address.getPointer(segMan) = value;
}

int invoke_selector(EngineState *s, reg_t object, int selector_id, SelectorInvocation noinvalid, 
	StackPtr k_argp, int k_argc, int argc, ...) {
	va_list argp;
	int i;
	int framesize = 2 + 1 * argc;
	reg_t address;
	int slc_type;
	StackPtr stackframe = k_argp + k_argc;

	stackframe[0] = make_reg(0, selector_id);  // The selector we want to call
	stackframe[1] = make_reg(0, argc); // Argument count

	slc_type = lookup_selector(s->_segMan, object, selector_id, NULL, &address);

	if (slc_type == kSelectorNone) {
		warning("Selector '%s' of object at %04x:%04x could not be invoked",
		         s->_kernel->getSelectorName(selector_id).c_str(), PRINT_REG(object));
		if (noinvalid == kStopOnInvalidSelector)
			error("[Kernel] Not recoverable: VM was halted");
		return 1;
	}
	if (slc_type == kSelectorVariable) {
		warning("Attempting to invoke variable selector %s of object %04x:%04x", 
			s->_kernel->getSelectorName(selector_id).c_str(), PRINT_REG(object));
		return 0;
	}

	va_start(argp, argc);
	for (i = 0; i < argc; i++) {
		reg_t arg = va_arg(argp, reg_t);
		stackframe[2 + i] = arg; // Write each argument
	}
	va_end(argp);

	ExecStack *xstack;

	// Now commit the actual function:
	xstack = send_selector(s, object, object, stackframe, framesize, stackframe);

	xstack->sp += argc + 2;
	xstack->fp += argc + 2;

	run_vm(s, 0); // Start a new vm

	return 0;
}

static int _obj_locate_varselector(SegManager *segMan, Object *obj, Selector slc) {
	// Determines if obj explicitly defines slc as a varselector
	// Returns -1 if not found
	byte *buf;
	uint varnum;

	if (getSciVersion() < SCI_VERSION_1_1) {
		varnum = obj->getVarCount();
		int selector_name_offset = varnum * 2 + SCRIPT_SELECTOR_OFFSET;
		buf = obj->_baseObj + selector_name_offset;
	} else {
		if (!(obj->getInfoSelector().offset & SCRIPT_INFO_CLASS))
			obj = segMan->getObject(obj->getSuperClassSelector());

		buf = (byte *)obj->_baseVars;
		varnum = obj->getVariable(1).toUint16();
	}

	for (uint i = 0; i < varnum; i++)
		if (READ_LE_UINT16(buf + (i << 1)) == slc) // Found it?
			return i; // report success

	return -1; // Failed
}

SelectorType lookup_selector(SegManager *segMan, reg_t obj_location, Selector selector_id, ObjVarRef *varp, reg_t *fptr) {
	Object *obj = segMan->getObject(obj_location);
	int index;
	bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);

	// Early SCI versions used the LSB in the selector ID as a read/write
	// toggle, meaning that we must remove it for selector lookup.
	if (oldScriptHeader)
		selector_id &= ~1;

	if (!obj) {
		error("lookup_selector(): Attempt to send to non-object or invalid script. Address was %04x:%04x", 
				PRINT_REG(obj_location));
	}

	index = _obj_locate_varselector(segMan, obj, selector_id);

	if (index >= 0) {
		// Found it as a variable
		if (varp) {
			varp->obj = obj_location;
			varp->varindex = index;
		}
		return kSelectorVariable;
	} else {
		// Check if it's a method, with recursive lookup in superclasses
		while (obj) {
			index = obj->funcSelectorPosition(selector_id);
			if (index >= 0) {
				if (fptr)
					*fptr = obj->getFunction(index);

				return kSelectorMethod;
			} else {
				obj = segMan->getObject(obj->getSuperClassSelector());
			}
		}

		return kSelectorNone;
	}


//	return _lookup_selector_function(segMan, obj, selector_id, fptr);
}

} // End of namespace Sci