// zeal - A portable Glk-based Z-code interpreter
// Copyright (C) 2000 Jeremy Condit <jcondit@eecs.harvard.edu>
// 
// 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// =======================================================================
//  inst.cc:
//
//  this module contains all of the instruction handling routines.
//  at the end are the tables used to decode instructions.
//
//  something tells me that the instruction routines should be machine
//  members as well, but the thought of bloating the machine class even
//  more just irks me.  so they're separate, for now.
// =======================================================================

#include "zeal.h"
#include "error.h"
#include "inst.h"
#include "machine.h"
#include "iface.h"
#include "bfish.h"
#include "dict.h"
#include "obj.h"
#include "prop.h"
#include "osdep.h"
#include "random.h"
#include "stdlite.h"

extern machine* m;
extern iface* io;
extern osdep* os;

extern game_state* initial;

game_state* undo = NULL;

// =======================================================================   
//  error handling
// ======================================================================= 

// unimplemented
//
// generic placeholder in tables for instructions that are undefined or
// just plain unimplemented.

void
unimplemented(inst_args* args)
{
    io->put_error("Error: Unimplemented instruction (op 0x%x, ext 0x%x).",
                   args->opcode, args->ext);
    glk_exit();
}

// =======================================================================
//  instructions (2 operands)
// =======================================================================

void
je(inst_args* args)
{
    ASSERT(args->argc >= 1);

    uword a = args->argv[0];
    bool success = false;

    for (int i = 1; i < args->argc; i++) {
        if (a == args->argv[i]) {
            success = true;
        }
    }

    m->branch(success);
}

void
jl(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->branch(((word) args->argv[0] < (word) args->argv[1]));
}

void
jg(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->branch(((word) args->argv[0] > (word) args->argv[1]));
}

void
dec_chk(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    ubyte var = (ubyte) args->argv[0];
    word value = (word) m->read_var(var) - 1;
    m->write_var(var, (uword) value);

    m->branch((value < (word) args->argv[1]));
}

void
inc_chk(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    ubyte var = (ubyte) args->argv[0];
    word value = (word) m->read_var(var) + 1;
    m->write_var(var, (uword) value);

    m->branch((value > (word) args->argv[1]));
}

void
jin(inst_args* args)
{
    ASSERT(args->argc == 2);

    if (args->argv[0] == 0) {
        WARN("Tried to examine child list of object 0.");
        m->branch(false);
    } else {
        m->branch((object(args->argv[0]).get_parent().get_number() ==
                                                               args->argv[1]));
    }
}

void
test(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->branch(((args->argv[0] & args->argv[1]) == args->argv[1]));
}

void
or_(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value(args->argv[0] | args->argv[1]);
}

void
and_(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value(args->argv[0] & args->argv[1]);
}

void
test_attr(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    bool cond = false;

    if (args->argv[0] == 0) {
        WARN("Tried to test attribute of object 0.");
    } else {
        cond = object(args->argv[0]).get_attr((ubyte) args->argv[1]);
    }

    m->branch(cond);
}

void
set_attr(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    if (args->argv[0] == 0) {
        WARN("Tried to set attribute of object 0.");
    } else {
        object(args->argv[0]).set_attr((ubyte) args->argv[1], true);
    }
}

void
clear_attr(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    if (args->argv[0] == 0) {
        WARN("Tried to clear attribute of object 0.");
    } else {
        object(args->argv[0]).set_attr((ubyte) args->argv[1], false);
    }
}

void
store(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    m->write_var((ubyte) args->argv[0], args->argv[1]);
}

void
insert_obj(inst_args* args)
{
    ASSERT(args->argc == 2);

    if (args->argv[0] == 0) {
        WARN("Tried to insert object 0 into child list.");
    } else if (args->argv[1] == 0) {
        WARN("Tried to insert into child list of object 0.");
    } else {
        object dest(args->argv[1]);
        object(args->argv[0]).insert(dest);
    }
}

void
loadw(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value(m->read_word((address) args->argv[0] +
                                (2 * (address) args->argv[1])));
}

void
loadb(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value(m->read_byte((address) args->argv[0] +
                                (address) args->argv[1]));
}

