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

// =======================================================================
//  obj.cc:
//
//  this module defines the object class, which is used as a higher-level
//  representation of objects within the z-machine's memory.  the object
//  basically contains the address of the associated object, providing an
//  interface that simplifies code in inst.cc a great deal.
// =======================================================================

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

extern machine* m;

// =======================================================================
//  object
// =======================================================================

// constructor
//
// make an object object from its number.

object::object(uword n_in)
  : n(n_in), addr(0)
{
    if (n > 0) {
        addr = m->read_word(HEADER_OBJECT_BASE) +
               (2 * get_num_defaults()) +
               ((n - 1) * get_object_size());
    }
}

// copy constructor
//
// whee!

object::object(const object& obj)
  : n(obj.n), addr(obj.addr)
{
}

// operator=
//
// words, words, words...

object&
object::operator=(const object& obj)
{
    n = obj.n;
    addr = obj.addr;
    return *this;
}

// operator==
//
// allows easy comparison of objects.

bool
object::operator==(const object& obj)
{
    ASSERT((n == obj.n) == (addr == obj.addr));
    return (n == obj.n);
}

// operator!=
//
// allows easy comparison of objects.

bool
object::operator!=(const object& obj)
{
    ASSERT((n == obj.n) == (addr == obj.addr));
    return (n != obj.n);
}

// valid
//
// does this object object represent a valid z-machine object?

bool
object::valid()
{
    return (n > 0);
}

// get_number
//
// get the number identifying this object within the z-machine.

uword
object::get_number()
{
    return n;
}

// get_name
//
// get the address of this object's name in memory.

address
object::get_name()
{
    return (address)m->read_word(addr + get_table_offset()) + 1;
}

// get_attr_byte
//
// gets one of the bytes of attribute flags.  internal only.

ubyte
object::get_attr_byte(ubyte attr) const
{
    return m->read_byte(addr + (attr / 8));
}

// set_attr_byte
//
// sets one of the bytes of attribute flags.  internal only.

void
object::set_attr_byte(ubyte attr, ubyte byte)
{
    m->write_byte(addr + (attr / 8), byte);
}

// get_attr
//
// retrieves a boolean attribute, given the number of the attribute.

bool
object::get_attr(ubyte attr) const
{
    ASSERT(n > 0);
    ASSERT((m->version() <= 3 && attr < 32) || (m->version() >= 4 && attr < 48));

    return ((get_attr_byte(attr) & (1 << (7 - (attr % 8)))) != 0);
}

// set_attr
//
// sets a boolean attribute, given the number of the attribute.

void
object::set_attr(ubyte attr, bool value)
{
    ASSERT(n > 0);
    ASSERT((m->version() <= 3 && attr < 32) || (m->version() >= 4 && attr < 48));

    ubyte byte = get_attr_byte(attr);

    if (value) {
        byte |= (1 << (7 - (attr % 8)));
    } else {
        byte &= ~(1 << (7 - (attr % 8)));
    }

    set_attr_byte(attr, byte);
}

// get_parent
//
// get an object representing this z-machine object's parent.

object
object::get_parent() const
{
    ASSERT(n > 0);

    uword n_parent = (m->version() <= 3) ?
                     m->read_byte(addr + PARENT_OFFSET1) :
                     m->read_word(addr + PARENT_OFFSET4);

    return object(n_parent);
}

// get_sibling
//
// get an object representing this z-machine object's sibling.

object
object::get_sibling() const
{
    ASSERT(n > 0);

    uword n_sibling = (m->version() <= 3) ?
                      m->read_byte(addr + SIBLING_OFFSET1) :
                      m->read_word(addr + SIBLING_OFFSET4);

    return object(n_sibling);
}

// get_child
//
// get an object representing this z-machine object's child.

object
object::get_child() const
{
    ASSERT(n > 0);

    uword n_child = (m->version() <= 3) ?
                    m->read_byte(addr + CHILD_OFFSET1) :
                    m->read_word(addr + CHILD_OFFSET4);

    return object(n_child);
}

// set_parent
//
// sets a z-machine object's parent to another z-machine object.

void
object::set_parent(const object& parent)
{
    ASSERT(n > 0);

    if (m->version() <= 3) {
        ASSERT(parent.n <= MAX_UBYTE);
        m->write_byte(addr + PARENT_OFFSET1, (ubyte) parent.n);
    } else {
        m->write_word(addr + PARENT_OFFSET4, parent.n);
    }
}

// set_sibling
//
// sets a z-machine object's sibling to another z-machine object.

void
object::set_sibling(const object& sibling)
{
    ASSERT(n > 0);

    if (m->version() <= 3) {
        ASSERT(sibling.n <= MAX_UBYTE);
        m->write_byte(addr + SIBLING_OFFSET1, (ubyte) sibling.n);
    } else {
        m->write_word(addr + SIBLING_OFFSET4, sibling.n);
    }
}

// set_child
//
// sets a z-machine object's child to another z-machine object.

void
object::set_child(const object& child)
{
    ASSERT(n > 0);

    if (m->version() <= 3) {
        ASSERT(child.n <= MAX_UBYTE);
        m->write_byte(addr + CHILD_OFFSET1, (ubyte) child.n);
    } else {
        m->write_word(addr + CHILD_OFFSET4, child.n);
    }
}

// insert
//
// insert this z-machine object into the child list of the given z-machine
// object.  (it appears as the first child.)

void
object::insert(object& dest)
{
    remove();

    set_parent(dest);
    set_sibling(dest.get_child());
    dest.set_child(*this);
}

// remove
//
// remove an object from the object tree.  (its parent becomes 0.)

void
object::remove()
{
    object parent = get_parent();

    if (parent.valid()) {
        object prev = parent;
        object child = parent.get_child();

        while (child != *this) {
            prev = child;
            child = child.get_sibling();
            ASSERT(child.valid());
        }

        if (prev == parent) {
            parent.set_child(get_sibling());
        } else {
            prev.set_sibling(get_sibling());
        }
    }

    set_parent(0);                                        
}

// get_property
//
// returns the property object for the nth property of this object.

property
object::get_property(ubyte n)
{
    return property(m->read_word(addr + get_table_offset()), n);
}

// get_first_property
//
// get the first property in this object's property list.

property
object::get_first_property()
{
    address table = m->read_word(addr + get_table_offset());
    return property(table + 1 + (m->read_byte(table) * 2));
}
