/* 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/frotz/processor.h"

namespace Glk {
namespace Frotz {

#define MAX_OBJECT 2000

enum O1 {
	O1_PARENT          = 4,
	O1_SIBLING         = 5,
	O1_CHILD           = 6,
	O1_PROPERTY_OFFSET = 7,
	O1_SIZE            = 9
};

enum O4 {
	O4_PARENT          = 6,
	O4_SIBLING         = 8,
	O4_CHILD           = 10,
	O4_PROPERTY_OFFSET = 12,
	O4_SIZE            = 14
};

zword Processor::object_address(zword obj) {
	// Check object number
	if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) {
		print_string("@Attempt to address illegal object ");
		print_num(obj);
		print_string(".  This is normally fatal.");
		new_line();
		runtimeError(ERR_ILL_OBJ);
	}

	// Return object address
	if (h_version <= V3)
		return h_objects + ((obj - 1) * O1_SIZE + 62);
	else
		return h_objects + ((obj - 1) * O4_SIZE + 126);
}

zword Processor::object_name(zword object) {
	zword obj_addr;
	zword name_addr;

	obj_addr = object_address(object);

	// The object name address is found at the start of the properties
	if (h_version <= V3)
		obj_addr += O1_PROPERTY_OFFSET;
	else
		obj_addr += O4_PROPERTY_OFFSET;

	LOW_WORD(obj_addr, name_addr);

	return name_addr;
}

zword Processor::first_property(zword obj) {
	zword prop_addr;
	zbyte size;

	// Fetch address of object name
	prop_addr = object_name (obj);

	// Get length of object name
	LOW_BYTE(prop_addr, size);

	// Add name length to pointer
	return prop_addr + 1 + 2 * size;
}

zword Processor::next_property(zword prop_addr) {
	zbyte value;

	// Load the current property id
	LOW_BYTE(prop_addr, value);
	prop_addr++;

	// Calculate the length of this property
	if (h_version <= V3)
		value >>= 5;
	else if (!(value & 0x80))
		value >>= 6;
	else {
		LOW_BYTE(prop_addr, value);
		value &= 0x3f;

		if (value == 0)
			// demanded by Spec 1.0
			value = 64;
	}

	// Add property length to current property pointer
	return prop_addr + value + 1;
}

void Processor::unlink_object(zword object) {
	zword obj_addr;
	zword parent_addr;
	zword sibling_addr;

	if (object == 0) {
		runtimeError(ERR_REMOVE_OBJECT_0);
		return;
	}

	obj_addr = object_address(object);

	if (h_version <= V3) {

		zbyte parent;
		zbyte younger_sibling;
		zbyte older_sibling;
		zbyte zero = 0;

		// Get parent of object, and return if no parent
		obj_addr += O1_PARENT;
		LOW_BYTE(obj_addr, parent);
		if (!parent)
			return;

		// Get (older) sibling of object and set both parent and sibling pointers to 0
		SET_BYTE(obj_addr, zero);
		obj_addr += O1_SIBLING - O1_PARENT;
		LOW_BYTE(obj_addr, older_sibling);
		SET_BYTE(obj_addr, zero);

		// Get first child of parent (the youngest sibling of the object)
		parent_addr = object_address(parent) + O1_CHILD;
		LOW_BYTE(parent_addr, younger_sibling);

		// Remove object from the list of siblings
		if (younger_sibling == object)
			SET_BYTE(parent_addr, older_sibling);
		else {
			do {
				sibling_addr = object_address(younger_sibling) + O1_SIBLING;
				LOW_BYTE(sibling_addr, younger_sibling);
			} while (younger_sibling != object);
			SET_BYTE(sibling_addr, older_sibling);
		}
	} else {
		zword parent;
		zword younger_sibling;
		zword older_sibling;
		zword zero = 0;

		// Get parent of object, and return if no parent
		obj_addr += O4_PARENT;
		LOW_WORD(obj_addr, parent);
		if (!parent)
			return;

		// Get (older) sibling of object and set both parent and sibling pointers to 0
		SET_WORD(obj_addr, zero);
		obj_addr += O4_SIBLING - O4_PARENT;
		LOW_WORD(obj_addr, older_sibling);
		SET_WORD(obj_addr, zero);

		// Get first child of parent (the youngest sibling of the object)
		parent_addr = object_address(parent) + O4_CHILD;
		LOW_WORD(parent_addr, younger_sibling);

		// Remove object from the list of siblings
		if (younger_sibling == object) {
			SET_WORD(parent_addr, older_sibling);
		} else {
			do {
				sibling_addr = object_address(younger_sibling) + O4_SIBLING;
				LOW_WORD(sibling_addr, younger_sibling);
			} while (younger_sibling != object);
			SET_WORD(sibling_addr, older_sibling);
		}
	}
}

void Processor::z_clear_attr() {
	zword obj_addr;
	zbyte value;

	if (_storyId == SHERLOCK)
		if (zargs[1] == 48)
			return;

	if (zargs[1] > ((h_version <= V3) ? 31 : 47))
		runtimeError(ERR_ILL_ATTR);

	// If we are monitoring attribute assignment display a short note
	if (_attribute_assignment) {
		stream_mssg_on();
		print_string("@clear_attr ");
		print_object(zargs[0]);
		print_string(" ");
		print_num(zargs[1]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_CLEAR_ATTR_0);
		return;
	}

	// Get attribute address
	obj_addr = object_address(zargs[0]) + zargs[1] / 8;

	// Clear attribute bit
	LOW_BYTE(obj_addr, value);
	value &= ~(0x80 >> (zargs[1] & 7));
	SET_BYTE(obj_addr, value);
}

void Processor::z_jin() {
	zword obj_addr;

	// If we are monitoring object locating display a short note
	if (_object_locating) {
		stream_mssg_on();
		print_string("@jin ");
		print_object(zargs[0]);
		print_string(" ");
		print_object(zargs[1]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_JIN_0);
		branch(0 == zargs[1]);
		return;
	}

	obj_addr = object_address(zargs[0]);

	if (h_version <= V3) {
		zbyte parent;

		// Get parent id from object
		obj_addr += O1_PARENT;
		LOW_BYTE(obj_addr, parent);

		// Branch if the parent is obj2
		branch(parent == zargs[1]);

	} else {
		zword parent;

		// Get parent id from object
		obj_addr += O4_PARENT;
		LOW_WORD(obj_addr, parent);

		// Branch if the parent is obj2
		branch(parent == zargs[1]);
	}
}

void Processor::z_get_child() {
	zword obj_addr;

	// If we are monitoring object locating display a short note
	if (_object_locating) {
		stream_mssg_on();
		print_string("@get_child ");
		print_object(zargs[0]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_CHILD_0);
		store(0);
		branch(false);
		return;
	}

	obj_addr = object_address(zargs[0]);

	if (h_version <= V3) {
		zbyte child;

		// Get child id from object
		obj_addr += O1_CHILD;
		LOW_BYTE(obj_addr, child);

		// Store child id and branch
		store(child);
		branch(child);
	} else {
		zword child;

		// Get child id from object
		obj_addr += O4_CHILD;
		LOW_WORD(obj_addr, child);

		// Store child id and branch
		store(child);
		branch(child);
	}
}

void Processor::z_get_next_prop() {
	zword prop_addr;
	zbyte value;
	zbyte mask;

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_NEXT_PROP_0);
		store(0);
		return;
	}

	// Property id is in bottom five (six) bits
	mask = (h_version <= V3) ? 0x1f : 0x3f;

	// Load address of first property
	prop_addr = first_property(zargs[0]);

	if (zargs[1] != 0) {
		// Scan down the property list
		do {
			LOW_BYTE(prop_addr, value);
			prop_addr = next_property(prop_addr);
		} while ((value & mask) > zargs[1]);

		// Exit if the property does not exist
		if ((value & mask) != zargs[1])
			runtimeError(ERR_NO_PROP);
	}

	// Return the property id
	LOW_BYTE(prop_addr, value);
	store((zword)(value & mask));
}

void Processor::z_get_parent() {
	zword obj_addr;

	// If we are monitoring object locating display a short note
	if (_object_locating) {
		stream_mssg_on();
		print_string("@get_parent ");
		print_object(zargs[0]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_PARENT_0);
		store(0);
		return;
	}

	obj_addr = object_address(zargs[0]);

	if (h_version <= V3) {
		zbyte parent;

		// Get parent id from object
		obj_addr += O1_PARENT;
		LOW_BYTE(obj_addr, parent);

		// Store parent
		store(parent);

	} else {
		zword parent;

		// Get parent id from object
		obj_addr += O4_PARENT;
		LOW_WORD(obj_addr, parent);

		// Store parent
		store(parent);
	}
}

void Processor::z_get_prop() {
	zword prop_addr;
	zword wprop_val;
	zbyte bprop_val;
	zbyte value;
	zbyte mask;

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_PROP_0);
		store(0);
		return;
	}

	// Property id is in bottom five (six) bits
	mask = (h_version <= V3) ? 0x1f : 0x3f;

	// Load address of first property
	prop_addr = first_property(zargs[0]);

	// Scan down the property list
	for (;;) {
		LOW_BYTE(prop_addr, value);
		if ((value & mask) <= zargs[1])
			break;
		prop_addr = next_property(prop_addr);
	}

	if ((value & mask) == zargs[1]) {
		// property found

		// Load property(byte or word sized)
		prop_addr++;

		if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) {
			LOW_BYTE(prop_addr, bprop_val);
			wprop_val = bprop_val;
		} else {
			LOW_WORD(prop_addr, wprop_val);
		}
	} else {
		// property not found

		// Load default value
		prop_addr = h_objects + 2 * (zargs[1] - 1);
		LOW_WORD(prop_addr, wprop_val);
	}

	// Store the property value
	store(wprop_val);
}

void Processor::z_get_prop_addr() {
	zword prop_addr;
	zbyte value;
	zbyte mask;

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_PROP_ADDR_0);
		store(0);
		return;
	}

	if (_storyId == BEYOND_ZORK)
		if (zargs[0] > MAX_OBJECT)
			{ store(0); return; }

	// Property id is in bottom five (six) bits
	mask = (h_version <= V3) ? 0x1f : 0x3f;

	// Load address of first property
	prop_addr = first_property(zargs[0]);

	// Scan down the property list
	for (;;) {
		LOW_BYTE(prop_addr, value);
		if ((value & mask) <= zargs[1])
			break;
		prop_addr = next_property(prop_addr);
	}

	// Calculate the property address or return zero
	if ((value & mask) == zargs[1]) {

		if (h_version >= V4 && (value & 0x80))
			prop_addr++;
		store((zword)(prop_addr + 1));

	} else {
		store(0);
	}
}

void Processor::z_get_prop_len() {
	zword addr;
	zbyte value;

	// Back up the property pointer to the property id
	addr = zargs[0] - 1;
	LOW_BYTE(addr, value);

	// Calculate length of property
	if (h_version <= V3)
		value = (value >> 5) + 1;
	else if (!(value & 0x80))
		value = (value >> 6) + 1;
	else {
		value &= 0x3f;

		if (value == 0)
			value = 64;        // demanded by Spec 1.0
	}

	// Store length of property
	store(value);
}

void Processor::z_get_sibling() {
	zword obj_addr;

	if (zargs[0] == 0) {
		runtimeError(ERR_GET_SIBLING_0);
		store(0);
		branch(false);
		return;
	}

	obj_addr = object_address(zargs[0]);

	if (h_version <= V3) {
		zbyte sibling;

		// Get sibling id from object
		obj_addr += O1_SIBLING;
		LOW_BYTE(obj_addr, sibling);

		// Store sibling and branch
		store(sibling);
		branch(sibling);

	} else {
		zword sibling;

		// Get sibling id from object
		obj_addr += O4_SIBLING;
		LOW_WORD(obj_addr, sibling);

		// Store sibling and branch
		store(sibling);
		branch(sibling);
	}
}

void Processor::z_insert_obj() {
	zword obj1 = zargs[0];
	zword obj2 = zargs[1];
	zword obj1_addr;
	zword obj2_addr;

	// If we are monitoring object movements display a short note
	if (_object_movement) {
		stream_mssg_on();
		print_string("@move_obj ");
		print_object(obj1);
		print_string(" ");
		print_object(obj2);
		stream_mssg_off();
	}

	if (obj1 == 0) {
		runtimeError(ERR_MOVE_OBJECT_0);
		return;
	}

	if (obj2 == 0) {
		runtimeError(ERR_MOVE_OBJECT_TO_0);
		return;
	}

	// Get addresses of both objects
	obj1_addr = object_address(obj1);
	obj2_addr = object_address(obj2);

	// Remove object 1 from current parent
	unlink_object(obj1);

	// Make object 1 first child of object 2
	if (h_version <= V3) {
		zbyte child;

		obj1_addr += O1_PARENT;
		SET_BYTE(obj1_addr, obj2);
		obj2_addr += O1_CHILD;
		LOW_BYTE(obj2_addr, child);
		SET_BYTE(obj2_addr, obj1);
		obj1_addr += O1_SIBLING - O1_PARENT;
		SET_BYTE(obj1_addr, child);

	} else {
		zword child;

		obj1_addr += O4_PARENT;
		SET_WORD(obj1_addr, obj2);
		obj2_addr += O4_CHILD;
		LOW_WORD(obj2_addr, child);
		SET_WORD(obj2_addr, obj1);
		obj1_addr += O4_SIBLING - O4_PARENT;
		SET_WORD(obj1_addr, child);
	}
}

void Processor::z_put_prop() {
	zword prop_addr;
	zword value;
	zbyte mask;

	if (zargs[0] == 0) {
		runtimeError(ERR_PUT_PROP_0);
		return;
	}

	// Property id is in bottom five or six bits
	mask = (h_version <= V3) ? 0x1f : 0x3f;

	// Load address of first property
	prop_addr = first_property(zargs[0]);

	// Scan down the property list
	for (;;) {
		LOW_BYTE(prop_addr, value);
		if ((value & mask) <= zargs[1])
			break;

		prop_addr = next_property(prop_addr);
	}

	// Exit if the property does not exist
	if ((value & mask) != zargs[1])
		runtimeError(ERR_NO_PROP);

	// Store the new property value (byte or word sized)
	prop_addr++;

	if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) {
		zbyte v = zargs[2];
		SET_BYTE(prop_addr, v);
	} else {
		zword v = zargs[2];
		SET_WORD(prop_addr, v);
	}
}

void Processor::z_remove_obj() {
	// If we are monitoring object movements display a short note
	if (_object_movement) {
		stream_mssg_on();
		print_string("@remove_obj ");
		print_object(zargs[0]);
		stream_mssg_off();
	}

	// Call unlink_object to do the job
	unlink_object(zargs[0]);
}

void Processor::z_set_attr() {
	zword obj_addr;
	zbyte value;

	if (_storyId == SHERLOCK)
		if (zargs[1] == 48)
			return;

	if (zargs[1] > ((h_version <= V3) ? 31 : 47))
		runtimeError(ERR_ILL_ATTR);

	// If we are monitoring attribute assignment display a short note
	if (_attribute_assignment) {
		stream_mssg_on();
		print_string("@set_attr ");
		print_object(zargs[0]);
		print_string(" ");
		print_num(zargs[1]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_SET_ATTR_0);
		return;
	}

	// Get attribute address
	obj_addr = object_address(zargs[0]) + zargs[1] / 8;

	// Load attribute byte
	LOW_BYTE(obj_addr, value);

	// Set attribute bit
	value |= 0x80 >> (zargs[1] & 7);

	// Store attribute byte
	SET_BYTE(obj_addr, value);
}

void Processor::z_test_attr() {
	zword obj_addr;
	zbyte value;

	if (zargs[1] > ((h_version <= V3) ? 31 : 47))
		runtimeError(ERR_ILL_ATTR);

	// If we are monitoring attribute testing display a short note
	if (_attribute_testing) {
		stream_mssg_on();
		print_string("@test_attr ");
		print_object(zargs[0]);
		print_string(" ");
		print_num(zargs[1]);
		stream_mssg_off();
	}

	if (zargs[0] == 0) {
		runtimeError(ERR_TEST_ATTR_0);
		branch(false);
		return;
	}

	// Get attribute address
	obj_addr = object_address(zargs[0]) + zargs[1] / 8;

	// Load attribute byte
	LOW_BYTE(obj_addr, value);

	// Test attribute
	branch(value & (0x80 >> (zargs[1] & 7)));
}

} // End of namespace Frotz
} // End of namespace Glk