#include "config.h"
#include "object.h"
#include "instr.h"
#include "interp.h"
#include "interface.h"
#include "globals.h"
#include "cache.h"
#include "operproto.h"

#define FNS_HASH_SIZE 16

struct fns_hash_item {
  struct proto *parent;
  struct fns *func;
};

struct fns_hash_item fns_hash_table[FNS_HASH_SIZE];

void clear_fns_hash() {
  int loop;

  loop=0;
  while (loop<FNS_HASH_SIZE)
    fns_hash_table[loop++].parent=NULL;
}

struct fns *hash_find_fns(char *name, struct object *obj) {
  int index;

  index=obj->parent->proto_obj->refno%FNS_HASH_SIZE;
  if (fns_hash_table[index].parent==obj->parent)
    return fns_hash_table[index].func;
  fns_hash_table[index].parent=obj->parent;
  return fns_hash_table[index].func=find_fns(name,obj);
}

int s_iterate(struct object *caller, struct object *obj, struct object
              *player, struct var_stack **rts) {
  struct var *arglist,*old_locals;
  struct fns *tmp_fns;
  struct var_stack *arg_stack;
  long num_args,loop;
  struct var tmp;
  char *name;
  struct object *curr_obj,*avoid1,*avoid2;
  unsigned int old_num_locals;

  if (pop(&tmp,rts,obj)) return 1;
  if (tmp.type!=NUM_ARGS) {
    clear_var(&tmp);
    return 1;
  }
  if (tmp.value.num<4) return 1;
  num_args=tmp.value.num-4;
  if (num_args)
    arglist=MALLOC(sizeof(struct var)*num_args);
  else
    arglist=NULL;
  loop=0;
  while (loop<num_args) {
    if (pop(&tmp,rts,obj)) {
      while (loop>0)
        clear_var(&(arglist[--loop]));
      if (arglist) FREE(arglist);
      return 1;
    }
    arglist[loop].type=tmp.type;
    switch (tmp.type) {
      case INTEGER:
        arglist[loop].value.integer=tmp.value.integer;
        break;
      case STRING:
        arglist[loop].value.string=tmp.value.string;
        break;
      case OBJECT:
        arglist[loop].value.objptr=tmp.value.objptr;
        break;
      default:
        clear_var(&tmp);
        arglist[loop].type=INTEGER;
        arglist[loop].value.integer=0;
    }
    tmp.type=INTEGER;
    tmp.value.integer=0;
    loop++;
  }
  if (pop(&tmp,rts,obj)) {
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  if (tmp.type!=STRING) {
    clear_var(&tmp);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  name=tmp.value.string;
  if (pop(&tmp,rts,obj)) {
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  if (tmp.type==INTEGER && tmp.value.integer==0) {
    tmp.type=OBJECT;
    tmp.value.objptr=NULL;
  }
  if (tmp.type!=OBJECT) {
    clear_var(&tmp);
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  avoid1=tmp.value.objptr;
  if (pop(&tmp,rts,obj)) {
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  if (tmp.type==INTEGER && tmp.value.integer==0) {
    tmp.type=OBJECT;
    tmp.value.objptr=NULL;
  }
  if (tmp.type!=OBJECT) {
    clear_var(&tmp);
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  avoid2=tmp.value.objptr;
  if (pop(&tmp,rts,obj)) {
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  if (tmp.type==INTEGER && tmp.value.integer==0) {
    tmp.type=OBJECT;
    tmp.value.objptr=NULL;
  }
  if (tmp.type!=OBJECT) {
    clear_var(&tmp);
    FREE(name);
    loop=0;
    while (loop<num_args) clear_var(&(arglist[loop++]));
    if (arglist) FREE(arglist);
    return 1;
  }
  curr_obj=tmp.value.objptr;
  old_locals=locals;
  old_num_locals=num_locals;
  clear_fns_hash();
  while (curr_obj) {
    if (curr_obj!=avoid1 && curr_obj!=avoid2) {
      tmp_fns=hash_find_fns(name,curr_obj);
      if (tmp_fns) if (tmp_fns->is_static) tmp_fns=NULL;
      if (tmp_fns) {
        arg_stack=NULL;
        loop=num_args;
        while (loop-->0)
          push(&(arglist[loop]),&arg_stack);    
        tmp.type=NUM_ARGS;
        tmp.value.num=num_args;
        push(&tmp,&arg_stack);
        interp(obj,curr_obj,player,&arg_stack,tmp_fns);
        free_stack(&arg_stack);
      }
    }
    curr_obj=curr_obj->next_object;
  }
  locals=old_locals;
  num_locals=old_num_locals;
  loop=0;
  while (loop<num_args) clear_var(&(arglist[loop++]));
  if (arglist) FREE(arglist);
  tmp.type=INTEGER;
  tmp.value.integer=0;
  push(&tmp,rts);
  return 0;
}

int s_next_who(struct object *caller, struct object *obj, struct object
               *player, struct var_stack **rts) {
  struct var tmp;

  if (pop(&tmp,rts,obj)) return 1;
  if (tmp.type!=NUM_ARGS) {
    clear_var(&tmp);
    return 1;
  }
  if (tmp.value.num>1) return 1;
  if (tmp.value.num) {
    if (pop(&tmp,rts,obj)) return 1;
    if (tmp.type==INTEGER && tmp.value.integer==0) {
      tmp.type=OBJECT;
      tmp.value.objptr=NULL;
    }
    if (tmp.type!=OBJECT) {
      clear_var(&tmp);
      return 1;
    }
  } else {
    tmp.type=OBJECT;
    tmp.value.objptr=NULL;
  }
  tmp.value.objptr=next_who(tmp.value.objptr);
  if (!tmp.value.objptr) {
    tmp.type=INTEGER;
    tmp.value.integer=0;
  }
  push(&tmp,rts);
  return 0;
}