#include "otalk.h"
#include <assert.h>
#include "primitives.h"

T_OBJECT       call_method               (int args, T_OBJECT symbol, t_stackpointer stackpointer,int);
T_OBJECT       findMethod                (T_OBJECT symbol, T_OBJECT );
T_OBJECT       interpreter_error_handler (T_OBJECT , t_stackpointer ,T_OBJECT ,int);

#define VALUE(PC) ((*PC) + 128*(*(PC+1)))


T_OBJECT *method_hashtable;
T_OBJECT *method_hashtable_class;

int cache_hit=0;
int cache_miss=0;

extern struct primitive_def primitives[];

t_incarnation incarnations[1000];
t_incarnation *incarnation_level = incarnations;

#define PUSH_INCARNATION(M,SP,A) { incarnation_level->method = (M);         \
                                   incarnation_level->args = (A);           \
                                   incarnation_level->stackpointer = (SP);  \
				   ++incarnation_level;} 
#define POP_INCARNATION()      (--incarnation_level)
#define INCARNATION_SELF(IC)   ((IC)->stackpointer[-(IC)->args])

t_incarnation* find_errorhandler_level(t_incarnation* level)
{
  if (level == NULL)
    level = incarnation_level;
  --level;

  while (level >= incarnations) {
    if (O_METHOD_ERROR_HANDLER(level->method) != VALUE_NIL)
      return level;

    --level;
  }

  return NULL;
}

T_OBJECT find_errorhandler(t_incarnation* level)
{
  T_OBJECT result,v;
  t_incarnation* found = find_errorhandler_level(level);
  int i,args;

  if (found == NULL)
    return NULL;

  result = make_incarnation_object();
  args = found->args;
  INCARNATION_METHOD(result)   = found->method;
  INCARNATION_RECEIVER(result) = INCARNATION_SELF(found);
  INCARNATION_ARGS(result)     = (v = make_vector(args));

  for (i=0; i<args; ++i)
    VECTOR_ENTRY(v,i) = found->stackpointer[-found->args + i];

  return result;
}

void print_traceback(t_incarnation* level)
{
  if (level == NULL)
    level = incarnation_level;
  --level;

  while (level >= incarnations) {
    printf("a %s.%-40s (found in %s)\n",
	   STRING_CHARS(CLASS_NAME(CLASS(INCARNATION_SELF(level)))),
	   STRING_CHARS(O_METHOD_NAME(level->method)),
	   STRING_CHARS(CLASS_NAME(O_METHOD_OWNER(level->method))));
    --level;
  }
}

/* ----------------------------------------------------------------------
   interpreter_main_loop()
   ---------------------------------------------------------------------- */
