/*
 * User object handling.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /home/kde/kdesupport/js/object.c,v $
 * $Id: object.c,v 1.1.1.1 1998/08/09 21:05:33 kulow Exp $
 */

#include "jsint.h"

/*
 * Types and definitions.
 */

#define HASH_SIZE 128

/*
 * Prototypes for static functions.
 */

static void hash_create (JSVirtualMachine *vm, JSObject *obj);

static void hash_insert (JSVirtualMachine *vm, JSObject *obj, const char *name,
			 unsigned int name_len, int pos);

static void hash_delete (JSVirtualMachine *vm, JSObject *obj, const char *name,
			 unsigned int name_len);

static int hash_lookup (JSObject *obj, char *name, unsigned int name_len);


/*
 * Global functions.
 */

JSObject *
js_vm_object_new (JSVirtualMachine *vm)
{
  JSObject *obj;

  obj = js_vm_alloc (vm, sizeof (*obj));
  obj->hash = NULL;
  obj->num_props = 0;
  obj->props = NULL;

  return obj;
}


void
js_vm_object_mark (JSObject *obj)
{
  int i;
  unsigned int num_objects;

  if (obj == NULL)
    return;

 tail_recursive:

  if (!js_vm_mark_ptr (obj))
    /* This object has already been marked.  Nothing to do here. */
    return;

  js_vm_mark_ptr (obj->props);

  /* Mark property hash. */
  if (obj->hash)
    {
      JSObjectPropHashBucket *b;
      int i;

      js_vm_mark_ptr (obj->hash);
      js_vm_mark_ptr (obj->hash_lengths);

      for (i = 0; i < HASH_SIZE; i++)
	for (b = obj->hash[i]; b; b = b->next)
	  {
	    js_vm_mark_ptr (b);
	    js_vm_mark_ptr (b->data);
	  }
    }

  /* Mark all non-object properties. */
  num_objects = 0;
  for (i = 0; i < obj->num_props; i++)
    {
      if (obj->props[i].value.type == JS_OBJECT)
	{
	  if (!js_vm_is_marked_ptr (obj->props[i].value.u.vobject))
	    num_objects++;
	}
      else
	js_vm_mark (&obj->props[i].value);
    }

  /* And finally, mark all objects we have left. */
  if (num_objects > 0)
    {
      /* Find the objects. */
      for (i = 0; i < obj->num_props; i++)
	if (obj->props[i].value.type == JS_OBJECT
	    && !js_vm_is_marked_ptr (obj->props[i].value.u.vobject))
	  {
	    if (num_objects == 1)
	      {
		/*
		 * Hahaa, this is the only non-marked object.  We can
		 * do a tail-recursion optimization.
		 */
		obj = obj->props[i].value.u.vobject;
		goto tail_recursive;
	      }

	    /* Just mark it. */
	    js_vm_mark (&obj->props[i].value);
	  }
    }
}


int
js_vm_object_load_property (JSVirtualMachine *vm, JSObject *obj,
			    JSSymbol prop, JSNode *value_return)
{
  unsigned int ui;
  JSSymbol link_sym = vm->syms.s___proto__;
  JSObject *link_obj = NULL;

follow_link:

  /* Check if we know this property. */
  for (ui = 0; ui < obj->num_props; ui++)
    if (obj->props[ui].name == prop)
      {
	JS_COPY (value_return, &obj->props[ui].value);
	return JS_PROPERTY_FOUND;
      }
    else if (obj->props[ui].name == link_sym
	     && obj->props[ui].value.type == JS_OBJECT)
      link_obj = obj->props[ui].value.u.vobject;

  /* Undefined so far. */
  if (link_obj)
    {
      /* Follow the link. */
      obj = link_obj;

      link_obj = NULL;
      goto follow_link;
    }

  /* Undefined.  Make it undef. */
  value_return->type = JS_UNDEFINED;
  return JS_PROPERTY_UNKNOWN;
}


void
js_vm_object_store_property (JSVirtualMachine *vm, JSObject *obj,
			     JSSymbol prop, JSNode *val)
{
  unsigned int ui;
  JSSymbol free_slot = JS_SYMBOL_NULL;

  /* Check if we already know this property. */
  for (ui = 0; ui < obj->num_props; ui++)
    if (obj->props[ui].name == prop)
      {
	JS_COPY (&obj->props[ui].value, val);
	return;
      }
    else if (obj->props[ui].name == JS_SYMBOL_NULL)
      free_slot = ui;

  /* Must create a new property. */

  if (free_slot == JS_SYMBOL_NULL)
    {
      /* Expand our array of properties. */
      obj->props = js_vm_realloc (vm, obj->props,
				  (obj->num_props + 1) * sizeof (JSProperty));
      free_slot = obj->num_props++;
    }

  obj->props[free_slot].name = prop;
  obj->props[free_slot].attributes = 0;
  JS_COPY (&obj->props[free_slot].value, val);

  /* Insert it to the hash (if the hash has been created). */
  if (obj->hash)
    {
      const char *name;

      name = js_vm_symname (vm, prop);
      hash_insert (vm, obj, name, strlen (name), free_slot);
    }
}