void
get_prop(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    uword value = 0;

    if (args->argv[0] == 0) {
        WARN("Tried to get property of object 0.");
    } else {
        property prop = object(args->argv[0])
                           .get_property((ubyte) args->argv[1]);
        if (prop.valid()) {
            value = prop.read_word();
        } else {
            ASSERT(args->argv[1] > 0);
            value = m->read_word(m->read_word(HEADER_OBJECT_BASE) +
                                 (2 * ((address) args->argv[1] - 1)));
        }
    }

    m->store_value(value);
}

void
get_prop_addr(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    address addr = 0;

    if (args->argv[0] == 0) {
        WARN("Tried to get address of object 0 property.");
    } else {
        property prop = object(args->argv[0])
                           .get_property((ubyte) args->argv[1]);
        if (prop.valid()) {
            addr = prop.get_address();
        }
    }

    m->store_value(addr);
}

void
get_next_prop(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    property prop;

    if (args->argv[0] == 0) {
        WARN("Tried to get next property for object 0.");
    } else if (args->argv[1] == 0) {
        prop = object(args->argv[0]).get_first_property();
    } else {
        prop = object(args->argv[0]).get_property((ubyte) args->argv[1])
                                    .get_next();
    }

    m->store_value(prop.valid() ? prop.get_number() : 0);
}

void
add(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value((word) args->argv[0] + (word) args->argv[1]);
}

void
sub(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value((word) args->argv[0] - (word) args->argv[1]);
}


void
mul(inst_args* args)
{
    ASSERT(args->argc == 2);

    m->store_value((word) args->argv[0] * (word) args->argv[1]);
}

void
div(inst_args* args)
{
    ASSERT(args->argc == 2);

    if (args->argv[1] == 0) {
        FATAL("Divide by zero.");
    }

    m->store_value((word) args->argv[0] / (word) args->argv[1]);
}

void
mod(inst_args* args)
{
    ASSERT(args->argc == 2);

    if (args->argv[1] == 0) {
        FATAL("Mod by zero.");
    }

    m->store_value((word) args->argv[0] % (word) args->argv[1]);
}

void
call_2s(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(m->version() >= 4);

    m->call_routine(args->argv[0], 1, args->argv + 1, true);
}

void
call_2n(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(m->version() >= 5);

    m->call_routine(args->argv[0], 1, args->argv + 1, false);
}

void
set_colour(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(m->version() >= 5);

    // TODO: implement color
}

void
throw_(inst_args* args)
{
    ASSERT(args->argc == 2);
    ASSERT(m->version() >= 5);

    // TODO: implement throw
    FATAL("Throw not implemented.");
}

// =======================================================================
//  instructions (1 operand)
// =======================================================================

void
jz(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->branch((args->argv[0] == 0));
}

void
get_sibling(inst_args* args)
{
    ASSERT(args->argc == 1);

    uword sib;
    if (args->argv[0] == 0) {
        WARN("Tried to get sibling of object 0.");
        sib = 0;
    } else {
        sib = object(args->argv[0]).get_sibling().get_number();
    }

    m->store_value(sib);
    m->branch((sib != 0));
}

void
get_child(inst_args* args)
{
    ASSERT(args->argc == 1);

    uword child;
    if (args->argv[0] == 0) {
        WARN("Tried to get child of object 0.");
        child = 0;
    } else {
        child = object(args->argv[0]).get_child().get_number();
    }

    m->store_value(child);
    m->branch((child != 0));
}

void
get_parent(inst_args* args)
{
    ASSERT(args->argc == 1);

    uword parent;
    if (args->argv[0] == 0) {
        WARN("Tried to get parent of object 0.");
        parent = 0;
    } else {
        parent = object(args->argv[0]).get_parent().get_number();
    }

    m->store_value(parent);
}

void
get_prop_len(inst_args* args)
{
    ASSERT(args->argc == 1);

    ASSERT(args->argv[0] > 0);
    m->store_value(property::from_addr(args->argv[0]).get_size());
}

void
inc(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    ubyte var = (ubyte) args->argv[0];

    m->write_var(var, (word) m->read_var(var) + 1);
}

void
dec(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    ubyte var = (ubyte) args->argv[0];

    m->write_var(var, (word) m->read_var(var) - 1);
}

void
print_addr(inst_args* args)
{
    ASSERT(args->argc == 1);

    bfish_addr((address) (args->argv[0])).print_string(io);
}

