/*
 * process.c
 *
 * Interpreter loop and program control
 *
 */

#include "Frotz/frotz.h"

#define MAX_CALL_LEVEL 3

zword zargs[8];
int zargc;

static zword saved_zargs[MAX_CALL_LEVEL][8];
static int saved_zargc[MAX_CALL_LEVEL];
int z_finished;

static call_level = 0;

void __extended__ (void);
void __illegal__ (void);

void (*op0_opcodes[0x10]) (void) = {
    z_rtrue,
    z_rfalse,
    z_print,
    z_print_ret,
    z_nop,
    z_save,
    z_restore,
    z_restart,
    z_ret_popped,
    z_catch,
    z_quit,
    z_new_line,
    z_show_status,
    z_verify,
    __extended__,
    z_piracy
};

void (*op1_opcodes[0x10]) (void) = {
    z_jz,
    z_get_sibling,
    z_get_child,
    z_get_parent,
    z_get_prop_len,
    z_inc,
    z_dec,
    z_print_addr,
    z_call_s,
    z_remove_obj,
    z_print_obj,
    z_ret,
    z_jump,
    z_print_paddr,
    z_load,
    z_call_n
};

void (*var_opcodes[0x40]) (void) = {
    __illegal__,
    z_je,
    z_jl,
    z_jg,
    z_dec_chk,
    z_inc_chk,
    z_jin,
    z_test,
    z_or,
    z_and,
    z_test_attr,
    z_set_attr,
    z_clear_attr,
    z_store,
    z_insert_obj,
    z_loadw,
    z_loadb,
    z_get_prop,
    z_get_prop_addr,
    z_get_next_prop,
    z_add,
    z_sub,
    z_mul,
    z_div,
    z_mod,
    z_call_s,
    z_call_n,
    z_set_colour,
    z_throw,
    __illegal__,
    __illegal__,
    __illegal__,
    z_call_s,
    z_storew,
    z_storeb,
    z_put_prop,
    z_read,
    z_print_char,
    z_print_num,
    z_random,
    z_push,
    z_pull,
    z_split_window,
    z_set_window,
    z_call_s,
    z_erase_window,
    z_erase_line,
    z_set_cursor,
    z_get_cursor,
    z_set_text_style,
    z_buffer_mode,
    z_output_stream,
    z_input_stream,
    z_sound_effect,
    z_read_char,
    z_scan_table,
    z_not,
    z_call_n,
    z_call_n,
    z_tokenise,
    z_encode_text,
    z_copy_table,
    z_print_table,
    z_check_arg_count
};

void (*ext_opcodes[0x1d]) (void) = {
    z_save,
    z_restore,
    z_log_shift,
    z_art_shift,
    z_set_font,
    z_draw_picture,
    z_picture_data,
    z_erase_picture,
    z_set_margins,
    z_save_undo,
    z_restore_undo,
    __illegal__,
    __illegal__,
    __illegal__,
    __illegal__,
    __illegal__,
    z_move_window,
    z_window_size,
    z_window_style,
    z_get_wind_prop,
    z_scroll_window,
    z_pop_stack,
    z_read_mouse,
    z_mouse_window,
    z_push_stack,
    z_put_wind_prop,
    z_print_form,
    z_make_menu,
    z_picture_table
};

/*
 * save_operands
 *
 * Save the values of the operands and the operand count.
 *
 */

static void save_operands (void)
{
    int i;

    if (++call_level >= MAX_CALL_LEVEL)
	call_level -= MAX_CALL_LEVEL;

    for (i = 0; i < 8; i++)
	saved_zargs[call_level][i] = zargs[i];

    saved_zargc[call_level] = zargc;

}/* save_operands */

/*
 * restore_operands
 *
 * Restore the values of the operands and the operand count.
 *
 */

static void restore_operands (void)
{
    int i;

    for (i = 0; i < 8; i++)
	zargs[i] = saved_zargs[call_level][i];

    zargc = saved_zargc[call_level];

    if (--call_level < 0)
	call_level += MAX_CALL_LEVEL;

}/* restore_operands */

/*
 * call
 *
 * Call a subroutine. Save PC and FP then load new PC and initialise
 * new stack frame. Note that the caller may legally provide less or
 * more arguments than the function actually has. The call type "ct"
 * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call).
 *
 */

void call (zword routine, int argc, zword *args, int ct)
{
    long pc;
    zword value;
    zbyte count;
    int i;

    if (sp - stack < 4 + argc)
	runtime_error ("Stack overflow");

    GET_PC (pc)

    *--sp = ZWORD (pc >> 9);		/* for historical reasons */
    *--sp = ZWORD (pc & 0x1ff);		/* Frotz keeps its stack  */
    *--sp = ZWORD (fp - stack - 1);	/* format compatible with */
    *--sp = ZWORD (argc | (ct << 8));	/* Mark Howell's Zip      */

    fp = sp;

    /* Calculate byte address of routine */

    if (h_version <= V3)
	pc = (long) routine << 1;
    else if (h_version <= V5)
	pc = (long) routine << 2;
    else if (h_version <= V7)
	pc = ((long) routine << 2) + ((long) h_functions_offset << 3);
    else /* h_version == V8 */
	pc = (long) routine << 3;

    if (pc >= story_size)
	runtime_error ("Call to illegal address");

    SET_PC (pc)

    /* Initialise local variables */

    CODE_BYTE (count)

    if (count > 15)
	runtime_error ("Call to non-routine");

    value = 0;

    for (i = 0; i < count; i++) {

	if (h_version <= V4)		/* V1 to V4 games provide default */
	    CODE_WORD (value)		/* values for all local variables */

	*--sp = ZWORD ((argc-- > 0) ? args[i] : value);

    }

}/* call */

