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

// =======================================================================
//  bfish.cc:
//
//  the bfish class decodes z-encoded strings into zscii text, handing
//  off the results to any zscii_output class.  bfish itself is an
//  abstract class; the bfish_addr and bfish_pc classes implement the
//  next_zword method to tell bfish where to get its data.
// =======================================================================
 
#include "zeal.h"
#include "error.h"
#include "bfish.h"
#include "machine.h"
#include "alpha.h"

extern machine* m;

// =======================================================================
//  bfish
// =======================================================================

// static data
//
// maintains mapping between shift states for earlier z-machine version.

const int bfish::shift_change[][3] =
{
    { 1, 2, 0 },
    { 2, 0, 1 }
};

// constructor/destructor
//
// empty, for now.

bfish::bfish()
  : alpha()
{
}

bfish::~bfish()
{
}

// print_string
//
// decodes a string of z-chars, printing it using the method in the output
// object provided.  (this allows printing to a variety of different
// streams.)
//
// the context structure is allocated on the stack so that we're
// reentrant.  you might think i'd want to make this struct into a class,
// but you'd be wrong.  :-P

void
bfish::print_string(zscii_output* output, bool is_abbrev)
{
    uword word;

    context ctx;
    ctx.cur_state = NORMAL;
    ctx.cur_shift = 0;
    ctx.locked_shift = 0;
    ctx.output = output;
    ctx.is_abbrev = is_abbrev;

    while (1) {
        word = next_zword();

        print_zchar(&ctx, (word & 0x7c00) >> 10);
        print_zchar(&ctx, (word & 0x03e0) >>  5);
        print_zchar(&ctx, (word & 0x001f));

        if (word & 0x8000) {
            return;
        }
    }
}

// print_zchar
//
// helper for print_string that prints a single z-char.  we can call
// print_string again here since all the context for this string of
// z-chars is in the ctx structure.

void
bfish::print_zchar(context* ctx, ubyte zchar)
{
    state new_state = NORMAL;
    int new_shift = ctx->locked_shift;

    switch (ctx->cur_state) {
        case ABBREV:
            {
            ASSERT_MSG(!ctx->is_abbrev, "Found nested abbreviation!");
            address entry = m->read_word(HEADER_ABBREV_BASE) +
                            (2 * ((32 * (ctx->abbrev - 1)) + zchar));
            address str = m->read_word(entry) * 2;
            bfish_addr(str).print_string(ctx->output, true);
            }
            break;
        case ZSCII1:
            ctx->zscii = zchar;
            new_state = ZSCII2;
            break;
        case ZSCII2:
            ctx->output->put_char((ctx->zscii << 5) | zchar);
            break;
        case NORMAL:
            if (zchar == 0) {
                ctx->output->put_char(' ');
            } else if (zchar <= 3) {
                if (m->version() == 1 && zchar == 1) {
                    ctx->output->put_char('\r');
                } else if (m->version() < 3 && (zchar == 2 || zchar == 3)) {
                    new_shift = shift_change[zchar - 1][ctx->cur_shift];
                } else {
                    // version >= 3, cases 1, 2, 3
                    // version == 2, case 1
                    ctx->abbrev = zchar;
                    new_state = ABBREV;
                }
            } else if (zchar <= 5) {
                if (m->version() < 3) {
                    new_shift = shift_change[zchar - 3][ctx->cur_shift];
                    ctx->locked_shift = new_shift;
                } else {
                    new_shift = zchar - 3;
                }
            } else if (zchar == 6 && ctx->cur_shift == 2) {
                new_state = ZSCII1;
            } else {
                ubyte c = alpha.zchar_to_zscii(ctx->cur_shift, zchar);
                ctx->output->put_char(c);
            }
            break;
    }

    ctx->cur_state = new_state;
    ctx->cur_shift = new_shift;
}

// =======================================================================
//  bfish_addr
// =======================================================================

// constructor/destructor
//
// creates a bfish that reads from a specific address in memory.

bfish_addr::bfish_addr(address addr_in)
  : addr(addr_in)
{
}

bfish_addr::~bfish_addr()
{
}

// next_zword
//
// get next word and increment address.

uword
bfish_addr::next_zword()
{
    uword w = m->read_word(addr);
    addr += 2;
    return w;
}

// =======================================================================
//  bfish_pc
// =======================================================================

// constructor/destructor
//
// creates a bfish that reads from the current pc (and thus increments
// it).

bfish_pc::bfish_pc()
{
}

bfish_pc::~bfish_pc()
{
}

// next_zword
//
// get next word from pc.

uword
bfish_pc::next_zword()
{
    return m->read_pc_word();
}
