/*
 * 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 h_globals_offset;

//struct zframe *fp;
unsigned * restrict stack;
//unsigned frame_number;

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;
int size_shift;
//int story_shift;

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

void z_check_arg_count(unsigned int argc)
{
    conditional_jump(argc <= fp->args);

}/* check_arg_count */

#if 0
/*
 * 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;
    struct zframe *ofp;
    int i = 1, locals;

    /* Convert calls to 0 as returning FALSE */

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

    /* Create new frame on stack */
    ofp = fp;
    sp -= ZFRAME_STACK;
    fp = (struct zframe *) sp;
    fp->ret_pc = pc;
    fp->ret_fp = ofp;
    fp->args = argc - 1;
    fp->type = type;

    frame_number++;

    /* Calculate new PC */

    pc = datap + (argv[0] << story_shift) + h_routines_offset;

    /* Read argument count and initialise local variables */

    locals = read_code_byte();

    /* Need to remember number of locals for Quetzal */
    fp->locals = locals;

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

}/* call */
#endif

/*
 * 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 */

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

void z_ret(unsigned value)
{
    unsigned int type;

    sp = (unsigned *)(fp+1);
    type = fp->type;
    pc = fp->ret_pc;
    fp = fp->ret_fp;
    frame_number--;

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

    switch (type)
    {
        case FUNCTION:
            store_operand(value);
            break;

        case ASYNC:
            interrupt_finished++;
            *--sp = (zword_t) value;
            break;
    }

}/* ret */
#endif

/*
 * 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 (hx_present(HX_FLAGS3))
    {
        set_word_priv(h_extension_table+HX_FLAGS3, hx_flags3);
    }

    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)
        {
            v6_update_char_size();
        }
        else
        {
            set_byte_priv(H_CHAR_WIDTH_V5, h_char_width);
            set_byte_priv(H_CHAR_HEIGHT_V5, h_char_height);
        }
        if (hx_present(HX_DEFAULT_TRUE_FG))
        {
            unsigned fg = os_colour_to_z_true_colour(true_default_fg);
            set_word_priv(h_extension_table + HX_DEFAULT_TRUE_FG, fg);
        }
        if (hx_present(HX_DEFAULT_TRUE_BG))
        {
            unsigned bg = os_colour_to_z_true_colour(true_default_bg);
            set_word_priv(h_extension_table + HX_DEFAULT_TRUE_BG, bg);
        }
    }

    set_word_priv(H_REVISION_NUMBER, 0x0101);
}

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

void z_restart(void)
{
    unsigned int i, preserved_flags;
    static int just_loaded=1;

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

    /* Reset output buffer */

    reset_buffer();

    /* Reset text control flags */

    wrapping = 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);

    /* Sort out the extension table */

    if (h_extension_table)
        h_extension_length = get_word_priv(h_extension_table+HX_LENGTH);
    if (hx_present(HX_FLAGS3))
        hx_flags3 = get_word_priv(h_extension_table+HX_FLAGS3);

    restart_sound();

    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;
            colourmem[i][0] = true_default_fg;
            colourmem[i][1] = true_default_bg;
            wind_prop[i][WPROP_FONT_NUMBER]=1;
            wind_prop[i][WPROP_FONT_SIZE]=v6ro_char_size(1, NORMAL);
            wind_prop[i][WPROP_ATTRIBUTES]=WATTR_BUFFER;
            wind_prop[i][WPROP_LINE_COUNT]=0;
            wind_prop[i][WPROP_TRUE_FG]=os_colour_to_z_true_colour(true_default_fg);
            wind_prop[i][WPROP_TRUE_BG]=os_colour_to_z_true_colour(true_default_bg);
            wind_prop[i][WPROP_COLOUR_DATA]=(os_colour_to_z_colour(true_default_bg)<<8) |
                                             os_colour_to_z_colour(true_default_fg);
        }
        /* Window 0 fills screen */
        wind_prop[0][WPROP_Y_HEIGHT]=h_screen_rows*V6_CHAR_H;
        wind_prop[0][WPROP_X_WIDTH]=h_screen_cols*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]=h_screen_cols*V6_CHAR_W;
    }

    game_wants_caret=1;

    /* Restart the screen */
    if (h_type != V6)
    {
        set_true_colours(-1, -1, -1);
    	set_attribute (NORMAL);
    	z_erase_window(-1);
    }

    /* Check the Unicode translation table */

    if (hx_present(HX_UNICODE_TABLE))
    {
        unsigned table = get_word(h_extension_table + HX_UNICODE_TABLE);
        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_game_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 (h_type >= V5 && 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 = (struct zframe *) sp;
    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(void)
{
    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)
{
    while (frame_number > new_frame)
    {
        fp = fp->ret_fp;
        frame_number--;
    }

    if (frame_number == new_frame)
    {
        z_ret(value);
    }
    else
    {
        fatal_lookup("BadFram");
    }

}/* z_throw */

void dump_backtrace(const char *filename)
{
    FILE *f;
    struct zframe *ifp;
    unsigned *isp;
    zbyte_t *ipc;
    int i, n;

    f = fopen(filename, "w");
    if (!f) return;

    fprintf(f, "Stack space free = %04X\n", sp-stack);
    for (isp = sp, ifp = fp, ipc = pc, i = frame_number;
         i >= (h_type == V6);
         i--, ipc=ifp->ret_pc, ifp=ifp->ret_fp, isp=(unsigned *)(ifp+1))
    {
        int locals = i == 0 ? 0 : ifp->locals;
        fprintf(f, "\nPC = %05X\n", ipc-datap);
        n = (unsigned *) ifp - isp - locals;
        if (n > 0)
        {
            fprintf(f, "Stack: %04X\n", *isp++);
            while (isp < (unsigned *) ifp - locals)
                fprintf(f, "       %04X\n", *isp++);
        }
        if (locals)
        {
            fprintf(f, "Locals:");
            for (n = 1; n <= locals; n++)
            {
                fprintf(f, "%s%04X", n == ifp->args+1 ? " | " : " ",
                                     ((unsigned *)ifp)[-n]);
            }
            if (n == ifp->args+1) fprintf(f, " |");
            fputc('\n', f);
        }
    }

    fprintf(f, "\nGlobals:\n");
    for (i=0; i<240; i++)
    {
        n = h_globals_offset + i*2;
        fprintf(f, "%04X%c", get_word_priv(n), (i&15)==15?'\n':' ');
    }

    fclose(f);
}