void
call_1s(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(m->version() >= 4);

    m->call_routine(args->argv[0], 0, NULL, true);
}

void
remove_obj(inst_args* args)
{
    ASSERT(args->argc == 1);

    if (args->argv[0] == 0) {
        WARN("Tried to remove object 0.");
    } else {
        object(args->argv[0]).remove();
    }
}

void
print_obj(inst_args* args)
{
    ASSERT(args->argc == 1);

    bfish_addr((object(args->argv[0])).get_name()).print_string(io);
}

void
ret(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->return_value(args->argv[0]);
}

void
jump(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->jump((word) args->argv[0] - 2);
}

void
print_paddr(inst_args* args)
{
    ASSERT(args->argc == 1);

    address addr = m->packed_to_byte(args->argv[0], false);
    bfish_addr(addr).print_string(io);
}

void
load(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    m->store_value(m->read_var((ubyte) args->argv[0]));
}

void
call_1n(inst_args* args)
{
    ASSERT(args->argc == 1);

    if (m->version() <= 4) {
        m->store_value(~(args->argv[0]));
    } else {
        m->call_routine(args->argv[0], 0, NULL, false);
    }
}

// =======================================================================
//  instructions (0 operands)
// =======================================================================

void
rtrue(inst_args* args)
{
    ASSERT(args->argc == 0);
    m->return_value(1);
}

void
rfalse(inst_args* args)
{
    ASSERT(args->argc == 0);
    m->return_value(0);
}

void
print(inst_args* args)
{
    ASSERT(args->argc == 0);
    
    bfish_pc().print_string(io);
}

void
print_ret(inst_args* args)
{
    ASSERT(args->argc == 0);

    bfish_pc().print_string(io);
    io->put_char('\r');

    m->return_value(1);
}

void
nop(inst_args* args)
{
    ASSERT(args->argc == 0);
}

void
save(inst_args* args)
{
    if (m->version() <= 4) {
        ASSERT(args->argc == 0);
    } else {
        ASSERT(args->argc <= 3);
    }

    bool success = false;

    frefid_t f = glk_fileref_create_by_prompt(fileusage_SavedGame |
                                              fileusage_BinaryMode,
                                              filemode_Write, 0);

    if (f != NULL) {
        strid_t s = glk_stream_open_file(f, filemode_Write, 0);

        if (s != NULL) {
            success = m->save_state(s, initial);
            glk_stream_close(s, NULL);
        }

        glk_fileref_destroy(f);
    }

    if (m->version() <= 3) {
        m->branch(success);
    } else {
        m->store_value(success ? 1 : 0);
    }
}

void
restore(inst_args* args)
{
    if (m->version() <= 4) {
        ASSERT(args->argc == 0);
    } else {
        ASSERT(args->argc <= 3);
    }

    bool success = false;

    frefid_t f = glk_fileref_create_by_prompt(fileusage_SavedGame |
                                              fileusage_BinaryMode,
                                              filemode_Read, 0);

    if (f != NULL) {
        strid_t s = glk_stream_open_file(f, filemode_Read, 0);

        if (s != NULL) {
            bool script_bit = m->read_bit(HEADER_FLAGS2 + 1, 0);
            bool font_bit = m->read_bit(HEADER_FLAGS2 + 1, 1);

            success = m->load_state(s, initial);

            m->write_bit(HEADER_FLAGS2 + 1, 0, script_bit);
            m->write_bit(HEADER_FLAGS2 + 1, 1, font_bit);

            glk_stream_close(s, NULL);
        }

        glk_fileref_destroy(f);
    }

    if (m->version() <= 3) {
        m->branch(success);
    } else {
        m->store_value(success ? 2 : 0);
    }
}

void
restart(inst_args* args)
{
    ASSERT(args->argc == 0);

    bool script_bit = m->read_bit(HEADER_FLAGS2 + 1, 0);
    bool font_bit = m->read_bit(HEADER_FLAGS2 + 1, 1);

    game_state* cur = m;
    *cur = *initial;

    m->set_header();

    m->write_bit(HEADER_FLAGS2 + 1, 0, script_bit);
    m->write_bit(HEADER_FLAGS2 + 1, 1, font_bit);
}

void
ret_popped(inst_args* args)
{
    ASSERT(args->argc == 0);
    m->return_value(m->read_var(0));
}

