/******************************************************************************
  Copyright (c) 1995, 1996 Xerox Corporation.  All rights reserved.
  Portions of this code were written by Stephen White, aka ghond.
  Use and copying of this software and preparation of derivative works based
  upon this software are permitted.  Any distribution of this software or
  derivative works must comply with all applicable United States export
  control laws.  This software is made available AS IS, and Xerox Corporation
  makes no warranty about the software, its performance or its conformity to
  any specification.  Any person obtaining a copy of this software is requested
  to send their name and post office or electronic mail address to:
    Pavel Curtis
    Xerox PARC
    3333 Coyote Hill Rd.
    Palo Alto, CA 94304
    Pavel@Xerox.Com
 *****************************************************************************/

/*****************************************************************************
 * Routines for manipulating properties on DB objects
 *****************************************************************************/

#include "config.h"
#include "db.h"
#include "db_private.h"
#include "list.h"
#include "storage.h"
#include "utils.h"
#include "waif.h"

Propdef
dbpriv_new_propdef(const char *name)
{
    Propdef newprop;

    newprop.name = str_ref(name);
    newprop.hash = str_hash(name);
    return newprop;
}

int
dbpriv_count_properties(Objid oid)
{
    Object *o;
    int nprops = 0;

    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent))
	nprops += o->propdefs.cur_length;

    return nprops;
}

static int
property_defined_at_or_below(const char *pname, int phash, Objid oid)
{
    /* Return true iff some descendant of OID defines a property named PNAME.
     */
    Objid c;
    Proplist *props = &dbpriv_find_object(oid)->propdefs;
    int length = props->cur_length;
    int i;

    for (i = 0; i < length; i++)
	if (props->l[i].hash == phash
	    && !mystrcasecmp(props->l[i].name, pname))
	    return 1;

    for (c = dbpriv_find_object(oid)->child;
	 c != NOTHING;
	 c = dbpriv_find_object(c)->sibling)
	if (property_defined_at_or_below(pname, phash, c))
	    return 1;

    return 0;
}

static void
insert_prop(Objid oid, int pos, Pval pval)
{
    Pval *new_propval;
    Object *o;
    int i, nprops;

    nprops = dbpriv_count_properties(oid);
    new_propval = mymalloc(nprops * sizeof(Pval), M_PVAL);

    o = dbpriv_find_object(oid);

    free_waif_propdefs(o->waif_propdefs);
    o->waif_propdefs = NULL;

    for (i = 0; i < pos; i++)
	new_propval[i] = o->propval[i];

    new_propval[pos] = pval;
    new_propval[pos].var = var_ref(pval.var);
    if (new_propval[pos].perms & PF_CHOWN)
	new_propval[pos].owner = o->owner;

    for (i = pos + 1; i < nprops; i++)
	new_propval[i] = o->propval[i - 1];

    if (o->propval)
	myfree(o->propval, M_PVAL);
    o->propval = new_propval;
}

static void
insert_prop_recursively(Objid root, int root_pos, Pval pv)
{
    Objid c;

    insert_prop(root, root_pos, pv);
    pv.var.type = TYPE_CLEAR;	/* do after initial insert_prop so only
				   children will be TYPE_CLEAR */
    for (c = dbpriv_find_object(root)->child;
	 c != NOTHING;
	 c = dbpriv_find_object(c)->sibling) {
	int new_prop_count = dbpriv_find_object(c)->propdefs.cur_length;

	insert_prop_recursively(c, new_prop_count + root_pos, pv);
    }
}

int
db_add_propdef(Objid oid, const char *pname, Var value, Objid owner,
	       unsigned flags)
{
    Object *o;
    Pval pval;
    int i;
    db_prop_handle h;

    h = db_find_property(oid, pname, 0);

    if (h.ptr || property_defined_at_or_below(pname, str_hash(pname), oid))
	return 0;

    o = dbpriv_find_object(oid);
    if (o->propdefs.cur_length == o->propdefs.max_length) {
	Propdef *old_props = o->propdefs.l;
	int new_size = (o->propdefs.max_length == 0
			? 8 : 2 * o->propdefs.max_length);

	o->propdefs.l = mymalloc(new_size * sizeof(Propdef), M_PROPDEF);
	for (i = 0; i < o->propdefs.max_length; i++)
	    o->propdefs.l[i] = old_props[i];
	o->propdefs.max_length = new_size;

	if (old_props)
	    myfree(old_props, M_PROPDEF);
    }
    o->propdefs.l[o->propdefs.cur_length++] = dbpriv_new_propdef(pname);

    pval.var = value;
    pval.owner = owner;
    pval.perms = flags;

    insert_prop_recursively(oid, o->propdefs.cur_length - 1, pval);

    return 1;
}