T_OBJECT interpreter_main_loop(register t_stackpointer stackpointer, T_OBJECT method, long args)
{
  t_stackpointer original  = stackpointer;
  t_codevector codevector  = CODE_VECTOR(method);
  T_OBJECT*     constants  = CODE_CONSTANTS(method);
  t_byte* pc_start = PC_START(codevector);
  t_byte* pc = pc_start;
  int i;
  T_OBJECT self = original[-args];
  /*
  printf("Evaluate for %s Method %s:",
	 STRING_CHARS(CLASS_NAME(CLASS(*original))),
	 STRING_CHARS(O_METHOD_NAME(method))
	 );
  print_method(method);
  */
  PUSH_INCARNATION(method,stackpointer,args);

  while(1) {
    /*printf("Do %d\n",*pc);
     */
    switch(*pc) {

    /* --- IP_PUSH_SLOT Wert ------------------------------ */
    case IP_PUSH_SLOT :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,*pc));
      ++pc;
      break;

    /* --- IP_PUSH_SLOT_0 Wert ------------------------------ */
    case IP_PUSH_SLOT_0 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,0));
      break;

    /* --- IP_PUSH_SLOT_1 Wert ------------------------------ */
    case IP_PUSH_SLOT_1 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,1));
      break;

    /* --- IP_PUSH_SLOT_2 Wert ------------------------------ */
    case IP_PUSH_SLOT_2 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,2));
      break;

    /* --- IP_PUSH_SLOT_3 Wert ------------------------------ */
    case IP_PUSH_SLOT_3 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,3));
      break;

    /* --- IP_PUSH_SLOT_4 Wert ------------------------------ */
    case IP_PUSH_SLOT_4 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,4));
      break;

    /* --- IP_PUSH_SLOT_5 Wert ------------------------------ */
    case IP_PUSH_SLOT_5 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,5));
      break;

    /* --- IP_PUSH_SLOT_6 Wert ------------------------------ */
    case IP_PUSH_SLOT_6 :
      ++pc;
      STACK_PUSH(stackpointer,SLOT_N(self,6));
      break;

    /* --- IP_SET_SLOT Wert ------------------------------ */
    case IP_SET_SLOT :
      ++pc;
      SET_SLOT_N(self,*pc,*stackpointer);
      ++pc;
      break;

    /* --- IP_PUSH_ARGS Wert ------------------------------ */
    case IP_PUSH_ARGS :
      ++pc;
      for (i=0; i<*pc; ++i)
	STACK_PUSH(stackpointer,VALUE_NIL);
      ++pc;
      break;

    /* --- IP_PUSH_SELF ------------------------------ */
    case IP_PUSH_SELF :
      ++pc;
      STACK_PUSH(stackpointer,self);
      break;

    /* --- IP_PUSH_ARG Wert: =0: letztes Argument, -1 vorletztes Argument, >0 temp.Var. -- */
    case IP_PUSH_ARG :
      ++pc;
      STACK_PUSH(stackpointer,original[*pc]);
      ++pc;
      break;

    /* --- IP_PUSH_ARG_0 Wert: <=0: Parameter,self oder super,   >0: temp.Var. -- */
    case IP_PUSH_ARG_0 :
      ++pc;
      STACK_PUSH(stackpointer,original[0]);
      break;

    /* --- IP_PUSH_ARG_1 Wert: <=0: Parameter,self oder super,   >0: temp.Var. -- */
    case IP_PUSH_ARG_1 :
      ++pc;
      STACK_PUSH(stackpointer,original[-1]);
      break;

    /* --- IP_PUSH_ARG_2 Wert: <=0: Parameter,self oder super,   >0: temp.Var. -- */
    case IP_PUSH_ARG_2 :
      ++pc;
      STACK_PUSH(stackpointer,original[-2]);
      break;

    /* --- IP_PUSH_ARG_3 Wert: <=0: Parameter,self oder super,   >0: temp.Var. -- */
    case IP_PUSH_ARG_3 :
      ++pc;
      STACK_PUSH(stackpointer,original[-3]);
      break;

    /* --- IP_PUSH_ARG_4 Wert: <=0: Parameter,self oder super,   >0: temp.Var. -- */
    case IP_PUSH_ARG_4 :
      ++pc;
      STACK_PUSH(stackpointer,original[-4]);
      break;

    /* --- IP_PUSH_TMP_1 --- */
    case IP_PUSH_TMP_1:
      ++pc;
      STACK_PUSH(stackpointer,original[1]);
      break;

    /* --- IP_PUSH_TMP_2 --- */
    case IP_PUSH_TMP_2:
      ++pc;
      STACK_PUSH(stackpointer,original[2]);
      break;

    /* --- IP_PUSH_TMP_3 --- */
    case IP_PUSH_TMP_3:
      ++pc;
      STACK_PUSH(stackpointer,original[3]);
      break;

    /* --- IP_PUSH_TMP_4 --- */
    case IP_PUSH_TMP_4:
      ++pc;
      STACK_PUSH(stackpointer,original[4]);
      break;


    /* --- IP_SET_ARG Wert: <0: Parameter, >0: temp.Var. -- */
    case IP_SET_ARG :
      ++pc;
      original[*pc] = *stackpointer;
      ++pc;
      break;

    /* --- IP_PUSH_CONST Wert ------------------------------ */
    case IP_PUSH_CONST :
      ++pc;
      STACK_PUSH(stackpointer,constants[(*pc)]);
      ++pc;
      break;

    /* --- IP_PUSH_CONST_0 Wert ------------------------------ */
    case IP_PUSH_CONST_0 :
      ++pc;
      STACK_PUSH(stackpointer,constants[0]);
      break;

    /* --- IP_PUSH_CONST_1 Wert ------------------------------ */
    case IP_PUSH_CONST_1 :
      ++pc;
      STACK_PUSH(stackpointer,constants[1]);
      break;

    /* --- IP_PUSH_CONST_2 Wert ------------------------------ */
    case IP_PUSH_CONST_2 :
      ++pc;
      STACK_PUSH(stackpointer,constants[2]);
      break;

    /* --- IP_PUSH_CONST_3 Wert ------------------------------ */
    case IP_PUSH_CONST_3 :
      ++pc;
      STACK_PUSH(stackpointer,constants[3]);
      break;

    /* --- IP_PUSH_CONST_4 Wert ------------------------------ */
    case IP_PUSH_CONST_4 :
      ++pc;
      STACK_PUSH(stackpointer,constants[4]);
      break;

    /* --- IP_PUSH_CONST_5 Wert ------------------------------ */
    case IP_PUSH_CONST_5 :
      ++pc;
      STACK_PUSH(stackpointer,constants[5]);
      break;

    /* --- IP_PUSH_CONST_6 Wert ------------------------------ */
    case IP_PUSH_CONST_6 :
      ++pc;
      STACK_PUSH(stackpointer,constants[6]);
      break;

    /* --- IP_PUSH_CONST_7 Wert ------------------------------ */
    case IP_PUSH_CONST_7 :
      ++pc;
      STACK_PUSH(stackpointer,constants[7]);
      break;

    /* --- IP_PUSH_CONST_LONG Wert ------------------------------ */
    case IP_PUSH_CONST_LONG :
      ++pc;
      STACK_PUSH(stackpointer,constants[VALUE(pc)]);
      pc+=2;
      break;

    /* --- IP_PUSH_GLOBAL Wert ------------------------------ */
    case IP_PUSH_GLOBAL :
      ++pc;
      STACK_PUSH(stackpointer,global_value_for(constants[*pc]));
      ++pc;
      break;


    /* --- IP_POP_N Wert  ------------------------------ */
    case IP_POP_N :
      ++pc;
      STACK_POP(stackpointer,*pc);
      ++pc;
      break;

    /* --- IP_POP_N Wert  ------------------------------ */
    case IP_POP :
      ++pc;
      STACK_POP_1(stackpointer);
      break;

    /* --- IP_GOTO_N Wert  ------------------------------ */
    case IP_GOTO_N :
      ++pc;
      pc = pc_start + (*pc);
      break;

    /* --- IP_GOTO_LONG_N Wert  ------------------------------ */
    case IP_GOTO_LONG_N :
      ++pc;
      pc = pc_start + VALUE(pc);
      break;

    /* --- IP_GOTOREL_LONG_N Wert  ------------------------------ */
    case IP_GOTOREL_LONG_N :
      ++pc;
      pc = pc + VALUE(pc);
      break;

    /* --- IP_IF Wert  ------------------------------ */
    case IP_IF :
      ++pc;
      if (*stackpointer != VALUE_T) {
	pc = pc_start + VALUE(pc);
      }
      else {
	pc += 2;
      }
      /* STACK_POP_1(stackpointer); */
      break;

    /* --- IP_CALL Wert  ------------------------------ */
    case IP_CALL :
      {
	int args;

	++pc;
	args = *(pc+1);
	if (call_method(args,constants[*pc],stackpointer,0) == NULL) {
	  printf("*** Throw from %s\n",STRING_CHARS(O_METHOD_NAME(method)));
	  POP_INCARNATION();
	  return NULL;
	}
	stackpointer[-args] = *stackpointer;
	stackpointer -= args;
	pc+=2;
	break;
      }
    /* --- IP_CALL Wert  ------------------------------ */
    case IP_CALL_WITH_ZERO :
      {
	++pc;
	if (call_method(0,constants[*pc],stackpointer,0) == NULL) {
	  printf("*** Throw from %s\n",STRING_CHARS(O_METHOD_NAME(method)));
	  POP_INCARNATION();
	  return NULL;
	}
	pc+=1;
	break;
      }

    /* --- IP_CALL_SUPER Wert  ------------------------------ */
    case IP_CALL_SUPER :
      {
	int args;

	++pc;
	args = *(pc+1);
	if (call_method(args,constants[*pc],stackpointer,1) == NULL) {
	  printf("*** Throw from %s\n",STRING_CHARS(O_METHOD_NAME(method)));
	  POP_INCARNATION();
	  return NULL;
	}
	stackpointer[-args] = *stackpointer;
	stackpointer -= args;
	pc+=2;
	break;
      }

    /* --- IP_CALL_PRIM Wert  ------------------------------ */
    case IP_CALL_PRIM :
      {
	T_OBJECT result;
	++pc;
	result = (primitives[*pc].fun)(stackpointer);
	if (result == NULL) {
	  printf("*** Throw from %s\n",STRING_CHARS(O_METHOD_NAME(method)));
	  POP_INCARNATION();
	  return NULL;
	}
	STACK_POP(stackpointer,primitives[*pc].args);
	*stackpointer = result;
	++pc;
	break;
      }

    /* --- IP_CALL_PRIM_LONG Wert  ------------------------------ */
    case IP_CALL_PRIM_LONG :
      {
	T_OBJECT result;
	int i;

	++pc;
	i = VALUE(pc);
	result = (primitives[i].fun)(stackpointer);
	if (stackpointer == NULL) {
	  printf("*** Throw from %s\n",STRING_CHARS(O_METHOD_NAME(method)));
	  POP_INCARNATION();
	  return NULL;
	}
	STACK_POP(stackpointer,primitives[i].args);
	*stackpointer = result;
	pc+=2;
	break;
      }

    /* --- IP_RETURN  ------------------------------ */
    case IP_RETURN :
      {
	*original = *stackpointer;

	POP_INCARNATION();
	return *stackpointer;
      }

    /* --- default ------------------------------ */
    default:
      fprintf(stderr,"Unbekannter Operator in Stackmachine %d\n",(int)*pc);
      exit(9);
    }
  }

  POP_INCARNATION();
  return NULL;
}

