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

// =======================================================================
//  machine.h:
//
//  this module defines the main machine class, which handles memory
//  references, local variables, global variables, initialization, and
//  other good stuff.
//
//  note that the implementation of machine is split between two files:
//  machine.cc and quetzal.cc
// =======================================================================

#ifndef _machine_h_
#define _machine_h_

#include "zeal.h"

const address HEADER_VERSION         = 0x00;
const address HEADER_FLAGS1          = 0x01;
const address HEADER_RELEASE         = 0x02;
const address HEADER_HIGH_BASE       = 0x04;
const address HEADER_INIT_PC         = 0x06;
const address HEADER_DICTIONARY_BASE = 0x08;
const address HEADER_FLAGS2          = 0x10;
const address HEADER_SERIAL          = 0x12;
const address HEADER_ABBREV_BASE     = 0x18;
const address HEADER_OBJECT_BASE     = 0x0a;
const address HEADER_GLOBAL_BASE     = 0x0c;
const address HEADER_STATIC_BASE     = 0x0e;
const address HEADER_LENGTH          = 0x1a;
const address HEADER_CHECKSUM        = 0x1c;
const address HEADER_INT_NUM         = 0x1e;
const address HEADER_INT_VER         = 0x1f;
const address HEADER_SCREEN_HEIGHT   = 0x20;
const address HEADER_SCREEN_WIDTH    = 0x21;
const address HEADER_SCREEN_WIDTH2   = 0x22;
const address HEADER_SCREEN_HEIGHT2  = 0x24;
const address HEADER_FONT_WIDTH      = 0x26;
const address HEADER_FONT_HEIGHT     = 0x27;
const address HEADER_ROUTINE_OFFSET  = 0x28;
const address HEADER_STRING_OFFSET   = 0x2a;
const address HEADER_BG_COLOR        = 0x2c;
const address HEADER_FG_COLOR        = 0x2d;
const address HEADER_REV_NUMBER      = 0x32;
const address HEADER_ALPHA_BASE      = 0x34;

struct branch_info {
    word offset;
    bool on_true;
};

struct frame {
    address return_addr;
    uword locals[15];
    ubyte num_locals;
    ubyte num_args;
    uint stack_ptr;
    ubyte return_var;
    bool return_var_valid;
    bool interrupt_start;
};

class interrupt_continue {
    public:
        virtual void execute(uword value) = 0;
        virtual void cancel() = 0;
};

class machine;

class game_state {
    public:
        game_state(const game_state& orig);
        virtual ~game_state();

        game_state& operator=(const game_state& orig);

    private:
        // we could just make these protected, but that doesn't solve the
        // problem of a machine trying to access a different game_state's
        // members, which needs to happen occasionally.
        friend machine;

        game_state();

        ubyte ver;

        address pc;

        // TODO: perhaps a stack class would be in order?

        uword* stack;
        uint stack_size;

        frame* call_stack;
        uint call_stack_size;
        uint call_stack_ptr;

        ubyte* memory;
        uint mem_size;
        uint dyn_size;

        bool is_halted;
        bool in_interrupt;

        interrupt_continue* cont;
};

class machine : public game_state {
    public:
        machine(strid_t s);
        virtual ~machine();

        bool halted();
        bool interrupted();
        ubyte version();

        void one_inst();
        void halt();

        bool read_bit(address addr, ubyte which);
        void write_bit(address addr, ubyte which, bool bit);

        ubyte read_byte(address addr);
        void write_byte(address addr, ubyte value);

        uword read_word(address addr);
        void write_word(address addr, uword value);

        uword read_var(ubyte var);
        void write_var(ubyte var, uword value);

        ubyte read_pc_opcode();
        ubyte read_pc_byte();
        uword read_pc_word();
        uword read_pc_var();
        branch_info read_pc_branch();

        void set_header();

        address packed_to_byte(address addr, bool routine);

        strid_t open_mem_stream(address addr);
        void close_mem_stream(strid_t s);

        ubyte get_num_args();

        void store_value(uword value);
        void call_routine(address addr, ubyte argc, uword* argv,
                          bool store_return);
        void interrupt(address addr, interrupt_continue* cont_in);
        void return_value(uword value);
        void branch(bool condition);
        void jump(uint amt);

        bool save_state(strid_t s, game_state* initial);
        bool load_state(strid_t s, game_state* initial);

        bool verify();

    private:
        static const uint MAX_STACK;
        static const uint MAX_CALL_STACK;

        machine(const machine& orig);
        machine& operator=(const machine& orig);

        void check_read(address addr);
        void check_write(address addr);

        struct story_sig {
            uword release;
            uword serial[3];
            uword checksum;
        };

        uint chars_to_int(const char* s);

        void write_file_byte(strid_t s, ubyte b) { write_file_int(s, b, 1); }
        void write_file_word(strid_t s, uword w) { write_file_int(s, w, 2); }
        void write_file_int(strid_t s, uint d, uint n = 4);
        void write_header(strid_t s);
        void write_umem(strid_t s);
        void write_cmem(strid_t s, game_state* initial);
        void write_frame(strid_t s, frame* fr, uint prev_stack_ptr);
        void write_stacks(strid_t s);

        ubyte read_file_byte(strid_t s) { return read_file_int(s, 1); }
        uword read_file_word(strid_t s) { return read_file_int(s, 2); }
        uint read_file_int(strid_t s, uint n = 4);
        bool read_header(strid_t s, uint length, story_sig* sig);
        bool read_umem(strid_t s, uint length);
        bool read_cmem(strid_t s, uint length, game_state* initial);
        uint read_frame(strid_t s, frame* fr, uint prev_stack_ptr);
        bool read_stacks(strid_t s, uint length);
};

#endif // _machine_h_
