/*
 * property.c
 *
 * Property manipulation routines
 *
 */

#include <string.h>
#include <stdio.h>

#include "ztypes.h"
#include "property.h"
#include "osdepend.h"
#include "memory.h"
#include "operand.h"
#include "object.h"
#include "text.h"
#include "v6.h"

int property_mask = 0;
int property_size_mask = 0;

unsigned h_static_offset;
unsigned h_static_limit;
//unsigned h_high_memory_offset;

/*
 * get_property_addr
 *
 * Calculate the address of the start of the property list associated with an
 * object.
 *
 */

static unsigned int get_property_addr(unsigned int obj)
{
    unsigned int offset, prop;

    /* Calculate the address of the property pointer in the object */

    offset = object_address(obj);
    offset += (h_type <= V3) ? O3_PROPERTY_OFFSET : O4_PROPERTY_OFFSET;

    /* Read the property pointer */

    prop = get_word(offset);

    /* Skip past object description which is an ASCIC of encoded words */

    prop += get_byte(prop) * 2 + 1;

    return prop;

}/* get_prop_addr */

/*
 * get_next_property
 *
 * Calculate the address of the next property in a property list.
 *
 */

static unsigned int get_next_property(unsigned int propp)
{
    unsigned int value;

    /* Load the current property id */

    value = get_byte(propp);
    propp++;

    /* Calculate the length of this property */

    if (h_type <= V3)
        /* In V1-3, first byte has size-1 in top 3 bits, property number in bottom 5 */
        value >>= 5;

    else if (value & 0x80)
    {
        /* If top bit of byte 1 is set, size is bottom 6 bits of second byte (+1) */
        value = get_byte(propp) & 0x3f;
        if (value == 0) value = 64;
    }
    else
        /* Otherwise if bit 6 of byte 1 is set, size=2 */
        /* Otherwise size=1 */
        value >>= 6;

    /* Address property length + 1 to current property pointer */

    return propp + value + 1;

}/* get_next_property */

/*
 * get_prop
 *
 * Load a property from a property list. Properties are held in list sorted by
 * property id, with highest ids first. There is also a concept of a default
 * property for loading only. The default properties are held in a table pointed
 * to be the object pointer, and occupy the space before the first object.
 *
 */

void z_get_prop(unsigned int obj, unsigned int prop)
{
    unsigned int propp, size;

    /* Load address of first property */

    propp = get_property_addr(obj);

    /* Scan down the property list while the target property id is less than the
       property id in the list */

    while (((size = get_byte(propp)) & property_mask) > prop)
        propp = get_next_property(propp);

    /* If the property ids match then load the first property */

    if ((size & property_mask) == prop)
    {
        /* Only load first property if it is a byte sized property */
    	if (h_type >= V4)
    	{
    	    if (size & 0x80)
    	    {
    	    	size = get_byte(++propp) & 0x3f;
    	    	if (size == 0) size = 64;
    	    }
    	    else
    	    	size = (size>>6) + 1;
    	    prop++;
        }
        else
            size= (size>>5) + 1;

        propp++;

        if (size == 1)
        {
            store_operand(get_byte(propp));
            return;
        }
        //if (size != 2)
        //    warning("Property too long");
    }
    else

        /* Calculate the address of the default property */

        propp = h_objects_offset + ((prop - 1) * 2);

    /* Load the first property word */

    store_operand(get_word(propp));

}/* get_prop */

/*
 * put_prop
 *
 * Store a property value in a property list. The property must exist in the
 * property list to be replaced.
 *
 */

void z_put_prop(unsigned int obj, unsigned int prop, unsigned value)
{
    unsigned int propp, size;

    /* Load address of first property */

    propp = get_property_addr(obj);

    /* Scan down the property list while the target property id is less than the
       property id in the list */

    while (((size = get_byte(propp)) & property_mask) > prop)
        propp = get_next_property(propp);

    /* If the property id was found then store a new value, otherwise complain */

    if ((size & property_mask) == prop)
    {
        /* Determine if this is a byte or word sized property */
    	if (h_type >= V4)
    	{
    	    if (size & 0x80)
    	    {
    	        propp++;
    	        size=get_byte(propp) & 0x3f;
    	        if (size == 0) size = 64;
    	    }
    	    else
    	    	size = (size>>6) + 1;
    	}
    	else
    	    size = (size>>5) + 1;

        propp++;

        if (size == 1)
            set_byte(propp, value);
        else
            set_word(propp, value);
    }
    else
        fatal_lookup("NoProp");

}

