/*
 * control.c
 *
 * Functions that alter the flow of control.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "options.h"
#include "ztypes.h"
#include "control.h"
#include "operand.h"
#include "memory.h"
#include "interpre.h"
#include "screen.h"
#include "screenio.h"
#include "osdepend.h"
#include "fileio.h"
#include "text.h"
#include "v6.h"
#include "v6ro.h"

#ifdef INFIX
#include "debug.h"
#endif

zbyte_t h_interpreter=6;
zbyte_t h_interpreter_version;
unsigned h_routines_offset;
zbyte_t h_default_bg_col;
zbyte_t h_default_fg_col;

unsigned *sp;
unsigned *fp;
unsigned *stack;
zbyte_t *pc;
unsigned frame_number;

extern int default_fg, default_bg;

static const char *const v1_lookup_table[3] =
{
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    " 0123456789.,!?_#'\"/\\<-:()"
};

static const char *const v2_lookup_table[3] =
{
    "abcdefghijklmnopqrstuvwxyz",
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    " \n0123456789.,!?_#'\"/\\-:()"
};

int interrupt_finished = 0;
int size_scaler = 0;
int story_shift = 0;

/*
 * check_arg_count
 *
 * Jump if argument is present.
 *
 */

void z_check_arg_count(unsigned int argc)
{
    conditional_jump(argc <= (*(fp + 1) & ARGS_MASK));

}/* check_arg_count */

/*
 * call
 *
 * Call a subroutine. Save PC and FP then load new PC and initialise stack based
 * local arguments.
 *
 */

void z_call(int argc, unsigned *argv, int type)
{
    unsigned arg, current_pc;
    int i = 1, args;

    /* Convert calls to 0 as returning FALSE */

    if (argv[0] == 0)
    {
        if (type == FUNCTION)
            store_operand(FALSE);
        return;
    }

    /* Save current PC, FP and argument count on stack */

    current_pc = pc-datap;

    /* Could cheat because the stack is really 32 bit, but this would screw up
       save files. Have to think about this. */
    *--sp = current_pc >> 9;
    *--sp = current_pc & 0x1ff;
    *--sp = fp - stack;
    *--sp = (argc - 1) | type;

    /* Create FP for new subroutine and load new PC */

    fp = sp - 1;
    pc = datap + (argv[0] << story_shift) + h_routines_offset;

    /* Read argument count and initialise local variables */

    args = read_code_byte();

    /* Need to remember number of locals for Quetzal */
    fp[1] |= args << LOCALS_SHIFT;

    while (--args >= 0)
    {
        arg = (h_type >= V5) ? 0 : read_code_word ();
        *--sp = (--argc > 0) ? argv[i++] : arg;
    }

    frame_number++;

}/* call */

/*
 * call_interrupt
 *
 * Same as z_call, but returns a value. This is rarely used and saves
 * the overhead of having z_call returning a value all the time
 */
int call_interrupt(unsigned addr)
{
    /* Calls to 0 return 0 */

    if (addr == 0)
    	return 0;

    /* Call the interpreter directly */

    z_call(1, &addr, ASYNC);

    interpret();
    interrupt_finished--;

    /* Return result of interrupt routine */

    return *sp++;

}/* call_interrupt */

/*
 * ret
 *
 * Return from subroutine. Restore FP and PC from stack.
 *
 */

void z_ret(unsigned value)
{
    unsigned int argc;

    /* Clean stack */

    sp = fp + 1;

    /* Restore argument count, FP and PC */

    argc = *sp++;
    fp = stack + *sp++;
    pc = datap + *sp++;
    pc += *sp++ << 9;
    frame_number--;

    /* Return the value (type == FUNC), push it onto the system stack
       (type == INTR) or throw it away (type == PROC) */

    if ((argc & TYPE_MASK) == FUNCTION)
    	store_operand(value);
    else if ((argc & TYPE_MASK) == ASYNC)
    {
        interrupt_finished++;
        *--sp=(zword_t) value;
    }

}/* ret */

/*
 * jump
 *
 * Unconditional jump. Jump is PC relative.
 *
 */

void z_jump(short offset)
{
    pc += offset - 2;

}/* jump */

