#include <string.h>
#include <assert.h>
#include "compile.h"

static t_node method;
static struct generator_def generator;


void compile                 (t_node node);
void compile_number          (t_node node);
void compile_string          (t_node node);
void compile_symbol          (t_node node);
void compile_name            (t_node node);
void compile_assign          (t_node node);
void compile_binary_operator (t_node node);
void compile_unary_operator  (t_node node);
void compile_call            (t_node node,t_node receiver);
void compile_closure         (t_node node);
void compile_method          (t_node node);
void compile_dot             (t_node node);
void compile_return          (t_node node);
void compile_if              (t_node node);
void compile_while           (t_node node);

/* ############################################################
   Generator
   ############################################################ */

void init_generator(void)
{
  static init = 0;
  if (init != 0)
    return;
  
  init = 1;
  generator.vector = (t_codevector)mymalloc(5000);
}

void gen_prepare(void)
{
  generator.last_command = 255;
  generator.length = 0;
  generator.count_constants = 0;
}

int gen_add_constant(T_OBJECT new)
{
  int i;

  for (i=0; i<generator.count_constants; ++i)
    if (generator.constants[i] == new)
      return i;

  generator.constants[generator.count_constants] = new;
  generator.count_constants += 1;

  return generator.count_constants - 1;
}

int gen_next_pos()
{
  return generator.length;
}

void gen_add_byte(t_byte byte)
{
  generator.vector[generator.length] = byte;
  generator.length += 1;
  /* fprintf(stderr,"Byte: %d\n",(int)byte); */
}

void gen_add_command(t_byte byte)
{
  gen_add_byte(byte);
  generator.last_command = byte;
}

void gen_add_int(int i)
{
  gen_add_byte(i%128);
  gen_add_byte(i/128);
}

void gen_add_short_int(int i)
{
  if (i<0)
    gen_add_byte(-((-i)%128));
  else
    gen_add_byte(i%128);
}

void gen_add_constant_command(int index)
{
  switch(index) {
  case 0 : gen_add_command(IP_PUSH_CONST_0); break;
  case 1 : gen_add_command(IP_PUSH_CONST_1); break;
  case 2 : gen_add_command(IP_PUSH_CONST_2); break;
  case 3 : gen_add_command(IP_PUSH_CONST_3); break;
  case 4 : gen_add_command(IP_PUSH_CONST_4); break;
  case 5 : gen_add_command(IP_PUSH_CONST_5); break;
  case 6 : gen_add_command(IP_PUSH_CONST_6); break;
  case 7 : gen_add_command(IP_PUSH_CONST_7); break;
  default:
    gen_add_command(IP_PUSH_CONST);
    gen_add_short_int(index);
  }
}


int gen_add_dummy()
{
  int pos = generator.length;

  gen_add_byte(0);

  return pos;
}

int gen_add_dummy_int()
{
  int pos = generator.length;

  gen_add_int(0);

  return pos;
}

void gen_set_dummy_byte(t_byte byte, int pos)
{
  generator.vector[pos] = byte;
}

void gen_set_dummy_int(int v, int pos)
{
  generator.vector[pos]   = v%128;
  generator.vector[pos+1] = v/128;
}

void gen_add_finally_return()
{
  if (generator.last_command != IP_RETURN) {
    gen_add_command(IP_RETURN);
  }
}

void gen_install_method(t_node m_node)
{
  T_OBJECT method;

  method = make_method(NODE_NAME(METHOD_CLASS(m_node)),
		       NODE_NAME(METHOD_NAME(m_node)),
		       generator.constants,
 		       generator.count_constants,
		       generator.vector,
		       generator.length,
		       NODE_FILL(METHOD_PARAMETERS(m_node)),
		       (NODE_TAG(METHOD_ERRORHANDLER(m_node)) == tag_eof)?
		                       NULL:
		                       NODE_NAME(METHOD_ERRORHANDLER(m_node)));

}

/* ############################################################
   Kompilieren
   ############################################################ */

void top_compile(t_node m_node)
{
  init_generator();
  gen_prepare();
  method = m_node;
  compile(m_node);
  gen_add_finally_return();

  /* write_byte_code(generator.length,generator.vector,NULL,generator.constants); */

  gen_install_method(m_node);
}

void compile_liste(t_node node)
{
  int i;

  for (i=0; i<NODE_FILL(node); ++i) {
    compile(NODE_ENTRY(node,i));
  }
}

void compile_seq(t_node node)
{
  int i;

  for (i=0; i<NODE_FILL(node); ++i) {
    compile(NODE_ENTRY(node,i));
    if ((i+1) < NODE_FILL(node)) {
      gen_add_command(IP_POP);
    }
  }
}