/*
 * get_next_prop
 *
 * Load the property after the current property. If the current property is zero
 * then load the first property.
 *
 */

void z_get_next_prop(unsigned int obj, unsigned int prop)
{
    unsigned int propp, no;

    /* Load address of first property */

    propp = get_property_addr(obj);

    /* If the property id is non zero then find the next property */

    if (prop)
    {
        /* Scan down the property list while the target property id is less than the
           property id in the list */

        while ((no = (get_byte(propp) & property_mask)) > prop)
            propp = get_next_property (propp);

        /* If the property id was found then get the next property, otherwise complain */

        if (no == prop)
            propp = get_next_property (propp);
        else
            fatal_lookup("NoProp");
    }

    /* Return the next property id */

    store_operand(get_byte(propp) & property_mask);

}/* get_next_prop */

/*
 * get_prop_addr
 *
 * Load the address address of the data associated with a property.
 *
 */

void z_get_prop_addr(unsigned int obj, unsigned int prop)
{
    unsigned int propp, no;

    /* Load address of first property */

    propp = get_property_addr(obj);

    /* Scan down the property list while the target property id is less than the
       property id in the list */

    while (((no = get_byte(propp)) & property_mask) > prop)
        propp = get_next_property(propp);

    /* If the property id was found then calculate the property address, otherwise return zero */

    if ((no & property_mask) == prop)
    {
        /* Skip past property id, can be a byte or a word */

        if (h_type >= V4 && (no & 0x80))
            propp++;
        store_operand(propp+1);
    }
    else

        /* No property found, just return 0 */

        store_operand(0);

}/* get_prop_addr */

/*
 * get_prop_len
 *
 * Load the length of a property.
 *
 */

void z_get_prop_len(unsigned int propp)
{
    if (propp == 0)

        store_operand(0);

    else
    {
        unsigned size;

        /* Back up the property pointer to the property id */

        propp--;

        size = get_byte(propp);

        if (h_type <= V3)

            /* Property length is in high bits of property id */

            store_operand((size >> 5) + 1);

        else if (size & 0x80)
        {
            /* Property length is in property id */

            size &= 0x3f;
            if (size == 0) size = 64;

            store_operand(size);
        }
        else

            /* Word sized property if bit 6 set, else byte sized property */

            store_operand((size >> 6) + 1);
    }

}/* get_prop_len */

/*
 * scan_table
 *
 * Scan an array of bytes or words looking for a target byte or word. The
 * optional 4th parameter can set the address step and also whether to scan a
 * byte array.
 *
 */

void z_scan_table(int argc, unsigned *argv)
{
    unsigned int i, step, address;

    /* Supply default parameters */

    if (argc < 4)
        argv[3] = 0x82;

    address = argv[1];
    step = argv[3];

    /* Check size bit (bit 7 of step, 1 = word, 0 = byte) */

    if (step & 0x80)
    {
        step &= 0x7f;

        /* Scan down an array for count words looking for a match */

        for (i = 0; i < argv[2]; i++, address+=step)
            if (get_word(address) == argv[0])
            {
                store_operand(address);
                conditional_jump(TRUE);
                return;
            }
    }
    else
    {
        /*step &= 0x7f;*/

        /* Scan down an array for count bytes looking for a match */

        for (i = 0; i < argv[2]; i++, address+=step)
            if (get_byte(address) == argv[0])
            {
                store_operand(address);
                conditional_jump(TRUE);
                return;
            }
    }

    /* If the data was not found store zero and jump */

    store_operand(0);
    conditional_jump(FALSE);

}/* scan_table */

/*
 * copy_table
 *
 */