void restart_header(void)
{
    /* Mustn't mess with bits 0 to 2 of the flags */
    unsigned their_flags=get_word_priv(H_FLAGS) & (SCRIPTING_FLAG | FIXED_FONT_FLAG | REFRESH_FLAG);
    unsigned our_flags=h_flags &~ (SCRIPTING_FLAG | FIXED_FONT_FLAG | REFRESH_FLAG);

    set_byte_priv(H_CONFIG, h_config);
    set_word_priv(H_FLAGS, their_flags | our_flags);

    if (h_type >= V4)
    {
        set_byte_priv(H_INTERPRETER, h_interpreter);
        set_byte_priv(H_INTERPRETER_VERSION, h_interpreter_version);
        set_byte_priv(H_SCREEN_ROWS, h_screen_rows);
        set_byte_priv(H_SCREEN_COLUMNS, h_screen_cols);
    }

    if (h_type >= V5)
    {
        set_word_priv(H_SCREEN_WIDTH, h_screen_width);
        set_word_priv(H_SCREEN_HEIGHT, h_screen_height);
        set_byte_priv(H_DEFAULT_BG_COL, h_default_bg_col);
        set_byte_priv(H_DEFAULT_FG_COL, h_default_fg_col);
        if (h_type == V6)
        {
            set_byte_priv(H_CHAR_WIDTH_V6, h_char_width);
            set_byte_priv(H_CHAR_HEIGHT_V6, h_char_height);
        }
        else
        {
            set_byte_priv(H_CHAR_WIDTH_V5, h_char_width);
            set_byte_priv(H_CHAR_HEIGHT_V5, h_char_height);
        }
    }

    set_word_priv(H_REVISION_NUMBER, 0x0100);
}

/*
 * restart
 *
 * Restart game by initialising environment and reloading start PC.
 *
 */