void compile(t_node node)
{
  if (node == NULL) {
    fprintf(stderr,"*** Warning: NULL in compile()\n");
    return;
  }

  switch(NODE_TAG(node)) {
  case tag_number :  compile_number(node);   break;
  case tag_string :  compile_string(node);   break;
  case tag_symbol :  compile_symbol(node);   break;
  case tag_name   :  compile_name(node);     break;
  case tag_assign :  compile_assign(node);   break;

  case tag_plus   :  case tag_minus      :  
  case tag_div    :  case tag_or         :  
  case tag_and    :  case tag_mul        :  
  case tag_less   :  case tag_less_eq    :  
  case tag_greater:  case tag_greater_eq :  
  case tag_equal  : 
                     compile_binary_operator(node); break;
  /*case tag_method_vector : break;*/
  case tag_if            : compile_if      (node); break;
  case tag_while         : compile_while   (node); break;
  case tag_return        : compile_return  (node); break;
  case tag_dot           : compile_dot     (node); break;
    /*case tag_call          : compile_call    (node); break;*/
  case tag_closure_vector: compile_closure (node); break;
  case tag_method_vector : compile_method  (node); break;
  default:
    fprintf(stderr,"Kann Token %s nicht compilieren\n",tag_as_string(NODE_TAG(node)));
  }
}

void compile_number(t_node node)
{
  NODE_INDEX(node) = gen_add_constant(make_number(NODE_NUMBER(node)));
  gen_add_constant_command(NODE_INDEX(node));
}

void compile_string(t_node node)
{
  NODE_INDEX(node) = gen_add_constant(make_string(NODE_NAME(node)));
  gen_add_constant_command(NODE_INDEX(node));
}

void compile_symbol(t_node node)
{
  NODE_INDEX(node) = gen_add_constant(make_symbol(NODE_NAME(node)));
  gen_add_constant_command(NODE_INDEX(node));
}

void compile_name(t_node node)
{
  switch (NODE_TYPE(node)) {
  case TYPE_PARAMETER :
  case TYPE_VAR :
  case 0:
    switch(NODE_INDEX(node)) {
    case  1: gen_add_command(IP_PUSH_TMP_1); break;
    case  2: gen_add_command(IP_PUSH_TMP_2); break;
    case  3: gen_add_command(IP_PUSH_TMP_3); break;
    case  4: gen_add_command(IP_PUSH_TMP_4); break;

    case  0: gen_add_command(IP_PUSH_ARG_0); break;
    case -1: gen_add_command(IP_PUSH_ARG_1); break;
    case -2: gen_add_command(IP_PUSH_ARG_2); break;
    case -3: gen_add_command(IP_PUSH_ARG_3); break;
    case -4: gen_add_command(IP_PUSH_ARG_4); break;
    default:
      gen_add_command(IP_PUSH_ARG);
      gen_add_short_int(NODE_INDEX(node));
    }
    break;

  case TYPE_GLOBAL :
    gen_add_command(IP_PUSH_GLOBAL);
    gen_add_short_int(gen_add_constant(make_symbol(NODE_NAME(node))));
    break;

  case TYPE_SLOT :
    switch(NODE_INDEX(node)) {
    case 0: gen_add_command(IP_PUSH_SLOT_0); break;
    case 1: gen_add_command(IP_PUSH_SLOT_1); break;
    case 2: gen_add_command(IP_PUSH_SLOT_2); break;
    case 3: gen_add_command(IP_PUSH_SLOT_3); break;
    case 4: gen_add_command(IP_PUSH_SLOT_4); break;
    case 5: gen_add_command(IP_PUSH_SLOT_5); break;
    case 6: gen_add_command(IP_PUSH_SLOT_6); break;
    default:
      gen_add_command(IP_PUSH_SLOT);
      gen_add_short_int(NODE_INDEX(node));
    }
    break;

  case TYPE_SPECIAL :
    if (!strcmp("self",TOKEN_NAME(NODE_TOKEN(node))) ||
	!strcmp("super",TOKEN_NAME(NODE_TOKEN(node)))  ) {
      gen_add_command(IP_PUSH_SELF);
      /*gen_add_command(IP_PUSH_ARG);
      gen_add_short_int(-(NODE_FILL(METHOD_PARAMETERS(method))));*/
      return;
    }
    if (!strcmp("true",TOKEN_NAME(NODE_TOKEN(node)))) {
      NODE_INDEX(node) = gen_add_constant(VALUE_T);
      gen_add_constant_command(NODE_INDEX(node));      
      return;
    }
    if (!strcmp("false",TOKEN_NAME(NODE_TOKEN(node)))) {
      NODE_INDEX(node) = gen_add_constant(VALUE_F);
      gen_add_constant_command(NODE_INDEX(node));      
      return;
    }
    if (!strcmp("nil",TOKEN_NAME(NODE_TOKEN(node)))) {
      NODE_INDEX(node) = gen_add_constant(VALUE_NIL);
      gen_add_constant_command(NODE_INDEX(node));      
      return;
    }
    break;

  default:
    fprintf(stderr,
	    "*** in compile_name(): Don't know, how to compile type %d\n",
	    NODE_TYPE(node));
    assert(1==2);
  }
}