T_OBJECT interpreter_error_handler(T_OBJECT receiver, t_stackpointer stackpointer,T_OBJECT method, int args)
{
  extern T_OBJECT throw_symbol;
  extern T_OBJECT throw_value;
  T_OBJECT vector;
  T_OBJECT throw_symbol_copy = throw_symbol;
  T_OBJECT throw_value_copy  = throw_value;
  T_OBJECT res;
  int i;

  t_stackpointer original = stackpointer;

  if (O_METHOD_ERROR_HANDLER(method) == VALUE_NIL)
    return NULL;

  throw_symbol = NULL;
  throw_value  = NULL;

  vector = make_vector(args);

  for (i=1; i<=args; ++i)
    SET_SLOT_N(vector,i-1,*(stackpointer+i));

  STACK_PUSH(stackpointer,receiver);
  STACK_PUSH(stackpointer,throw_symbol_copy == NULL?VALUE_NIL:throw_symbol_copy);
  STACK_PUSH(stackpointer,throw_value_copy  == NULL?VALUE_NIL:throw_value_copy);
  STACK_PUSH(stackpointer,vector);
  STACK_PUSH(stackpointer,method);

  res = call_method(4,O_METHOD_ERROR_HANDLER(method),stackpointer,0);
  if (res != NULL)
    *original = *stackpointer;
    
  return res;
}