void
pop(inst_args* args)
{
    ASSERT(args->argc == 0);

    if (m->version() <= 4) {
        m->read_var(0);
    } else {
        FATAL("Catch not implemented.");
    }
}

void
quit(inst_args* args)
{
    ASSERT(args->argc == 0);
    m->halt();
}

void
new_line(inst_args* args)
{
    ASSERT(args->argc == 0);

    io->put_char(13);
}

void
show_status(inst_args* args)
{
    ASSERT(args->argc == 0);

    if (m->version() <= 3) {
        io->show_status();
    }
}

void
verify(inst_args* args)
{
    ASSERT(args->argc == 0);
    ASSERT(m->version() >= 3);

    m->branch(m->verify());
}

void
piracy(inst_args* args)
{
    ASSERT(args->argc == 0);
    ASSERT(m->version() >= 5);

    m->branch(true);
}

// =======================================================================
//  instructions (variable operands)
// =======================================================================

void
call_vs(inst_args* args)
{
    ASSERT(args->argc >= 1 && args->argc <= 4);

    m->call_routine(args->argv[0], args->argc - 1, args->argv + 1, true);
}

void
storew(inst_args* args)
{
    ASSERT(args->argc == 3);

    m->write_word((address) args->argv[0] + (2 * (address) args->argv[1]),
                  args->argv[2]);
}

void
storeb(inst_args* args)
{
    ASSERT(args->argc == 3);
    ASSERT(args->argv[2] <= MAX_UBYTE);

    m->write_byte((address) args->argv[0] + (address) args->argv[1],
                  (ubyte) args->argv[2]);
}

void
put_prop(inst_args* args)
{
    ASSERT(args->argc == 3);
    ASSERT(args->argv[1] <= MAX_UBYTE);

    object(args->argv[0]).get_property((ubyte) args->argv[1])
                         .write_word(args->argv[2]);
}

// this class completes the sread routine when input actually arrives
class line_continue : public input_continue {
    public:
        virtual void execute(uword len);
        virtual void cancel();

        address text;
        address parse;
        address start;
        ubyte skip;
};

void
line_continue::execute(uword len)
{
    if (m->version() <= 4) {
        m->write_byte(start + len, 0);
    } else {
        m->write_byte(text + 1, skip + len);
    }

    // A little hack that some folks I know requested...
    // if (strcmp((char*) &memory[start], "nfrm") == 0) system("nfrm");

    if (parse != 0) {
        dictionary().lex(text, parse, false);
    }

    if (m->version() >= 5) {
        m->store_value(10);
    }

    delete this;
}

void
line_continue::cancel()
{
    if (m->version() <= 4) {
        m->write_byte(start, 0);
    } else {
        m->write_byte(text + 1, skip);
    }

    if (m->version() >= 5) {
        m->store_value(0);
    }

    delete this;
}

void
sread(inst_args* args)
{
    if (m->version() <= 3) {
        ASSERT(args->argc == 2);
    } else {
        ASSERT(args->argc >= 2 && args->argc <= 4);
    }

    // TODO: time/routine

    address text = args->argv[0];
    address parse = args->argv[1];

    ubyte max = m->read_byte(text);
    if (max == 0) {
        FATAL("Text buffer too small!");
    }

    ubyte skip;
    address start;

    if (m->version() <= 4) {
        skip = 0;
        max -= 1;
        start = text + 1;
    } else {
        skip = m->read_byte(text + 1);
        max -= skip; // TODO: does max include skipped chars?
        start = text + skip + 2;
    }

    line_continue* cont = new line_continue;
    MEMCHECK(cont);
    cont->text = text;
    cont->parse = parse;
    cont->start = start;
    cont->skip = skip;

    if (args->argc == 4) {
        timer_info timer;

        timer.interval = args->argv[2];
        timer.addr = args->argv[3];

        io->read_zscii_line(start, max, &timer, cont);
    } else {
        io->read_zscii_line(start, max, NULL, cont);
    }
}

void
print_char(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    io->put_char((ubyte) args->argv[0]);
}

void
print_num(inst_args* args)
{
    ASSERT(args->argc == 1);

    char s[20];
    snprintf_lite(s, 10, "%d", (word) args->argv[0]);

    for (char* cur = s; *cur != 0; cur++) {
        io->put_char(*cur);
    }
}