void compile_method(t_node node)
{
  int args = NODE_FILL(METHOD_VARS(node));

  if (args > 0) {
    gen_add_command(IP_PUSH_ARGS);
    gen_add_short_int(args);
  }
  compile_seq(METHOD_STATEMENTS(node));
}

static int receiver_is_super(t_node receiver)
{
  return ((NODE_TAG(receiver) == tag_name)  &&
	  (!strcmp("super",NODE_NAME(receiver))));
}

void compile_call(t_node node, t_node receiver)
{
  char* name = TOKEN_NAME(NODE_TOKEN(CALL_NAME(node)));

  int index = primitive_name_index(name);
  /* printf("%d      %s\n",index,name); */
  compile_liste(CALL_ARGS(node));

  if (index < 0) {
    if (index < 128) {
      if (receiver_is_super(receiver)) {
	gen_add_command(IP_CALL_SUPER);
	gen_add_short_int(gen_add_constant(make_symbol(name)));
	gen_add_short_int(NODE_FILL(CALL_ARGS(node)));
      } 
      else {
	if (NODE_FILL(CALL_ARGS(node)) == 0) {
	  gen_add_command(IP_CALL_WITH_ZERO);
	  gen_add_short_int(gen_add_constant(make_symbol(name)));
 	} 
	else {
	  gen_add_command(IP_CALL);
	  gen_add_short_int(gen_add_constant(make_symbol(name)));
	  gen_add_short_int(NODE_FILL(CALL_ARGS(node)));
	}
      }
    }
    else {
      fprintf(stderr,"Mehr als 128 Konstanten in compile_call() !!!!!!!!\n");
    }
  } 
  else {
    if (index >= 128) {
      gen_add_command(IP_CALL_PRIM_LONG);
      gen_add_int(index);
    }
    else {
      gen_add_command(IP_CALL_PRIM);
      gen_add_short_int(index);
    }
    if (NODE_FILL(CALL_ARGS(node)) != primitive_mandatory_args(index)) {
      fprintf(stderr,"Argumentenanzahl bei %s stimmt nicht: Braucht %d, Aufruf mit %d\n",
	      name,
	      primitive_mandatory_args(index),
	      NODE_FILL(CALL_ARGS(node)));
    }
    
  }
}

void compile_dot(t_node node)
{
  compile(DOT_RECEIVER(node));
  compile_call(DOT_CALL(node),DOT_RECEIVER(node));
}

void compile_closure(t_node node)
{
  printf("Oho !!!!!\n");
  assert(1==2);
}

void compile_assign(t_node node)
{
  compile(ASSIGN_RIGHT(node));
  switch(NODE_TYPE(ASSIGN_LEFT(node))) {
  case TYPE_SLOT:
    gen_add_command(IP_SET_SLOT);
    gen_add_short_int(NODE_INDEX(ASSIGN_LEFT(node)));
    break;
  default:
    gen_add_command(IP_SET_ARG);
    gen_add_short_int(NODE_INDEX(ASSIGN_LEFT(node)));
  }
}


void compile_binary_operator(t_node node)
{
  compile(NODE_LEFT(node));
  compile(NODE_RIGHT(node));

  gen_add_command(IP_CALL);
  gen_add_short_int(gen_add_constant(make_symbol(binary_as_string(NODE_TOKEN(node)))));
  gen_add_short_int(1); /* 1, denn einer ist ja der Empfänger */
}

void compile_unary_operator(t_node node)
{
  compile(NODE_LEFT(node));
  gen_add_command(IP_CALL);
  gen_add_short_int(gen_add_constant(make_symbol(binary_as_string(NODE_TOKEN(node)))));
  gen_add_short_int(0);
}

void compile_return(t_node node)
{
  compile(NODE_LEFT(node));
  gen_add_command(IP_RETURN);
}

void compile_if(t_node node)
{
  int pos,pos2; 

  compile(IF_TEST(node));
  gen_add_command(IP_IF);
  pos = gen_add_dummy_int();

  gen_add_command(IP_POP);
  compile_seq(IF_THEN(node));
  gen_add_command(IP_GOTO_LONG_N);
  pos2 = gen_add_dummy_int();

  gen_set_dummy_int(gen_next_pos(),pos);
  gen_add_command(IP_POP);
  compile_seq(IF_ELSE(node));
  gen_set_dummy_int(gen_next_pos(),pos2);
}


void compile_while(t_node node)
{
  int start,ende;

  start = gen_next_pos();
  compile(WHILE_TEST(node));
  gen_add_command(IP_IF);
  ende = gen_add_dummy_int();

  gen_add_command(IP_POP);
  compile_seq(WHILE_BODY(node));
  gen_add_command(IP_POP);
  gen_add_command(IP_GOTO_LONG_N);
  gen_add_int(start);
  gen_set_dummy_int(gen_next_pos(),ende);
}


/*eof*/