void
js_vm_object_delete_property (JSVirtualMachine *vm, JSObject *obj,
			      JSSymbol prop)
{
  unsigned int ui;

  /* Check if we already know this property. */
  for (ui = 0; ui < obj->num_props; ui++)
    if (obj->props[ui].name == prop)
      {
	/* Found, remove it from our list of properties. */
	obj->props[ui].name = JS_SYMBOL_NULL;
	obj->props[ui].value.type = JS_UNDEFINED;

	/* Remove its name from the hash (if present). */
	if (obj->hash)
	  {
	    const char *name = js_vm_symname (vm, prop);
	    hash_delete (vm, obj, name, strlen (name));
	  }

	/* All done here. */
	return;
      }
}


void
js_vm_object_load_array (JSVirtualMachine *vm, JSObject *obj, JSNode *sel,
			 JSNode *value_return)
{
  if (sel->type == JS_INTEGER)
    {
      if (sel->u.vinteger < 0 || sel->u.vinteger >= obj->num_props)
	value_return->type = JS_UNDEFINED;
      else
	JS_COPY (value_return, &obj->props[sel->u.vinteger].value);
    }
  else if (sel->type == JS_STRING)
    {
      int pos;

      if (obj->hash == NULL)
	hash_create (vm, obj);

      pos = hash_lookup (obj, (char *) sel->u.vstring->data,
			 sel->u.vstring->len);
      if (pos < 0)
	value_return->type = JS_UNDEFINED;
      else
	JS_COPY (value_return, &obj->props[pos].value);
    }
  else
    {
      sprintf (vm->error, "load_property: illegal array index");
      js_vm_error (vm);
    }
}


void
js_vm_object_store_array (JSVirtualMachine *vm, JSObject *obj, JSNode *sel,
			  JSNode *value)
{
  if (sel->type == JS_INTEGER)
    {
      if (sel->u.vinteger < 0)
	{
	  sprintf (vm->error, "store_array: array index can't be nagative");
	  js_vm_error (vm);
	}
      if (sel->u.vinteger >= obj->num_props)
	{
	  /* Expand properties. */
	  obj->props = js_vm_realloc (vm, obj->props,
				      (sel->u.vinteger + 1)
				      * sizeof (JSProperty));

	  /* Init the possible gap. */
	  for (; obj->num_props <= sel->u.vinteger; obj->num_props++)
	    {
	      obj->props[obj->num_props].name = 0;
	      obj->props[obj->num_props].attributes = 0;
	      obj->props[obj->num_props].value.type = JS_UNDEFINED;
	    }
	}

      JS_COPY (&obj->props[sel->u.vinteger].value, value);
    }
  else if (sel->type == JS_STRING)
    {
      int pos;

      if (obj->hash == NULL)
	hash_create (vm, obj);

      pos = hash_lookup (obj, (char *) sel->u.vstring->data,
			 sel->u.vstring->len);
      if (pos < 0)
	{
	  /* It is undefined, define it. */
	  obj->props = js_vm_realloc (vm, obj->props,
				      (obj->num_props + 1)
				      * sizeof (JSProperty));

	  /*
	   * XXX if <sel> is a valid symbol, intern it and set symbol's
	   * name below.
	   */
	  obj->props[obj->num_props].name = JS_SYMBOL_NULL;
	  obj->props[obj->num_props].attributes = 0;
	  JS_COPY (&obj->props[obj->num_props].value, value);

	  hash_insert (vm, obj, (char *) sel->u.vstring->data,
		       sel->u.vstring->len, obj->num_props);

	  obj->num_props++;
	}
      else
	JS_COPY (&obj->props[pos].value, value);
    }
}