void
random(inst_args* args)
{
    ASSERT(args->argc == 1);

    int n = 0;

    if (args->argv[0] > 0) {
        n = (int)(genrand() * args->argv[0]) + 1;
    } else if (args->argv[0] == 0) {
        sgenrand(os->get_seed());
    } else {
        sgenrand(-1 * args->argv[0]);
    }

    m->store_value(n);
}

void
push(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->write_var(0, args->argv[0]);
}

void
pull(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(args->argv[0] <= MAX_UBYTE);

    m->write_var((ubyte) args->argv[0], m->read_var(0));
    // TODO: does v6 style pull appear in v7 and v8?
}

void
split_window(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(m->version() >= 3);

    uint lines = (uint) args->argv[0];

    io->split_window(lines);

    if (lines > 0 && m->version() == 3) {
        io->erase_window(iface::UPPER);
    }
}

void
set_window(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(m->version() >= 3);

    switch (args->argv[0]) {
        case 0:
            io->set_window(iface::LOWER);
            break;
        case 1:
            io->set_window(iface::UPPER);
            break;
        default:
            FATAL("Invalid window specified.");
            break;
    }
}

void
call_vs2(inst_args* args)
{
    ASSERT(args->argc >= 1 && args->argc <= 8);
    ASSERT(m->version() >= 4);

    m->call_routine(args->argv[0], args->argc - 1, args->argv + 1, true);
}

void
erase_window(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(m->version() >= 4);

    switch ((word)args->argv[0]) {
        case -2:
            io->erase_window(iface::UPPER);
            io->erase_window(iface::LOWER);
            break;
        case -1:
            io->split_window(0);
            io->erase_window(iface::LOWER);
            io->set_window(iface::LOWER);
            break;
        case 0:
            io->erase_window(iface::LOWER);
            break;
        case 1:
            io->erase_window(iface::UPPER);
            break;
        default:
            FATAL("Invalid window specified.");
            break;
    }
}

void
erase_line(inst_args* args)
{
    ASSERT(args->argc == 1);
    ASSERT(m->version() >= 4);

    // TODO: does v6 style erase_line appear in v7 and v8?
    if (args->argv[0] == 1) {
        io->erase_line();
    }
}

void
set_cursor(inst_args* args)
{
    ASSERT(args->argc == 2);

    io->set_cursor((uint) args->argv[1], (uint) args->argv[0]);
}

void
get_cursor(inst_args* args)
{
    ASSERT(args->argc == 1);

    address addr = args->argv[0];
    uint x;
    uint y;

    io->get_cursor(&x, &y);

    ASSERT(x <= MAX_UWORD);
    ASSERT(y <= MAX_UWORD);

    m->write_word(addr,     (uword) y);
    m->write_word(addr + 2, (uword) x);
}

void
set_text_style(inst_args* args)
{
    ASSERT(args->argc == 1);

    if (args->argv[0] == 0) {
        io->set_text_style(iface::ROMAN);
    } else if ((args->argv[0] & 0x1) != 0) {
        io->set_text_style(iface::REVERSE);
    } else if ((args->argv[0] & 0x2) != 0) {
        io->set_text_style(iface::BOLD);
    } else if ((args->argv[0] & 0x4) != 0) {
        io->set_text_style(iface::ITALIC);
    } else if ((args->argv[0] & 0x8) != 0) {
        io->set_text_style(iface::FIXED);
    }
}

void
buffer_mode(inst_args* args)
{
    ASSERT(args->argc == 1);

    // TODO: buffer_mode
    // ASSERT(args->argv[0] == 1);
}

void
output_stream(inst_args* args)
{
    if (m->version() == 3) {
        ASSERT(args->argc == 1);
    } else if (m->version() >= 4 && m->version() <= 5) {
        ASSERT(args->argc >= 1 && args->argc <= 2);
    } else {
        FATAL("Output stream opcode not available in this machine version.");
    }

    word stream = args->argv[0];

    if (stream > 0) {
        io->select((uint) stream, args->argv[1]);
    } else if (stream < 0) {
        io->deselect((uint) (-stream));
    }
}

void
input_stream(inst_args* args)
{
    ASSERT(args->argc == 1);
    FATAL("Unimplemented instruction: input_stream.");
    // TODO: input_stream
}

