#include "ast.h"
#include "verify.h"
#include <stdlib.h>
#define NUM_BUCKETS 253

#define dprintf

typedef struct bucket_elem_t bucket_elem;

struct bucket_elem_t {
  char *key;
  void *data;
  bucket_elem *next;
};

struct sym_data {
  ID_Type idt;
  BoundList *arraybounds;
  int type;
  int return_type;
  int constant;
  SubParams *params;
  LitExp *lit;
  int mylevel;

  struct sym_data *next;
};

typedef bucket_elem * bucket_list;
typedef bucket_list * hash_table;

hash_table tables[100];
struct sym_data *last[100];
struct sym_data *first[100];
int pseudos[100];
int curframe = -1;
int GLOBAL_lineno = 0;
char *GLOBAL_progname = NULL;
int GLOBAL_progstart = 0;

#define typemap_handle(x) case TYPE_ ## x ## : return #x ;

void semantic_prelude() {
  printf("SEMANTIC ERROR AT %d OF ?: ", GLOBAL_lineno);
}

void sym_mismatch(int sl, int el) {
  printf("SYMBOL NAME MISMATCH ERROR AT %d %d\n", sl, el);
  blankError();
}

char *typeMap(int type) {
  switch(type) {
    typemap_handle(int);
    typemap_handle(real);
    typemap_handle(bool);
    typemap_handle(string);
    typemap_handle(intA);
    typemap_handle(realA);
    typemap_handle(boolA);
    typemap_handle(stringA);
  }
  return "Undefined type.";
}

void Promote(Exp *e, int newtype) {
  printf("TYPE PROMOTION AT %d: ", GLOBAL_lineno);
  printf("%s ", typeMap(e->_type));
  printf("%s ", typeMap(newtype));
  jpdisp_Exp(e, "exp", 0);
  printf("\n");
}

void doError(char *s) {
  semantic_prelude();
  printf("%s!\n", s);
  blankError();
  exit(0);
}

void blankError() {
  while (curframe >= 0) {
    endFrame();
  }
  exit(0);
}

unsigned int hash (unsigned char *s)
{
  unsigned int crc = 0;
  int i;

  if (!s || !(*s)) return -1;

  for (i=0; i<strlen(s); i++) {

    crc  = (unsigned char)(crc >> 8) | (crc << 8);
    crc ^= s[i];
    crc ^= (unsigned char)(crc & 0xff) >> 4;
    crc ^= (crc << 8) << 4;
    crc ^= ((crc & 0xff) << 4) << 1;
  }

  return (crc % NUM_BUCKETS);
}

hash_table hash_create() { 
  return (hash_table) calloc(NUM_BUCKETS * sizeof(bucket_list), 1); 
}

void hash_insert (hash_table t, char *key, void *data) {
  bucket_list newhead; 

  newhead = (bucket_list) malloc(sizeof(bucket_elem));
  newhead->next = t[hash(key)]; 
  newhead->key = key;
  newhead->data = data; 
  t[hash(key)] = newhead;
}

int hash_find (hash_table t, char *key) { 
  bucket_list ptr; 

  ptr = t[hash(key)]; 

  while (ptr) { 
    if (strcmp(ptr->key, key) == 0) return 1;
    ptr = ptr->next;
  } 

  return 0;
}

void *hash_get (hash_table t, char *key) {
  bucket_list ptr; 

  ptr = t[hash(key)]; 
  
  while (ptr) { 
    if (strcmp(ptr->key, key) == 0) return ptr->data;
    ptr = ptr->next;
  } 
  
  return NULL;
}

int hash_delete (hash_table t, char *key) {
  bucket_list ptr; 
  
  ptr = t[hash(key)]; 

  if (!ptr->next) {
    if (strcmp(ptr->next->key, key) == 0) {
	t[hash(key)] = ptr->next;
	free(ptr);
    }
  }
  
  while (ptr && ptr->next) { 
    if (strcmp(ptr->next->key, key) == 0) {
	bucket_elem *tbd;

	tbd = ptr->next;

	ptr->next = ptr->next->next;

	free(tbd);
    }
  } 
  
  return NULL;
}

void check_collide(char *id, int nl) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    return;
  }

  if (sym->mylevel >= curframe) {
    printf("DUPLICATED SYMBOL ERROR AT %d, %d: %s\n", nl, sym->idt._lineno, id);
    blankError();
  }
}

void declareIDlist(IDList *ids, int type, BoundList *bounds) {
  IDList *cur = ids;

  while(cur) {
    struct sym_data *sym;

    sym = calloc(sizeof(struct sym_data), 1);

    memcpy(&sym->idt, &cur->idt, sizeof(ID_Type));
    sym->arraybounds = bounds;
    sym->constant = 0;
    sym->type = type;
    sym->next = NULL;
    sym->mylevel = curframe;

    check_collide(sym->idt.id, sym->idt._lineno);

    dprintf("Declaring ID [normal] %s...\n", sym->idt.id);

    hash_insert(tables[curframe], sym->idt.id, sym);

    if (last[curframe]) {
	last[curframe]->next = sym;
	last[curframe] = sym;
    } else {
	first[curframe] = sym;
	last[curframe] = sym;
    }
    
    cur = cur->next;
  }
}