void
js_vm_object_delete_array (JSVirtualMachine *vm, JSObject *obj, JSNode *sel)
{
  if (sel->type == JS_INTEGER)
    {
      if (0 <= sel->u.vinteger && sel->u.vinteger < obj->num_props)
	{
	  JSSymbol sym;

	  sym = obj->props[sel->u.vinteger].name;
	  obj->props[sel->u.vinteger].name = JS_SYMBOL_NULL;
	  obj->props[sel->u.vinteger].value.type = JS_UNDEFINED;

	  /* Remove its name from the hash (if present and it is not NULL). */
	  if (sym != JS_SYMBOL_NULL && obj->hash)
	    {
	      const char *name = js_vm_symname (vm, sym);
	      hash_delete (vm, obj, name, strlen (name));
	    }
	}
    }
  else if (sel->type == JS_STRING)
    {
      int pos;

      if (obj->hash == NULL)
	hash_create (vm, obj);

      pos = hash_lookup (obj, (char *) sel->u.vstring->data,
			 sel->u.vstring->len);
      if (pos >= 0)
	{
	  /* Found it. */
	  obj->props[pos].name = JS_SYMBOL_NULL;
	  obj->props[pos].value.type = JS_UNDEFINED;

	  /* And, delete its name from the hash. */
	  hash_delete (vm, obj, (char *) sel->u.vstring->data,
		       sel->u.vstring->len);
	}
    }
  else
    {
      sprintf (vm->error, "delete_array: illegal array index");
      js_vm_error (vm);
    }
}


int
js_vm_object_nth (JSVirtualMachine *vm, JSObject *obj, int nth,
		  JSNode *value_return)
{
  int i;
  JSObjectPropHashBucket *b;

  value_return->type = JS_UNDEFINED;

  if (nth < 0)
    return 0;

  if (obj->hash == NULL)
    hash_create (vm, obj);

  for (i = 0; i < HASH_SIZE && nth >= obj->hash_lengths[i]; i++)
    nth -= obj->hash_lengths[i];

  if (i >= HASH_SIZE)
    return 0;

  /* The chain <i> is the correct one. */
  for (b = obj->hash[i]; b && nth > 0; b = b->next, nth--)
    ;
  if (b == NULL)
    {
      fprintf (stderr,
	       "js_vm_object_nth(): chain didn't contain that many items\n");
      abort ();
    }

  js_vm_make_string (vm, value_return, b->data, b->len);

  return 1;
}


/*
 * Static functions.
 */

static void
hash_create (JSVirtualMachine *vm, JSObject *obj)
{
  int i;

  obj->hash = js_vm_alloc (vm, HASH_SIZE * sizeof (JSObjectPropHashBucket *));
  memset (obj->hash, 0, HASH_SIZE * sizeof (JSObjectPropHashBucket *));

  obj->hash_lengths = js_vm_alloc (vm, HASH_SIZE * sizeof (unsigned int));
  memset (obj->hash_lengths, 0, HASH_SIZE * sizeof (unsigned int));

  /* Insert all known properties to the hash. */
  for (i = 0; i < obj->num_props; i++)
    if (obj->props[i].name != JS_SYMBOL_NULL)
      {
	const char *name;

	name = js_vm_symname (vm, obj->props[i].name);
	hash_insert (vm, obj, name, strlen (name), i);
      }
}


static void
hash_insert (JSVirtualMachine *vm, JSObject *obj, const char *name,
	     unsigned int name_len, int pos)
{
  unsigned int hash;
  JSObjectPropHashBucket *b;

  hash = js_count_hash (name, name_len) % HASH_SIZE;
  for (b = obj->hash[hash]; b; b = b->next)
    if (b->len == name_len
	&& memcmp (b->data, name, name_len) == 0)
      {
	/* Ok, we already have a bucket */
	b->value = pos;
	return;
      }

  /* Create a new bucket. */
  b = js_vm_alloc (vm, sizeof (*b));
  b->len = name_len;
  b->data = js_vm_alloc (vm, b->len);
  memcpy (b->data, name, b->len);

  b->value = pos;

  b->next = obj->hash[hash];
  obj->hash[hash] = b;

  obj->hash_lengths[hash]++;
}


static void
hash_delete (JSVirtualMachine *vm, JSObject *obj, const char *name,
	     unsigned int name_len)
{
  unsigned int hash;
  JSObjectPropHashBucket *b, *prev;

  hash = js_count_hash (name, name_len) % HASH_SIZE;
  for (prev = NULL, b = obj->hash[hash]; b; prev = b, b = b->next)
    if (b->len == name_len
	&& memcmp (b->data, name, name_len) == 0)
      {
	/* Ok, found it. */
	if (prev)
	  prev->next = b->next;
	else
	  obj->hash[hash] = b->next;

	obj->hash_lengths[hash]--;

	break;
      }
}


static int
hash_lookup (JSObject *obj, char *name, unsigned int name_len)
{
  unsigned int hash;
  JSObjectPropHashBucket *b;

  hash = js_count_hash (name, name_len) % HASH_SIZE;
  for (b = obj->hash[hash]; b; b = b->next)
    if (b->len == name_len
	&& memcmp (b->data, name, name_len) == 0)
      return b->value;

  return -1;
}