void
sound_effect(inst_args* args)
{
    ASSERT(args->argc <= 4);

    // TODO: sound
}

// this class completes the read_char routine when input actually arrives
class char_continue : public input_continue {
    public:
        virtual void execute(uword c);
        virtual void cancel();
};

void
char_continue::execute(uword c)
{
    ASSERT(c <= MAX_UBYTE);

    m->store_value((ubyte) c);
    
    delete this;
}

void
char_continue::cancel()
{
    m->store_value(0);

    delete this;
}

void
read_char(inst_args* args)
{
    ASSERT(args->argc <= 3);
    ASSERT(args->argc == 0 || args->argv[0] == 1);

    char_continue* cont = new char_continue;
    MEMCHECK(cont);

    if (args->argc == 3) {
        timer_info timer;

        timer.interval = args->argv[1];
        timer.addr = args->argv[2];

        io->read_zscii(&timer, cont);
    } else {
        io->read_zscii(NULL, cont);
    }
}

void
scan_table(inst_args* args)
{
    ASSERT(args->argc >= 3 && args->argc <= 4);

    bool check_word = true;
    int entry_size = 2;

    if (args->argc == 4) {
        check_word = (args->argv[3] & 0x80) != 0;
        entry_size = (args->argv[3] & 0x7f);
    }

    uword target = args->argv[0];
    address cur = args->argv[1];
    address end = cur + (2 * args->argv[2]);

    address result = 0;
    bool found = false;

    while (cur < end) {
        if (( check_word && target == m->read_word(cur)) ||
            (!check_word && target == m->read_byte(cur))) {
            result = cur;
            found = true;
            break;
        }
        cur += entry_size;
    }

    m->store_value(result);
    m->branch(found);
}

void
not_(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->store_value(~(args->argv[0]));
}

void
call_vn(inst_args* args)
{
    ASSERT(args->argc >= 1 && args->argc <= 4);
    ASSERT(m->version() >= 5);

    m->call_routine(args->argv[0], args->argc - 1, args->argv + 1, false);
}

void
call_vn2(inst_args* args)
{
    ASSERT(args->argc >= 1 && args->argc <= 8);
    ASSERT(m->version() >= 5);

    m->call_routine(args->argv[0], args->argc - 1, args->argv + 1, false);
}

void
tokenise(inst_args* args)
{
    ASSERT(args->argc >= 2 && args->argc <= 4);

    dictionary(args->argv[2]).lex(args->argv[0], args->argv[1],
                                  (args->argv[3] != 0));
}

void
encode_text(inst_args* args)
{
    ASSERT(args->argc == 4);

    address start = args->argv[0] + args->argv[2];
    address end = start + args->argv[1];

    uword result[3];

    dictionary().z_encode(result, &start, end); // TODO: ignore separators?

    for (int i = 0; i < 3; i++) {
        m->write_word(args->argv[3] + (2 * i), result[i]);
    }
}

void
copy_table(inst_args* args)
{
    ASSERT(args->argc == 3);

    address first = args->argv[0];
    address second = args->argv[1];

    word size = args->argv[2];
    bool force_forward = false;

    if (size < 0) {
        force_forward = true;
        size *= -1;
    }

    if (second == 0) {
        for (word i = 0; i < size; i++) {
            m->write_byte(first + i, 0);
        }
    } else if (force_forward || second < first) {
        for (word i = 0; i < size; i++) {
            m->write_byte(second + i, m->read_byte(first + i));
        }
    } else if (second > first) {
        for (word i = size - 1; i >= 0; i--) {
            m->write_byte(second + i, m->read_byte(first + i));
        }
    }
}

void
print_table(inst_args* args)
{
    ASSERT(args->argc >= 2 && args->argc <= 4);
    ASSERT(m->version() >= 5);

    address addr = args->argv[0];
    uword width = args->argv[1];
    uword height = args->argv[2];
    uword skip = args->argv[3];

    uint x;
    uint y;
    io->get_cursor(&x, &y);
    // TODO: what if upper win isn't selected?

    if (args->argc == 2) {
        height = 1;
    }

    for (uword i = 0; i < height; i++) {
        io->set_cursor(x, y + i);
        for (uword j = 0; j < width; j++) {
            io->put_char(m->read_byte(addr++));
        }
        addr += skip;
    }
}