static void
rename_prop_recursively(Objid root, const char *old, const char *new)
{
    Objid c;
    Object *o = dbpriv_find_object(root);

    if (o->waif_propdefs)
	waif_rename_propdef(o, old, new);
    for (c = o->child; c != NOTHING; c = dbpriv_find_object(c)->sibling)
	rename_prop_recursively(c, old, new);
}

int
db_rename_propdef(Objid oid, const char *old, const char *new)
{
    Proplist *props = &dbpriv_find_object(oid)->propdefs;
    int hash = str_hash(old);
    int count = props->cur_length;
    int i;
    db_prop_handle h;

    for (i = 0; i < count; i++) {
	Propdef p;

	p = props->l[i];
	if (p.hash == hash && !mystrcasecmp(p.name, old)) {
	    if (mystrcasecmp(old, new) != 0) {	/* Not changing just the case */
		h = db_find_property(oid, new, 0);
		if (h.ptr
		|| property_defined_at_or_below(new, str_hash(new), oid))
		    return 0;
	    }
	    rename_prop_recursively(oid, props->l[i].name, new);
	    free_str(props->l[i].name);
	    props->l[i].name = str_ref(new);
	    props->l[i].hash = str_hash(new);

	    return 1;
	}
    }

    return 0;
}

static void
remove_prop(Objid oid, int pos)
{
    Pval *new_propval;
    Object *o;
    int i, nprops;

    o = dbpriv_find_object(oid);
    nprops = dbpriv_count_properties(oid);

    free_waif_propdefs(o->waif_propdefs);
    o->waif_propdefs = NULL;

    free_var(o->propval[pos].var);	/* free deleted property */

    if (nprops) {
	new_propval = mymalloc(nprops * sizeof(Pval), M_PVAL);
	for (i = 0; i < pos; i++)
	    new_propval[i] = o->propval[i];
	for (i = pos; i < nprops; i++)
	    new_propval[i] = o->propval[i + 1];
    } else
	new_propval = 0;

    if (o->propval)
	myfree(o->propval, M_PVAL);
    o->propval = new_propval;
}

static void
remove_prop_recursively(Objid root, int root_pos)
{
    Objid c;

    remove_prop(root, root_pos);
    for (c = dbpriv_find_object(root)->child;
	 c != NOTHING;
	 c = dbpriv_find_object(c)->sibling) {
	int new_prop_count = dbpriv_find_object(c)->propdefs.cur_length;

	remove_prop_recursively(c, new_prop_count + root_pos);
    }
}

int
db_delete_propdef(Objid oid, const char *pname)
{
    Proplist *props = &dbpriv_find_object(oid)->propdefs;
    int hash = str_hash(pname);
    int count = props->cur_length;
    int max = props->max_length;
    int i, j;

    for (i = 0; i < count; i++) {
	Propdef p;

	p = props->l[i];
	if (p.hash == hash && !mystrcasecmp(p.name, pname)) {
	    if (p.name)
		free_str(p.name);

	    if (max > 8 && props->cur_length <= ((max * 3) / 8)) {
		int new_size = max / 2;
		Propdef *new_props;

		new_props = mymalloc(new_size * sizeof(Propdef), M_PROPDEF);

		for (j = 0; j < i; j++)
		    new_props[j] = props->l[j];
		for (j = i + 1; j < count; j++)
		    new_props[j - 1] = props->l[j];

		myfree(props->l, M_PROPDEF);
		props->l = new_props;
		props->max_length = new_size;
	    } else
		for (j = i + 1; j < count; j++)
		    props->l[j - 1] = props->l[j];

	    props->cur_length--;
	    remove_prop_recursively(oid, i);

	    return 1;
	}
    }

    return 0;
}