void z_restart(void)
{
    unsigned int i, preserved_flags;
    static int just_loaded=1;
    extern unsigned palette[];

    /* Stop sounds */
    {
        static unsigned argv[4] = { 0, 3 };
        z_sound_effect(2, argv);
    }

    /* Reset output buffer */

    flush_buffer(FALSE);

    /* Reset text control flags */

    buffering = ON;
    output_stream_1 = ON;
    output_stream_3 = OFF;
    scripting_disable = OFF;

    /* Randomise */

    srand ((unsigned int) time(NULL));

    /* Remember scripting state */

    preserved_flags = get_word_priv(H_FLAGS) & (SCRIPTING_FLAG | FIXED_FONT_FLAG);

    /* Reload dynamic area (don't bother if we've only just loaded the game!) */

    if (just_loaded)
    	just_loaded=0;
    else
    	read_game(datap, h_static_offset);

    if (h_type == V6)
    {

        for (i=0; i<=7; i++)
        {
            wind_prop[i][WPROP_Y]=wind_prop[i][WPROP_X]=1;
            wind_prop[i][WPROP_Y_HEIGHT]=wind_prop[i][WPROP_X_WIDTH]=0;
            wind_prop[i][WPROP_Y_CURSOR]=wind_prop[i][WPROP_X_CURSOR]=1;
            wind_prop[i][WPROP_L_MARGIN]=wind_prop[i][WPROP_R_MARGIN]=0;
            wind_prop[i][WPROP_INTERRUPT_ROUTINE]=0;
            wind_prop[i][WPROP_INTERRUPT_COUNT]=0;
            wind_prop[i][WPROP_TEXT_STYLE]=NORMAL;
            wind_prop[i][WPROP_COLOUR_DATA]=(default_bg << 8) | default_fg;
            colourmem[i][0] = palette[default_fg-2];
            colourmem[i][1] = palette[default_bg-2];
            wind_prop[i][WPROP_FONT_NUMBER]=1;
            wind_prop[i][WPROP_FONT_SIZE]=(V6_CHAR_H << 8) | V6_CHAR_W;
            wind_prop[i][WPROP_ATTRIBUTES]=WATTR_BUFFER;
            wind_prop[i][WPROP_LINE_COUNT]=0;
        }
        /* Window 0 fills screen */
        wind_prop[0][WPROP_Y_HEIGHT]=25*V6_CHAR_H;
        wind_prop[0][WPROP_X_WIDTH]=80*V6_CHAR_W;
        wind_prop[0][WPROP_ATTRIBUTES]|=WATTR_WRAPPING|WATTR_SCROLL|WATTR_SCRIPT;
        /* Window 1 width of screen, zero height */
        wind_prop[1][WPROP_X_WIDTH]=80*V6_CHAR_W;
    }

    game_wants_caret=1;

    /* Restart the screen */
    if (h_type != V6)
    {
        static unsigned cols[2] = {1,1};
    	z_set_colour(2, cols);
    	z_split_window(0);
    	set_attribute (NORMAL);
    	z_erase_window(-1);
    }

    /* Check the Unicode translation table */

    if (h_extension_table && get_word(h_extension_table) >= 3)
    {
        unsigned table = get_word(h_extension_table + 6);
        if (table)
        {
            int i;

            zscii_to_unicode_table_size = get_byte(table);
            if (zscii_to_unicode_table_size > MAX_UNICODE_TABLE_SIZE)
            	zscii_to_unicode_table_size = MAX_UNICODE_TABLE_SIZE;

            for (i=0; i<zscii_to_unicode_table_size; i++)
                zscii_to_unicode_table[i] = get_word(table + 1 + i*2);
        }
    }

    setup_mapping_tables();

    restart_screen ();

    /* Reset the interpreter state */

    restart_header();

    preserved_flags = (get_word_priv(H_FLAGS) &~ (SCRIPTING_FLAG|FIXED_FONT_FLAG)) |
                       preserved_flags;
    set_word_priv (H_FLAGS, preserved_flags);

    if (h_type==V6)
    {
        z_set_window(0);
    	set_attribute(NORMAL);
        z_erase_window(-1);
        z_mouse_window(1);
        for (i=3; i<=32; i++)
            v6ro_remove_menu(i);
    }

    /* Initialise status region */

    if (h_type < V4) {
        z_split_window(0);
        blank_status_line ();
    }

    /* Check fixed space bit */

    fixed_space_bit = (get_word_priv(H_FLAGS) & FIXED_FONT_FLAG)!=0;

    /* Initialise the character translation lookup tables */

    for (i = 0; i < 3; i++) {
        if (get_word_priv(H_ALTERNATE_ALPHABET_OFFSET)) {
            lookup_table[i] = (char *)(datap + get_word_priv(H_ALTERNATE_ALPHABET_OFFSET) + i * 26);
        } else {
            if (h_type == V1)
                lookup_table[i] = v1_lookup_table[i];
            else
                lookup_table[i] = v2_lookup_table[i];
        }
    }

    /* Load start PC, SP and FP */

    pc = datap;
    sp = stack + STACK_SIZE;
    fp = stack + (STACK_SIZE - 1);
    frame_number = 0;

    if (h_type==V6)
    {
        unsigned arglist[1];
        arglist[0]=get_word_priv(H_START_PC);
        z_call(1, arglist, PROCEDURE);
    }
    else
        pc += get_word_priv(H_START_PC);

    #ifdef INFIX
    infix_initialise();
    #endif

}/* restart */

/*
 * catch
 *
 * Return the number of the current stack frame for later use with throw.
 * Before V5 games this was a simple pop.
 *
 */

void z_catch()
{
    if (h_type >= V5)
        store_operand(frame_number);
    else
        sp++;

}/* z_catch */

/*
 * throw
 *
 * Remove one or more stack frames and return. Works like longjmp, see catch.
 *
 */

void z_throw(unsigned value, unsigned new_frame)
{
    if (new_frame <= frame_number)
    {
        while (frame_number > new_frame)
        {
            fp = stack + fp[2];
            frame_number--;
        }
    }
    else
    {
        /* Assume it's a frame pointer from an old restore */
        if (stack + new_frame < fp)
        {
            fatal_lookup("BadFram");
            return; // Allow tail-call + leaf function optimisation
        }
        fp = stack + new_frame;
    }

    z_ret(value);

}/* z_throw */