void z_copy_table(unsigned int src, unsigned int dst, short count)
{
    unsigned int address, i;

    /* Catch no-op move case */

    if (src == dst || count == 0)
        return;

    /* If destination address is zero then fill source with zeros */

    if (dst == 0)
    {
        if (src <= H_FLAGS)
            for (i = 0; i < count; i++)
                z_storeb(src++, 0, 0);
        else
            for (i = 0; i < count; i++)
                set_byte(src++, 0);

        return;
    }

    /* if count>0, do a "memmove" - ie adjust copy direction to ensure
       copy works

       if count<0, copy forwards - this can be used to fill arrays */

    if (count > 0 && src > dst)
    	count=-count;

    if (dst <= H_FLAGS)
    {
        /* Slow method - need to check for FIXED_FONT_FLAG splattage */
        address = src;

        if (count < 0)
            while (count++)
            {
                z_storeb(dst++, 0, get_byte(address));
                address++;
            }
        else
        {
            address += count;
            dst += count;
            while (count--)
            {
                address--;
                z_storeb(--dst, 0, get_byte(address));
            }
        }
    }
    else
    {
        if (count < 0)
            while (count++)
                set_byte(dst++, get_byte(src++));
        else if (src < dst)
            while (count--)
                set_byte(dst+count, get_byte(src+count));
        else
            while (count--)
                set_byte(dst++, get_byte(src++));
    }

}/* copy_table */

/*
 * loadw
 *
 * Load a word from an array of words
 *
 */

void z_loadw(unsigned int addr, unsigned int offset)
{
    unsigned int address;

    /* Calculate word array index address */

    address = addr + (offset * 2);

    /* Store the byte */

    store_operand(get_word(address));

}/* loadw */

/*
 * loadb
 *
 * Load a byte from an array of bytes
 *
 */

void z_loadb(unsigned int addr, unsigned int offset)
{
    unsigned int address;

    /* Calculate byte array index address */

    address = addr + offset;

    /* Load the byte */

    store_operand(get_byte(address));

}/* loadb */

/*
 * storew
 *
 * Store a word in an array of words
 *
 */

void z_storew(unsigned int addr, unsigned int offset, unsigned value)
{
    /* Calculate word array index address */

    addr += offset * 2;

    /* Check whether we're writing the fixed space bit */

    if (addr == H_FLAGS)
    {
        int new_flag, old_flag;
        new_flag = (value & FIXED_FONT_FLAG)!=0;
    	old_flag=(get_word_priv(H_FLAGS) & FIXED_FONT_FLAG)!=0;

        if (new_flag != old_flag)
        {
            write_char(EMBED_FIXED+new_flag);
            if (h_type == V6) v6_update_char_size();
        }
    }

    /* Store the word */

    set_word(addr, value);

}/* storew */

/*
 * storeb
 *
 * Store a byte in an array of bytes
 *
 */

void z_storeb(unsigned int addr, unsigned int offset, unsigned value)
{
    /* Calculate byte array index address */

    addr += offset;

    /* Check whether we're writing the fixed space bit */

    if (addr == H_FLAGS)
    {
        int new_flag = (value & FIXED_FONT_FLAG)!=0;
    	int old_flag = (get_byte_priv(H_FLAGS) & FIXED_FONT_FLAG)!=0;

        if (new_flag != old_flag)
        {
            write_char(EMBED_FIXED+new_flag);
            if (h_type == V6) v6_update_char_size();
        }
    }

    /* Store the byte */

    set_byte (addr, value);

}/* storeb */

unsigned get_byte(unsigned offset)
{
    if (offset < h_static_limit)
        return datap[offset];

    return illegal_read(offset);
}

unsigned get_word(unsigned offset)
{
    if (offset+1 < h_static_limit)
        return (datap[offset] << 8) | datap[offset+1];

    return illegal_read(offset);
}

void set_byte(unsigned offset, unsigned value)
{
    if (offset < h_static_offset)
        datap[offset] = value;
    else
        illegal_write(offset);
}

void set_word(unsigned offset, unsigned value)
{
    if (offset+1 < h_static_offset)
    {
        datap[offset] = value >> 8;
        datap[offset+1] = value;
    }
    else
        illegal_write(offset);
}
