/* Copyright (c) 1993 Stephen F. White */

#include "cool.h"
#include "proto.h"
#include "servers.h"

#ifndef INLINE
void var_free (Var v)
{
  switch (v.type) {
  case NUM:
  case OBJ:
  case ERR:
  case PC:
    break;
  case LIST:
    list_free (v.v.list);
    break;
  case MAP:
    map_free (v.v.map);
    break;
  case STR:
    string_free (v.v.str);
    break;
  }
}

Var var_dup (Var v)
{
  switch (v.type) {
  case NUM:
  case OBJ:
  case ERR:
  case PC:
    break;
  case LIST:
    v.v.list = list_dup (v.v.list);
    break;
  case MAP:
    v.v.map = map_dup (v.v.map);
    break;
  case STR:
    v.v.str = string_dup (v.v.str);
    break;
  }
  return v;
}

#endif

int var_compare (Var v1, Var v2)
{
  switch (v1.type) {
  case NUM:
    return v1.v.num - v2.v.num;
  case STR:
    return strcasecmp (v1.v.str->str, v2.v.str->str);
  case OBJ:
	return v1.v.obj.id - v2.v.obj.id;
  case ERR:
  case LIST:
  case PC:
  case MAP:
    return -1;                  /* cannot compare objid, error, list or map values */
  }
  return -1;                    /* should never reach */
}

int var_eq (Var v1, Var v2)
{
  int i;
  MapPair *pair;
  Var to;

  if (v1.type != v2.type) {
    return 0;                   /* values of different types are unequal */
  }
  switch (v1.type) {
  case NUM:
  case PC:
    return v1.v.num == v2.v.num;
  case OBJ:
    return v1.v.obj.id == v2.v.obj.id && v1.v.obj.server == v2.v.obj.server;
  case ERR:
    return v1.v.err == v2.v.err;
  case STR:
    return !strcasecmp (v1.v.str->str, v2.v.str->str);
  case LIST:
    if (v1.v.list == v2.v.list) {       /* if they're the same list, */
      return 1;                 /* we're done */
    } else if (v1.v.list->len != v2.v.list->len) {
      return 0;
    }
    for (i = 0; i < v1.v.list->len; i++) {
      /* recursively compare all elements of list */
      if (!var_eq (v1.v.list->el[i], v2.v.list->el[i])) {
        return 0;
      }
    }
    return 1;
  case MAP:
    if (v1.v.map == v2.v.map) {
      return 1;
    } else if (v1.v.map->num != v2.v.map->num) {
      return 0;
    }
    for (i = 0; i < v1.v.map->size; i++) {
      for (pair = v1.v.map->table[i]; pair; pair = pair->next) {
        if (!map_find (v2.v.map, pair->from, &to)) {
          return 0;
        } else if (!var_eq (pair->to, to)) {
          return 0;
        }
      }
    }
    return 1;
  }
  return 0;
}

Var var_init (int type)
{
  Var v;

  switch (v.type = type) {
  case STR:
    v.v.str = string_new (1);
    break;
  case NUM:
  case PC:
    v.v.num = 0;
    break;
  case OBJ:
    v.v.obj.id = -1;
    v.v.obj.server = 0;
    break;
  case LIST:
    v.v.list = list_dup (empty_list);
    break;
  case MAP:
    v.v.map = map_new (0);
    break;
  case ERR:
    v.v.err = E_NONE;
    break;
  }
  return v;
}

int var_add_local (Method * m, int name, int *varno)
{
  Vardef *prev = 0, *v;

  for (v = m->vars, *varno = 0; v; v = v->next, (*varno)++) {
    prev = v;
    if (v->name == name) {
      return 1;
    }
  }
  v = MALLOC (Vardef, 1);
  v->name = name;
  v->value.type = NUM;
  v->value.v.num = 0;
  v->next = 0;
  if (prev) {
    prev->next = v;
  } else {
    m->vars = v;
  }
  return 0;
}

int var_add_global (Object * o, int name, Var init_value)
{
  Vardef *v;
  int hval, dummy;

  if (!o->vars) {
    o->vars = hash_new (HASH_INIT_SIZE);
  }
  hval = hash (sym_get (o, name)->str) % o->vars->size;
  if (var_find (o->vars->table[hval], name, &dummy)) {
    return 1;
  } else {
    v = MALLOC (Vardef, 1);
    v->name = name;
    v->value = init_value;
    v->next = o->vars->table[hval];
    o->vars->table[hval] = v;
    o->vars->num++;
    return 0;
  }
}

Error var_rm_global (Object * o, const char *name)
{
  Vardef *v, *prev = 0;
  int hval;

  if (!o->vars) {
    return E_VARNF;
  }
  hval = hash (name) % o->vars->size;
  for (v = o->vars->table[hval]; v; prev = v, v = v->next) {
    if (!strcasecmp (sym_get (o, v->name)->str, name)) {
      if (prev) {
        prev->next = v->next;
      } else {
        o->vars->table[hval] = v->next;
      }
      o->vars->num--;
      sym_free (o, v->name);
      var_free (v->value);
      FREE (v);
      return E_NONE;
    }
  }
  return E_VARNF;
}