/*
 * ret
 *
 * Return from the current subroutine and restore the previous stack
 * frame. The result may be stored (0), thrown away (1) or pushed on
 * the stack (2). In the latter case a direct call has been z_finished
 * and we must restore the operands and exit the interpreter loop.
 *
 */

void ret (zword value)
{
    long pc;
    int ct;

    sp = fp;

    ct = *sp++ >> 8;
    fp = stack + 1 + *sp++;
    pc = *sp++;
    pc = ((long) *sp++ << 9) | pc;

    SET_PC (pc)

    if (ct == 0)
	store (value);

    if (ct != 2)
	return;

    restore_operands ();

    z_finished++;

    *--sp = value;

}/* ret */

/*
 * branch
 *
 * Take a jump after an instruction based on the flag, either true or
 * false. The branch can be short or long; it is encoded in one or two
 * bytes respectively. When bit 7 of the first byte is set, the jump
 * takes place if the flag is true; otherwise it is taken if the flag
 * is false. When bit 6 of the first byte is set, the branch is short;
 * otherwise it is long. The offset occupies the bottom 6 bits of the
 * first byte plus all the bits in the second byte for long branches.
 * Uniquely, an offset of 0 means return false, and an offset of 1 is
 * return true.
 *
 */

void branch (int flag)
{
    long pc;
    zword offset;
    zbyte specifier;
    zbyte off1;
    zbyte off2;

    CODE_BYTE (specifier)

    off1 = ZBYTE (specifier & 0x3f);

    if (!flag)
	specifier ^= 0x80;

    if (!(specifier & 0x40)) {		/* it's a long branch */

	if (off1 & 0x20)		/* propagate sign bit */
	    off1 |= 0xc0;

	CODE_BYTE (off2)

	offset = (off1 << 8) | off2;

    } else offset = off1;		/* it's a short branch */

    if (specifier & 0x80)

	if (offset > 1) {		/* normal branch */

	    GET_PC (pc)
	    pc += (short) offset - 2;
	    SET_PC (pc)

	} else ret (offset);		/* special case, return 0 or 1 */

}/* branch */

/*
 * store
 *
 * Store an operand, either as a variable or pushed on the stack.
 *
 */

void store (zword value)
{
    zbyte variable;

    CODE_BYTE (variable)

    if (variable == 0)
	*--sp = value;
    else if (variable < 16)
	*(fp - variable) = value;
    else {
	zword addr = h_globals + 2 * (variable - 16);
	SET_WORD (addr, value)
    }

}/* store */

/*
 * load_operand
 *
 * Load an operand, either a variable or a constant.
 *
 */

static void load_operand (zbyte type)
{
    zword value;

    if (type & 2) { 			/* variable */

	zbyte variable;

	CODE_BYTE (variable)

	if (variable == 0)
	    value = *sp++;
	else if (variable < 16)
	    value = *(fp - variable);
	else {
	    zword addr = h_globals + 2 * (variable - 16);
	    LOW_WORD (addr, value)
	}

    } else if (type & 1) { 		/* small constant */

	zbyte bvalue;

	CODE_BYTE (bvalue)
	value = ZWORD (bvalue);

    } else CODE_WORD (value) 		/* large constant */

    zargs[zargc++] = value;

}/* load_operand */

/*
 * load_all_operands
 *
 * Given the operand specifier byte, load all (up to four) operands
 * for a VAR or EXT opcode.
 *
 */

static void load_all_operands (zbyte specifier)
{
    int i;

    for (i = 6; i >= 0; i -= 2) {

	zbyte type = ZBYTE ((specifier >> i) & 0x03);

	if (type == 3)
	    break;

	load_operand (type);

    }

}/* load_all_operands */

/*
 * interpret
 *
 * Z-code interpreter main loop
 *
 */