void declareIDlist_const(IDList *ids, LitExp *lit) {
  IDList *cur = ids;

  while(cur) {
    struct sym_data *sym;

    sym = calloc(sizeof(struct sym_data), 1);

    memcpy(&sym->idt, &cur->idt, sizeof(ID_Type));
    sym->arraybounds = lit->_arraybounds;
    sym->constant = 1;
    sym->type = lit->_type;
    sym->lit = lit;
    sym->next = NULL;
    sym->mylevel = curframe;
    
    check_collide(sym->idt.id, sym->idt._lineno);

    dprintf("Declaring ID [constant] %s...\n", sym->idt.id);

    hash_insert(tables[curframe], sym->idt.id, sym);

    if (last[curframe]) {
	last[curframe]->next = sym;
	last[curframe] = sym;
    } else {
	first[curframe] = sym;
	last[curframe] = sym;
    }
    
    cur = cur->next;
  }
}

void declareSub(ID_Type *idtp, SubParams *params, int rt, BoundList *arraybounds) {
  struct sym_data *sym, *sym2;
  
  sym = calloc(sizeof(struct sym_data), 1);
  
  memcpy(&sym->idt, idtp, sizeof(ID_Type));
  sym->arraybounds = arraybounds;
  sym->type = TYPE_SUB;
  sym->return_type = rt;
  sym->params = params;
  sym->next = NULL;
  sym->mylevel = curframe;
  
  dprintf("Declaring sub %s...\n", sym->idt.id);

  check_collide(sym->idt.id, sym->idt._lineno);
  
  hash_insert(tables[curframe], sym->idt.id, sym);

  if (last[curframe]) {
    last[curframe]->next = sym;
    last[curframe] = sym;
  } else {
    first[curframe] = sym;
    last[curframe] = sym;
  }

  sym2 = calloc(sizeof(struct sym_data), 1);

  memcpy(sym2, sym, sizeof(struct sym_data));

  hash_insert(tables[curframe-1], sym2->idt.id, sym2);

  if (last[curframe-1]) {
    last[curframe-1]->next = sym2;
    last[curframe-1] = sym2;
  } else {
    first[curframe-1] = sym2;
    last[curframe-1] = sym2;
  }
}



void dupFrame() {
  dprintf("Dup frame...\n");
  memcpy(tables[curframe + 1], tables[curframe], NUM_BUCKETS * sizeof(bucket_list));
  curframe++;
  first[curframe] = NULL;
  last[curframe] = NULL;
  pseudos[curframe] = pseudos[curframe-1] + 1;
}

int getType(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get the type of a nonexistent symbol!: %s\n", id);
    blankError();
  }

  return sym->type;
}

void newFrame() {
  dprintf("New frame...\n");
  curframe++;
  memset(tables[curframe], 0, NUM_BUCKETS * sizeof(bucket_list));
  first[curframe] = NULL;
  last[curframe] = NULL;
  pseudos[curframe] = 0;
}

BoundList *getBounds(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get the bounds of a nonexistent symbol!: %s\n", id);
    blankError();
  }
  
  return sym->arraybounds;
}

int getIntValue(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get int value of a nonexistent symbol!.\n");
    blankError();
  }

  if (!sym->lit) {
    semantic_prelude();
    printf("Tried to get int value of non constant symbol!.\n");
    blankError();
  }

  if (sym->lit->_type != TYPE_int) {
    semantic_prelude();
    printf("Expected int value...\n");
    blankError();
  }
  
  return sym->lit->litInt;
}  

void endFrame() {
  struct sym_data *sym;

  dprintf("End frame %d, dumping:...\n", curframe);
  sym = first[curframe];

  while(sym) {
    int nt;

    printf("%s ", sym->idt.id);
    printf("%d ", sym->idt._lineno);
    if (sym->constant) {
	printf("constant ");
    } else if (sym->type == TYPE_SUB) {
	printf("subroutine ");
    } else {
	printf("variable ");
    }

    if (sym->type == TYPE_SUB) {
	nt = sym->return_type;
    } else {
	nt = sym->type;
    }
    
    if (nt >= 4) {
	BoundList *b;

	printf("%s", typeMap(nt - 4));
	printf("[");
	b = sym->arraybounds;

	while (b) {
	  printf("%d..%d", b->bound->lower_bound->Value, b->bound->upper_bound->Value);
	  b = b->next;
	  if (b) {
	    printf(", ");
	  }
	}
	printf("] ");
    } else {
	printf("%s ", typeMap(nt));
    }
    if (!sym->constant) {
	printf("%d\n", pseudos[curframe]);
    } else {
	printf("%d ", pseudos[curframe]);
	switch(sym->lit->_type) {
	case TYPE_string:
	  printf("\"%s\"", sym->lit->litStr);
	  break;
	case TYPE_int:
	  printf("%d", sym->lit->litInt);
	  break;
	case TYPE_real:
	  printf("%f", sym->lit->litReal);
	  break;
	}
	printf("\n");
    }
    sym = sym->next;
  }
  
  if (pseudos[curframe] > 0) {
    pseudos[curframe]--;
  } else {
    if (curframe > 0) {
	printf("SYMBOL TABLE DUMPED: %s AT %d\n", first[curframe]->idt.id, first[curframe]->idt._lineno);
    } else {
	printf("SYMBOL TABLE DUMPED: %s AT %d\n", GLOBAL_progname, GLOBAL_progstart);
    }
  }
  curframe--;
}