int
db_count_propdefs(Objid oid)
{
    return dbpriv_find_object(oid)->propdefs.cur_length;
}

int
db_for_all_propdefs(Objid oid, int (*func) (void *, const char *), void *data)
{
    int i;
    Object *o = dbpriv_find_object(oid);
    int len = o->propdefs.cur_length;

    for (i = 0; i < len; i++)
	if (func(data, o->propdefs.l[i].name))
	    return 1;

    return 0;
}

struct contents_data {
    Var r;
    int i;
};

static int
add_to_list(void *data, Objid c)
{
    struct contents_data *d = data;

    d->i++;
    d->r.v.list[d->i].type = TYPE_OBJ;
    d->r.v.list[d->i].v.obj = c;

    return 0;
}

static void
get_bi_value(db_prop_handle h, Var * value)
{
    Objid oid = *((Objid *) h.ptr);

    switch (h.built_in) {
    case BP_NAME:
	value->type = TYPE_STR;
	value->v.str = str_ref(db_object_name(oid));
	break;
    case BP_OWNER:
	value->type = TYPE_OBJ;
	value->v.obj = db_object_owner(oid);
	break;
    case BP_PROGRAMMER:
	value->type = TYPE_INT;
	value->v.num = db_object_has_flag(oid, FLAG_PROGRAMMER);
	break;
    case BP_WIZARD:
	value->type = TYPE_INT;
	value->v.num = db_object_has_flag(oid, FLAG_WIZARD);
	break;
    case BP_R:
	value->type = TYPE_INT;
	value->v.num = db_object_has_flag(oid, FLAG_READ);
	break;
    case BP_W:
	value->type = TYPE_INT;
	value->v.num = db_object_has_flag(oid, FLAG_WRITE);
	break;
    case BP_F:
	value->type = TYPE_INT;
	value->v.num = db_object_has_flag(oid, FLAG_FERTILE);
	break;
    case BP_LOCATION:
	value->type = TYPE_OBJ;
	value->v.obj = db_object_location(oid);
	break;
    case BP_CONTENTS:
	{
	    struct contents_data d;

	    d.r = new_list(db_count_contents(oid));
	    d.i = 0;
	    db_for_all_contents(oid, add_to_list, &d);

	    *value = d.r;
	}
	break;
    default:
	panic("Unknown built-in property in GET_BI_VALUE!");
    }
}

db_prop_handle
db_find_property(Objid oid, const char *name, Var * value)
{
    static struct {
	const char *name;
	enum bi_prop prop;
	int hash;
    } ptable[] = {
	{
	    "name", BP_NAME, 0
	},
	{
	    "owner", BP_OWNER, 0
	},
	{
	    "programmer", BP_PROGRAMMER, 0
	},
	{
	    "wizard", BP_WIZARD, 0
	},
	{
	    "r", BP_R, 0
	},
	{
	    "w", BP_W, 0
	},
	{
	    "f", BP_F, 0
	},
	{
	    "location", BP_LOCATION, 0
	},
	{
	    "contents", BP_CONTENTS, 0
	}
    };
    static int ptable_init = 0;
    int i, n;
    db_prop_handle h;
    int hash = str_hash(name);
    Object *o;

    if (!ptable_init) {
	for (i = 0; i < Arraysize(ptable); i++)
	    ptable[i].hash = str_hash(ptable[i].name);
	ptable_init = 1;
    }
    for (i = 0; i < Arraysize(ptable); i++) {
	if (ptable[i].hash == hash && !mystrcasecmp(name, ptable[i].name)) {
	    static Objid ret;

	    ret = oid;
	    h.built_in = ptable[i].prop;
	    h.definer = NOTHING;
	    h.ptr = &ret;
	    if (value)
		get_bi_value(h, value);
	    return h;
	}
    }

    h.built_in = BP_NONE;
    n = 0;
    for (o = dbpriv_find_object(oid); o; o = dbpriv_find_object(o->parent)) {
	Proplist *props = &(o->propdefs);
	Propdef *defs = props->l;
	int length = props->cur_length;

	for (i = 0; i < length; i++, n++) {
	    if (defs[i].hash == hash
		&& !mystrcasecmp(defs[i].name, name)) {
		Pval *prop;

		h.definer = o->id;
		o = dbpriv_find_object(oid);
		prop = h.ptr = o->propval + n;

		if (value) {
		    while (prop->var.type == TYPE_CLEAR) {
			n -= o->propdefs.cur_length;
			o = dbpriv_find_object(o->parent);
			prop = o->propval + n;
		    }
		    *value = prop->var;
		}
		return h;
	    }
	}
    }

    h.ptr = 0;
    return h;
}