void
check_arg_count(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->branch((args->argv[0] <= m->get_num_args()));
}

// =======================================================================
//  instructions (extended opcodes)
// =======================================================================

// save & restore: see above

void
log_shift(inst_args* args)
{
    ASSERT(args->argc == 2);

    uword number = args->argv[0];
    word places = args->argv[1];

    if (places < 0) {
        m->store_value(number >> (-places));
    } else {
        m->store_value(number << places);
    }
}

void
art_shift(inst_args* args)
{
    ASSERT(args->argc == 2);

    word number = args->argv[0];
    word places = args->argv[1];

    if (places < 0) {
        m->store_value(number >> (-places));
    } else {
        m->store_value(number << places);
    }
}

void
set_font(inst_args* args)
{
    ASSERT(args->argc == 1);

    m->store_value(0); // did nothing
}

void
save_undo(inst_args* args)
{
    ASSERT(args->argc == 0);
    ASSERT(m->version() >= 5);
    ASSERT(!m->interrupted());

    if (undo == NULL) {
        undo = new game_state(*m);
        // no MEMCHECK necessary
    } else {
        *undo = *m;
    }

    // this most likely won't catch many error conditions (if new throws
    // bad_alloc), but it's better than nothing.
    m->store_value((undo != NULL) ? 1 : 0);
}

void
restore_undo(inst_args* args)
{
    ASSERT(args->argc == 0);
    ASSERT(m->version() >= 5);
    ASSERT(!m->interrupted());

    if (undo == NULL) {
        // failure
        m->store_value(0);
    } else {
        // restore and return save's "i'm restoring" return code
        game_state* cur = m;
        *cur = *undo;
        m->store_value(2);
    }
}

void
print_unicode(inst_args* args)
{
    ASSERT(args->argc == 1);
    FATAL("Unimplemented instruction: print_unicode.");
    // TODO: print_unicode
}

void
check_unicode(inst_args* args)
{
    ASSERT(args->argc == 1);
    FATAL("Unimplemented instruction: check_unicode.");
    // TODO: check_unicode
}

// =======================================================================
//  instruction handler tables
// =======================================================================

inst_handler two_inst_table[32] =
{
    unimplemented,
    je,
    jl,
    jg,
    dec_chk,
    inc_chk,
    jin,
    test,
    or_,
    and_,
    test_attr,
    set_attr,
    clear_attr,
    store,
    insert_obj,
    loadw,
    loadb,
    get_prop,
    get_prop_addr,
    get_next_prop,
    add,
    sub,
    mul,
    div,
    mod,
    call_2s,
    call_2n,
    set_colour,
    unimplemented,
    unimplemented,
    unimplemented,
    unimplemented
};

inst_handler one_inst_table[16] =
{
    jz,
    get_sibling,
    get_child,
    get_parent,
    get_prop_len,
    inc,
    dec,
    print_addr,
    call_1s,
    remove_obj,
    print_obj,
    ret,
    jump,
    print_paddr,
    load,
    call_1n, // not really (ha!)
};

inst_handler zero_inst_table[16] =
{
    rtrue,
    rfalse,
    print,
    print_ret,
    nop,
    save,
    restore,
    restart,
    ret_popped,
    pop,
    quit,
    new_line,
    show_status,
    verify,
    unimplemented, // extended opcode
    piracy
};

inst_handler var_inst_table[32] =
{
    call_vs,
    storew,
    storeb,
    put_prop,
    sread,
    print_char,
    print_num,
    random,
    push,
    pull,
    split_window,
    set_window,
    call_vs2,
    erase_window,
    erase_line,
    set_cursor,
    get_cursor,
    set_text_style,
    buffer_mode,
    output_stream,
    input_stream,
    sound_effect,
    read_char,
    scan_table,
    not_,
    call_vn,
    call_vn2,
    tokenise,
    encode_text,
    copy_table,
    print_table,
    check_arg_count
};

inst_handler ext_inst_table[] =
{
    save,
    restore,
    log_shift,
    art_shift,
    set_font,
    unimplemented, // draw_picture 6
    unimplemented, // picture_data 6
    unimplemented, // erase_picture 6
    unimplemented, // set_margins 6
    save_undo,
    restore_undo,
    print_unicode,
    check_unicode
};