extern int g_par_debug;
static int indent=0;

/* ----------------------------------------------------------------------
   call_method()
   ---------------------------------------------------------------------- */
T_OBJECT call_method(int args,T_OBJECT symbol, t_stackpointer stackpointer, int is_super)
{
  extern T_OBJECT g_top_object_class;
  T_OBJECT class;
  T_OBJECT orig_class;
  T_OBJECT res;
  T_OBJECT m;
  int i;
  long must_args;

  class = CLASS(*(stackpointer-args));
  if (is_super)
    class = CLASS_SUPER(class);

  orig_class = class;

  if (g_par_debug) {
    for (i=0; i<indent; ++i)
      printf(" ");
    printf("Methode %s on %s\n",STRING_CHARS(symbol),STRING_CHARS(CLASS_NAME(class)));
  }
  indent+=2;
  if (indent > 400) {
    fprintf(stderr,"\nPerhaps: Stack overflow\n");
    exit(10);
  }

  m = findMethod(symbol,class);
  if (m == NULL) {
    T_OBJECT vector = make_vector(args);
    T_OBJECT self = *(stackpointer-args);
    int i;

    for (i=0; i<args; ++i)
      SET_SLOT_N(vector,i,*(stackpointer+1+i-args));

    m = findMethod(doesNotUnderstand,class);
    STACK_PUSH(stackpointer,self);
    STACK_PUSH(stackpointer,symbol);
    STACK_PUSH(stackpointer,vector);
    res = interpreter_main_loop(stackpointer,m,2);
    indent-=2;
    return res;
  }
  
  if (args != (must_args = NUMBER(O_METHOD_N_ARGS(m)))) {
    printf("\n------------------------------------------------------------\n"
	   "*** Error Args Need: %d  Has: %d",NUMBER(O_METHOD_N_ARGS(m)),args);
    print_class(orig_class);
    printf("<%s>\n",STRING_CHARS(symbol));
    print_traceback(NULL);
    otalk_error("Parameteranzahl inkorrekt.\n");
  }
  res = interpreter_main_loop(stackpointer,m,args);
  indent-=2;
  return res;
}