Var
db_property_value(db_prop_handle h)
{
    Var value;

    if (h.built_in)
	get_bi_value(h, &value);
    else {
	Pval *prop = h.ptr;

	value = prop->var;
    }

    return value;
}

void
db_set_property_value(db_prop_handle h, Var value)
{
    if (!h.built_in) {
	Pval *prop = h.ptr;

	free_var(prop->var);
	prop->var = value;
    } else {
	Objid oid = *((Objid *) h.ptr);
	db_object_flag flag;

	switch (h.built_in) {
	case BP_NAME:
	    if (value.type != TYPE_STR)
		goto complain;
	    db_set_object_name(oid, value.v.str);
	    break;
	case BP_OWNER:
	    if (value.type != TYPE_OBJ)
		goto complain;
	    db_set_object_owner(oid, value.v.obj);
	    break;
	case BP_PROGRAMMER:
	    flag = FLAG_PROGRAMMER;
	    goto finish_flag;
	case BP_WIZARD:
	    flag = FLAG_WIZARD;
	    goto finish_flag;
	case BP_R:
	    flag = FLAG_READ;
	    goto finish_flag;
	case BP_W:
	    flag = FLAG_WRITE;
	    goto finish_flag;
	case BP_F:
	    flag = FLAG_FERTILE;
	  finish_flag:
	    if (is_true(value))
		db_set_object_flag(oid, flag);
	    else
		db_clear_object_flag(oid, flag);
	    free_var(value);
	    break;
	case BP_LOCATION:
	case BP_CONTENTS:
	  complain:
	    panic("Inappropriate value in DB_SET_PROPERTY_VALUE!");
	    break;
	default:
	    panic("Unknown built-in property in DB_SET_PROPERTY_VALUE!");
	}
    }
}

Objid
db_property_owner(db_prop_handle h)
{
    if (h.built_in) {
	panic("Built-in property in DB_PROPERTY_OWNER!");
	return NOTHING;
    } else {
	Pval *prop = h.ptr;

	return prop->owner;
    }
}

void
db_set_property_owner(db_prop_handle h, Objid oid)
{
    if (h.built_in)
	panic("Built-in property in DB_SET_PROPERTY_OWNER!");
    else {
	Pval *prop = h.ptr;

	prop->owner = oid;
    }
}

unsigned
db_property_flags(db_prop_handle h)
{
    if (h.built_in) {
	panic("Built-in property in DB_PROPERTY_FLAGS!");
	return 0;
    } else {
	Pval *prop = h.ptr;

	return prop->perms;
    }
}

void
db_set_property_flags(db_prop_handle h, unsigned flags)
{
    if (h.built_in)
	panic("Built-in property in DB_SET_PROPERTY_FLAGS!");
    else {
	Pval *prop = h.ptr;

	prop->perms = flags;
    }
}

int
db_property_allows(db_prop_handle h, Objid progr, db_prop_flag flag)
{
    return ((db_property_flags(h) & flag)
	    || progr == db_property_owner(h)
	    || is_wizard(progr));
}