void interpret (void)
{

    do {

	zbyte opcode;

	CODE_BYTE (opcode)

	zargc = 0;

	if (opcode < 0x80) {			/* 2OP opcodes */

	    load_operand (ZBYTE ((opcode & 0x40) ? 2 : 1));
	    load_operand (ZBYTE ((opcode & 0x20) ? 2 : 1));

	    var_opcodes[opcode & 0x1f] ();

	} else if (opcode < 0xb0) {		/* 1OP opcodes */

	    load_operand (ZBYTE (opcode >> 4));

	    op1_opcodes[opcode & 0x0f] ();

	} else if (opcode < 0xc0) {		/* 0OP opcodes */

	    op0_opcodes[opcode - 0xb0] ();

	} else {				/* VAR opcodes */

	    zbyte specifier1;
	    zbyte specifier2;

	    if (opcode == 0xec || opcode == 0xfa) {	/* opcodes 0xec */
		CODE_BYTE (specifier1)                  /* and 0xfa are */
		CODE_BYTE (specifier2)                  /* call opcodes */
		load_all_operands (specifier1);		/* with up to 8 */
		load_all_operands (specifier2);         /* arguments    */
	    } else {
		CODE_BYTE (specifier1)
		load_all_operands (specifier1);
	    }

	    var_opcodes[opcode - 0xc0] ();

	}

    } while (!z_finished);

}/* interpret */

/*
 * direct_call
 *
 * Call the interpreter loop directly. This is necessary when
 *
 * - a sound effect has been z_finished
 * - a read instruction has timed out
 * - a newline countdown has hit zero
 *
 * The interpreter returns the result value on the stack.
 *
 */

int direct_call (zword addr)
{

    if (addr) {

	save_operands ();

	call (addr, 0, 0, 2);

	interpret ();

	z_finished--;

	return (short) *sp++;

    } else return 0;

}/* direct_call */

/*
 * __extended__
 *
 * Load and execute an extended opcode.
 *
 */

static void __extended__ (void)
{
    zbyte opcode;
    zbyte specifier;

    CODE_BYTE (opcode)
    CODE_BYTE (specifier)

    load_all_operands (specifier);

    if (opcode < 0x1d)			/* extended opcodes from 0x1d on */
	ext_opcodes[opcode] ();		/* are reserved for future spec' */

}/* __extended__ */

/*
 * __illegal__
 *
 * Exit game because an unknown opcode has been used.
 *
 */

static void __illegal__ (void)
{

    runtime_error ("Illegal opcode");

}/* __illegal__ */

/*
 * z_catch, store the current stack frame for later use with z_throw.
 *
 *	no zargs used
 *
 */

void z_catch (void)
{

    store (ZWORD (fp - stack));

}/* z_catch */

/*
 * z_throw, go back to the given stack frame and return the given value.
 *
 *	zargs[0] = value to return
 *	zargs[1] = stack frame
 *
 */

void z_throw (void)
{

    if (zargs[1] >= STACK_SIZE)
	runtime_error ("Bad stack frame");

    fp = stack + zargs[1];

    ret (zargs[0]);

}/* z_throw */

/*
 * z_call_n, call a subroutine and discard its result.
 *
 * 	zargs[0] = packed address of subroutine
 *	zargs[1] = first argument (optional)
 *	...
 *	zargs[7] = seventh argument (optional)
 *
 */

void z_call_n (void)
{

    if (zargs[0])
	call (zargs[0], zargc - 1, zargs + 1, 1);

}/* z_call_n */

/*
 * z_call_s, call a subroutine and store its result.
 *
 * 	zargs[0] = packed address of subroutine
 *	zargs[1] = first argument (optional)
 *	...
 *	zargs[7] = seventh argument (optional)
 *
 */

void z_call_s (void)
{

    if (zargs[0])
	call (zargs[0], zargc - 1, zargs + 1, 0);
    else
	store (0);

}/* z_call_s */

/*
 * z_check_arg_count, branch if subroutine was called with >= n arg's.
 *
 * 	zargs[0] = number of arguments
 *
 */

void z_check_arg_count (void)
{

    if (fp == stack + STACK_SIZE)
	branch (zargs[0] == 0);
    else
	branch (zargs[0] <= (*fp & 0xff));

}/* z_check_arg_count */

/*
 * z_jump, jump unconditionally to the given address.
 *
 *	zargs[0] = PC relative address
 *
 */

void z_jump (void)
{
    long pc;

    GET_PC (pc)

    pc += (short) zargs[0] - 2;

    if (pc >= story_size)
	runtime_error ("Jump to illegal address");

    SET_PC (pc)

}/* z_jump */

/*
 * z_nop, no operation.
 *
 *	no zargs used
 *
 */

void z_nop (void)
{

    /* Do nothing */

}/* z_nop */

/*
 * z_quit, stop game and exit interpreter.
 *
 *	no zargs used
 *
 */

void z_quit (void)
{

    z_finished = MAX_CALL_LEVEL + 1;

}/* z_quit */

/*
 * z_ret, return from a subroutine with the given value.
 *
 *	zargs[0] = value to return
 *
 */

void z_ret (void)
{

    ret (zargs[0]);

}/* z_ret */

/*
 * z_ret_popped, return from a subroutine with a value popped off the stack.
 *
 *	no zargs used
 *
 */

void z_ret_popped (void)
{

    ret (*sp++);

}/* z_ret_popped */

/*
 * z_rfalse, return from a subroutine with false (0).
 *
 * 	no zargs used
 *
 */

void z_rfalse (void)
{

    ret (0);

}/* z_rfalse */

/*
 * z_rtrue, return from a subroutine with true (1).
 *
 * 	no zargs used
 *
 */

void z_rtrue (void)
{

    ret (1);

}/* z_rtrue */
