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

// =======================================================================
//  prop.cc:
//
//  this module defines the property class, which is used as a
//  higher-level representation of properties within the z-machine's
//  memory.  the object basically contains the address of the associated
//  property, providing an interface that simplifies code in inst.cc a
//  great deal.
//
//  note that properties can only be created by two means:
//    1. by an object object (see obj.cc)
//    2. as an invalid object, to be assigned later
// =======================================================================

#include "zeal.h"
#include "error.h"
#include "prop.h"
#include "machine.h"

extern machine* m;

// =======================================================================
//  property
// =======================================================================

// constructor
//
// creates an invalid property object.

property::property()
  : addr(0)
{
}

// constructor
//
// creates a property object corresponding to the nth property in a given
// property table.  only object objects can use this method!

property::property(address table, ubyte n)
{
    ASSERT(table != 0);

    addr = table + 1 + (2 * m->read_byte(table));

    while (1) {
        ubyte num = get_number();

        if (n == num) {
            return;
        } else if (n < num) {
            addr += get_header_size() + get_size();
        } else {
            addr = 0;
            return;
        }
    }
}

// constructor
//
// make a property object given the address of the property's header, not
// the property data.

property::property(address addr_in)
  : addr(addr_in)
{
    if (get_number() == 0) {
        addr = 0;
    }
}

// copy constructor
//
// just as expected.

property::property(const property& prop)
  : addr(prop.addr)
{
}

// operator=
//
// no surprises.

property&
property::operator=(const property& prop)
{
    addr = prop.addr;
    return *this;
}

// valid
//
// does this property object represent a real property?

bool
property::valid()
{
    return (addr != 0);
}

// get_number
//
// return the number identifying this property within its object.

ubyte
property::get_number()
{
    ASSERT(valid());
    return (m->read_byte(addr) & ((m->version() <= 3) ? 0x1f : 0x3f));
}

// get_header_size
//
// return the number of bytes preceding the actual property data.

ubyte
property::get_header_size()
{
    if (m->version() <= 3) {
        return 1;
    } else {
        return (m->read_byte(addr) & 0x80) ? 2 : 1;
    }
}

// get_size
//
// gets the size of the property data itself.

ubyte
property::get_size()
{
    ASSERT(valid());

    if (m->version() <= 3) {
        return (m->read_byte(addr) >> 5) + 1;
    } else {
        ubyte b = m->read_byte(addr);

        if (b & 0x80) {
            ubyte size = m->read_byte(addr + 1) & 0x3f;
            return (size != 0) ? size : 64;
        } else {
            return (b & 0x40) ? 2 : 1;
        }
    }
}

// get_address
//
// gets the address of the actual property data (not including header).

address
property::get_address()
{
    ASSERT(valid());
    return addr + get_header_size();
}

// get_next
//
// given a property, get the next property in line.  will be invalid for
// the last property.

property
property::get_next()
{
    ASSERT(valid());
    return property(addr + get_size() + get_header_size());
}

// read_word
//
// read a word from the beginning of a property.

uword
property::read_word()
{
    ASSERT(valid());

    switch (get_size()) {
        default:
            // TODO: is this valid?  christminster complains otherwise.
            // fall through
        case 2:
            return m->read_word(addr + get_header_size());
        case 1:
            return m->read_byte(addr + get_header_size());
    }
}

// write_word
//
// write a word to the beginning of a property.

void
property::write_word(uword word)
{
    ASSERT(valid());

    switch (get_size()) {
        case 1:
            m->write_byte(addr + get_header_size(), word & 0xff);
            break;
        case 2:
            m->write_word(addr + get_header_size(), word);
            break;
        default:
            ASSERT(false);
            break;
    }
}

// from_addr
//
// static function taking the place of the address-based constructor.
// creates a property based on the address of its data, not the address
// of the header.

property
property::from_addr(address addr)
{
    if (m->version() <= 3) {
        return property(addr - 1);
    } else {
        if (m->read_byte(addr - 1) & 0x80) {
            return property(addr - 2);
        } else {
            return property(addr - 1);
        }
    }
}