#define DOUBLE_HASH_INDEX(CLASS,SYMBOL) (((((int)CLASS)>>4) ^ (((int)SYMBOL)>>4)) & \
					 (METHOD_HASHTABLE_LENGTH-1))

T_OBJECT findMethod(T_OBJECT symbol, register T_OBJECT class)
{
  T_OBJECT liste,m,orig_class=class;
  int hash_index,max,i;

  hash_index = DOUBLE_HASH_INDEX(symbol,class);
  if ((method_hashtable_class[hash_index] == class) &&
      ((m=method_hashtable[hash_index]) != NULL)    &&
      (O_METHOD_NAME(m) == symbol)) {
    ++cache_hit;
    return m;
  }
  /*
  printf("Miss %s in %s Index: %d, %p %p\n",
	 STRING_CHARS(symbol),STRING_CHARS(CLASS_NAME(class)),
	 hash_index,symbol,class
	 );
  */
  ++cache_miss;

  do {
    liste = CLASS_METHODS(class);
    max = NUMBER(VECTOR_FILL(liste));

    for (i=1; i <= max; ++i) {
      m=VECTOR_ENTRY(liste,i);
      if (O_METHOD_NAME(m) == symbol) {
	method_hashtable[hash_index] = m;
	method_hashtable_class[hash_index] = orig_class;
	return m;
      }
    }
    if (class == g_top_object_class) {
      return NULL;
    }
    class = CLASS_SUPER(class);
    assert(class!=NULL);
  } while(1);

  return NULL;
}
 
/*eof*/