static void
fix_props(Objid oid, int parent_local, int old, int new, int common)
{
    Object *me = dbpriv_find_object(oid);
    Object *parent = dbpriv_find_object(me->parent);
    Pval *new_propval;
    int local = parent_local;
    int i;
    Objid c;

    /* This will invalidate waif_propdefs */
    free_waif_propdefs(me->waif_propdefs);
    me->waif_propdefs = NULL;

    local += me->propdefs.cur_length;

    for (i = local; i < local + old; i++)
	free_var(me->propval[i].var);

    if (local + new + common != 0) {
	new_propval = mymalloc((local + new + common) * sizeof(Pval), M_PVAL);
	for (i = 0; i < local; i++)
	    new_propval[i] = me->propval[i];
	for (i = 0; i < new; i++) {
	    Pval pv;

	    pv = parent->propval[parent_local + i];
	    new_propval[local + i] = pv;
	    new_propval[local + i].var.type = TYPE_CLEAR;
	    if (pv.perms & PF_CHOWN)
		new_propval[local + i].owner = me->owner;
	}
	for (i = 0; i < common; i++)
	    new_propval[local + new + i] = me->propval[local + old + i];
    } else
	new_propval = 0;

    if (me->propval)
	myfree(me->propval, M_PVAL);
    me->propval = new_propval;

    for (c = me->child; c != NOTHING; c = dbpriv_find_object(c)->sibling)
	fix_props(c, local, old, new, common);
}

int
dbpriv_check_properties_for_chparent(Objid oid, Objid new_parent)
{
    Object *o;
    int i;

    for (o = dbpriv_find_object(new_parent);
	 o;
	 o = dbpriv_find_object(o->parent)) {
	Proplist *props = &o->propdefs;

	for (i = 0; i < props->cur_length; i++)
	    if (property_defined_at_or_below(props->l[i].name,
					     props->l[i].hash,
					     oid))
		return 0;
    }

    return 1;
}

void
dbpriv_fix_properties_after_chparent(Objid oid, Objid old_parent)
{
    Objid o1, o2, common, new_parent;
    int common_props, old_props, new_props;

    /* Find the nearest common ancestor between old & new parent */
    new_parent = db_object_parent(oid);
    common = NOTHING;
    for (o1 = new_parent; o1 != NOTHING; o1 = db_object_parent(o1))
	for (o2 = old_parent; o2 != NOTHING; o2 = db_object_parent(o2))
	    if (o1 == o2) {
		common = o1;
		goto endouter;
	    }
  endouter:

    if (common != NOTHING)
	common_props = dbpriv_count_properties(common);
    else
	common_props = 0;

    old_props = dbpriv_count_properties(old_parent) - common_props;
    new_props = dbpriv_count_properties(new_parent) - common_props;

    fix_props(oid, 0, old_props, new_props, common_props);
}

char rcsid_db_properties[] = "$Id: db_properties.c,v 1.3 2007/09/12 07:33:29 spunky Exp $";

/* 
 * $Log: db_properties.c,v $
 * Revision 1.3  2007/09/12 07:33:29  spunky
 * This is a working version of the current HellMOO server
 *
 * Revision 1.3  1998/12/14 13:17:38  nop
 * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims
 *
 * Revision 1.2  1997/03/03 04:18:31  nop
 * GNU Indent normalization
 *
 * Revision 1.1.1.1  1997/03/03 03:44:59  nop
 * LambdaMOO 1.8.0p5
 *
 * Revision 2.6  1996/04/08  01:08:32  pavel
 * Fixed `db_rename_propdef()' to allow case-only changes.  Release 1.8.0p3.
 *
 * Revision 2.5  1996/02/11  00:46:48  pavel
 * Enhanced db_find_property() to report the defining object of the found
 * property.  Release 1.8.0beta2.
 *
 * Revision 2.4  1996/02/08  07:18:02  pavel
 * Renamed TYPE_NUM to TYPE_INT.  Updated copyright notice for 1996.
 * Release 1.8.0beta1.
 *
 * Revision 2.3  1995/12/31  03:27:40  pavel
 * Removed a few more uses of `unsigned'.  Reordered things in
 * db_delete_propdef() to fix an occasional memory smash.
 * Release 1.8.0alpha4.
 *
 * Revision 2.2  1995/12/28  00:41:34  pavel
 * Made *all* built-in property references return fresh value references.
 * Release 1.8.0alpha3.
 *
 * Revision 2.1  1995/12/11  07:52:27  pavel
 * Added support for renaming propdefs.
 *
 * Release 1.8.0alpha2.
 *
 * Revision 2.0  1995/11/30  04:21:13  pavel
 * New baseline version, corresponding to release 1.8.0alpha1.
 *
 * Revision 1.1  1995/11/30  04:21:02  pavel
 * Initial revision
 */