Vardef *var_find (Vardef * top, int name, int *varno)
{
  Vardef *v;

  for (v = top, *varno = 0; v; v = v->next, (*varno)++) {
    if (v->name == name) {
      return v;
    }
  }
  return 0;
}

Vardef *var_find_local_by_name (Object * o, Method * m, const char *name,
  int namelen, int *varno)
{
  Vardef *v;
  const char *vname;

  for (v = m->vars, *varno = 0; v; v = v->next, (*varno)++) {
    vname = sym_get (o, v->name)->str;
    if (strlen (vname) == (unsigned int)namelen && !strncasecmp (vname, name, namelen)) {
      return v;
    }
  }
  return 0;
}

Vardef *var_find_local (Method * m, int varno)
{
  Vardef *v;

  for (v = m->vars; v && varno; v = v->next, varno--);
  return v;
}

void var_assign_local (Var * vars, int varno, Var value)
{
  var_free (vars[varno]);
  vars[varno] = value;
}

Error var_assign_global (Object * o, String * name, Var value)
{
  Vardef *v;
  Var oldvalue;
  int hval;

  if (var_get_global (o, name->str, &oldvalue) == E_NONE) {
    if (value.type != oldvalue.type) {
      var_free (value);
      return E_TYPE;
    }
  }

  if (o->vars) {
    hval = hash (name->str) % o->vars->size;
    for (v = o->vars->table[hval]; v; v = v->next) {
      if (!strcasecmp (sym_get (o, v->name)->str, name->str)) {
        var_free (v->value);
        v->value = value;
        cache_put (o, o->id.id);
        return E_NONE;
      }
    }
  }
  name->ref++;
  var_add_global (o, sym_add (o, name), value);
  cache_put (o, o->id.id);
  return E_NONE;
}

void var_get_local (Var * vars, int varno, Var * r)
{
  *r = vars[varno];
}

Error var_get_global (Object * o, const char *name, Var * r)
{
  Vardef *v;
  Object *p;
  int i, hval;

  if (o->vars) {
    hval = hash (name) % o->vars->size;
    for (v = o->vars->table[hval]; v; v = v->next) {
      if (!strcasecmp (name, sym_get (o, v->name)->str)) {
        *r = v->value;
        return E_NONE;
      }
    }
  }

/*
 * couldn't find locally, search on parents, recursively
 */
  for (i = 0; i < o->parents->len; i++) {
    if ((p = retrieve (o->parents->el[i].v.obj))) {
      if (var_get_global (p, name, r) == E_NONE) {
        return E_NONE;          /* found it */
      }
    }
  }
/*
 * no match
 */
  return E_VARNF;
}

String *var_tostring (String * st, Var v, int quotes)
{
  switch (v.type) {
  case NUM:
  case PC:
    st = string_catnum (st, v.v.num);
    break;
  case OBJ:
    st = string_catobj (st, v.v.obj, v.v.obj.server);
    break;
  case STR:
    if (quotes) {
      st = string_catc (st, '"');
    }
    st = string_backslash (st, v.v.str->str);
    if (quotes) {
      st = string_catc (st, '"');
    }
    break;
  case LIST:
    st = list_tostring (st, v.v.list);
    break;
  case MAP:
    st = map_tostring (st, v.v.map);
    break;
  case ERR:
    st = string_cat (st, err_id2desc (v.v.err));
    break;
  }
  return st;
}

String *list_tostring (String * st, List * l)
{
  int i;

  st = string_catc (st, '{');
  for (i = 0; i < l->len; i++) {
    st = var_tostring (st, l->el[i], 1);
    if (i < l->len - 1) {
      st = string_cat (st, ", ");
    }
  }
  st = string_catc (st, '}');
  return st;
}

String *map_tostring (String * st, Map * map)
{
  int i, num = 0;
  MapPair *pair;

  st = string_catc (st, '[');
  for (i = 0; i < map->size; i++) {
    for (pair = map->table[i]; pair; pair = pair->next) {
      st = var_tostring (st, pair->from, 1);
      st = string_cat (st, " => ");
      st = var_tostring (st, pair->to, 1);
      if (num < map->num - 1) {
        st = string_cat (st, ", ");
      }
      num++;
    }
  }
  st = string_catc (st, ']');
  return st;
}

int var_count_local (Method * m)
{
  int c;
  Vardef *v;

  for (v = m->vars, c = 0; v; v = v->next, c++);
  return c;
}

void var_init_local (Object * on, Method * m, Var * vars)
{
  int i;
  Vardef *v;

  for (v = m->vars, i = 0; v; v = v->next, i++) {
    vars[i] = var_dup (v->value);
  }
}

void var_copy_vars (Object * source, Object * dest)
{
  Vardef *v;
  String *varname;
  int hval;

  if (!source->vars) {
    return;
  }
  for (hval = 0; hval < source->vars->size; hval++) {
    for (v = source->vars->table[hval]; v; v = v->next) {
      varname = sym_get (source, v->name);
      (void) var_assign_global (dest, varname, var_dup (v->value));
    }
  }
}