int getSubType(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get sub type of a nonexistent symbol!: %s.\n", id);
    blankError();
  }

  if (sym->type != TYPE_SUB) {
    semantic_prelude();
    printf("Tried to get sub type of non sub symbol!: %s.\n", id);
    blankError();
  }
  
  return sym->return_type;
}  

SubParams *getSubParams(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get sub type of a nonexistent symbol!: %s.\n", id);
    blankError();
  }

  if (sym->type != TYPE_SUB) {
    semantic_prelude();
    printf("Tried to get sub type of non sub symbol!: %s.\n", id);
    blankError();
  }
  
  return sym->params;
}

LitExp *getLitExp(char *id) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], id);

  if (!sym) {
    semantic_prelude();
    printf("Tried to get the lit exp of a nonexistent symbol!.\n");
    blankError();
  }

  if (!sym->lit) {
    semantic_prelude();
    printf("Tried to get int value of non constant symbol!.\n");
    blankError();
  }

  return sym->lit;
}

#define checkcase(x) case x: checkValidExp ## x ## (a, b, z); break;

void handle_logicexp(int op, Exp *a, Exp *b, LogicExp *z) {
  switch (op) {
    checkcase(_OR);
    checkcase(_AND);
  case _NOT:
    if (a->_type < 4) {
	/* not an array... */

	z->_type = TYPE_bool;

	if (a->_type != TYPE_bool) {
	  printf("Coerced a non-bool to a bool due to unary operator.\n");
	}
    }
    break;
  }
}

void handle_arithexp(int op, Exp *a, Exp *b, ArithExp *z) {
  switch (op) {
    checkcase(_PLUS);
    checkcase(_MINUS);
    checkcase(_MUL);
    checkcase(_DIV);
    checkcase(_REM);
  }
}

void handle_relexp(int op, Exp *x, Exp *y, RelExp *z) {
  if (x->_type == TYPE_int && y->_type == TYPE_real) {
    Promote(x, TYPE_real);
  } else if (x->_type == TYPE_real && y->_type == TYPE_int) {
    Promote(y, TYPE_real);
  } else if (x->_type == TYPE_real && y->_type == TYPE_real) {
    /* both real already... */
  } else if (x->_type == TYPE_int && y->_type == TYPE_int) {
    /* both int .. */
  } else if (x->_type == TYPE_string && y->_type == TYPE_string) {
    /* both strings */
  } else {
    doError("Relop used on incorrect arguments!");
    return;
  }
  z->_type = TYPE_bool;
}
  
void checkID(ID_Type *idtp, int type) {
  struct sym_data *sym;

  sym = hash_get(tables[curframe], idtp->id);

  if (!sym) {
    printf("UNDECLARED SYMBOL ERROR AT %d: %s\n", idtp->_lineno, idtp->id);
    blankError();
  }
  
  if (type == TYPE_ANY) return;

  if (type == TYPE_CONST) {
    if (!sym->constant) {
	semantic_prelude();
	printf("Identifier not const: %s\n", idtp->id);
	blankError();
    }
  }

  if (type == TYPE_MUTABLE) {
    if (sym->constant) {
	semantic_prelude();
	printf("Identifier const: %s\n", idtp->id);
	blankError();
    }
  }
}

void initVerify() {
  int i;

  curframe = -1;

  for (i=0; i<100; i++) {
    tables[i] = calloc( NUM_BUCKETS * sizeof(bucket_list), 1);
  }
  for (i=0; i<100; i++) {
    pseudos[i] = 0;
  }
}

void handle_cast(int type, Exp *expr) {
  Exp *e = (Exp *)calloc(sizeof(Exp), 1);
  Exp *t = (Exp *)calloc(sizeof(Exp), 1);
  
  e->_type = type;  
  checkValidExp_CONV(e, expr, t);
  free(e);
  free(t);
}

void handle_return(Exp *expr) {
  struct sym_data *sym;

  sym = first[curframe - pseudos[curframe]];

  if (!sym) {
    doError("Illegal return!");
    return;
  }

  if (sym->type != TYPE_SUB) {
    doError("Illegal return!");
    return;
  }
  
  handle_cast(sym->return_type, expr);
}
     





