#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "scr.h"
#include "code.h"
#include "debug.h"
#include "parse.h"
#include "pak.h"
#include "lex.h"

/* scr_parse_pak:

*/

script_t *scr_new(void)
{
	script_t *scr = NULL;

	scr = calloc(1, sizeof(script_t));
	if(!scr)
		error("Out of memory");

	return scr;
}

void scr_delete(script_t *scr)
{
	class_t *obj = NULL;
	int i=0;

	Assert(scr);

	for(i=0; i<PRIME; i++) {
		obj = scr->symtab[i];
		while(obj) {
			class_t *tmp = obj->next;
			scr_class_rem(scr, obj);
			warn("class_rem ok");
			obj = tmp;
		}
	}

	warn("free objs ok");

	free(scr);
}

script_t *scr_copy(script_t *scr)
{
	script_t *new_scr = NULL;
	int i=0;
	Assert(scr);

	new_scr = calloc(1, sizeof(script_t));
	if(!new_scr)
		error("Out of memory");

	*new_scr = *scr;

	new_scr->name = strdup(scr->name);
	if(!new_scr)
		error("Out of memory");

	new_scr->code = calloc(1, scr->code_len);
	if(!new_scr)
		error("Out of memory");

	memcpy(new_scr->code, scr->code, scr->code_len);


	for(i=0; i<PRIME; i++) {
		class_t *obj = scr->symtab[i];

		while(obj) {
			class_t *copy = scr_class_copy(obj);
			hash_insert((void **)new_scr->symtab, copy);
			obj = obj->next;
		}
	}

	return new_scr;
}

int scr_string_add(script_t *scr, char *str)
{
	int i = 0;

	Assert(scr);
	Assert(str);

	if(scr->string_count) {
		for(i=0; i<scr->string_count; i++) {
			if(strcmp(scr->stringtab[i], str) == 0) {
         	warn("got string: %s at %i", scr->stringtab[i], i);
				return i;
			}
		}
	}

	if(scr->string_size <= scr->string_count) {
		scr->stringtab = realloc(scr->stringtab, sizeof(char *) * (scr->string_size + 8));
		if(!scr->stringtab)
			error("Out of memory");

		memset(scr->stringtab+scr->string_size, 0, sizeof(char *) * 8);

		scr->string_size += 8;
	}

	scr->stringtab[scr->string_count] = strdup(str);
	if(!scr->stringtab[scr->string_count])
		error("Out of memory");

	warn("add string: '%s' at: %i", str, scr->string_count);

	scr->string_count++;

	return scr->string_count-1;
}

int scr_function_tie(script_t *scr, char *fname, func_ptr fptr)
{
	label_t *lab = NULL;
	Assert(scr);
	Assert(fname);
	Assert(fptr);

	if(hash_lookup((void **)scr->labeltab, fname))
		error("function '%s' already exists");

	lab = calloc(1, sizeof(label_t));
	if(!lab)
		error("Out of memory");

	lab->name = strdup(fname);
	lab->pos  = -1;

	lab->func = 1;
	lab->ptr = fptr;

	hash_insert((void **)scr->labeltab, (void *)lab);

	return 0;
}

char *scr_string_get(script_t *scr, int sindex)
{
	Assert(scr);
	Assert(sindex < scr->string_count);
	Assert(sindex >= 0);
	warn("geting string '%s' from %i", scr->stringtab[sindex], sindex);
	return scr->stringtab[sindex];
}

static void scr_expand_code_buf(script_t *scr, int expand)
{
	Assert(scr);
	Assert(expand >= 0);

	scr->code = realloc(scr->code, scr->code_len + expand);
	if(!scr->code)
		error("Out of memory");

	scr->code_len += expand;
}

void scr_emit_code(script_t *scr, int op, int typ1, void *arg1, int typ2, void *arg2)
{
	int ival = 0;
	char *sval = NULL;
	double dval = 0.0;

	Assert(scr);

	scr_expand_code_buf(scr, 1);
	scr->code[scr->code_pos++] = op;

	scr_expand_code_buf(scr, 1);
	scr->code[scr->code_pos++] = typ1;

	//warn("emit: typ1 = %i", typ1);

	switch(typ1) {
		case EMIT_INT:
			ival = *(int *)arg1;
			scr_expand_code_buf(scr, 4);
			scr->code[scr->code_pos++] = (char)ival;
			scr->code[scr->code_pos++] = ival >> 8;
			scr->code[scr->code_pos++] = ival >> 16;
			scr->code[scr->code_pos++] = ival >> 24;
			break;

		case EMIT_STR:
		case EMIT_IDENT:
		case EMIT_LABEL:
			sval = (char *)arg1;
			ival = scr_string_add(scr, sval);
			scr_expand_code_buf(scr, 4);
			scr->code[scr->code_pos++] = (char)ival;
			scr->code[scr->code_pos++] = ival >> 8;
			scr->code[scr->code_pos++] = ival >> 16;
			scr->code[scr->code_pos++] = ival >> 24;
			break;

		case EMIT_DOUBLE: /* BAD BAD BAD */
			dval = *(double *)arg1;
			ival = sizeof(double);
			scr_expand_code_buf(scr, ival);
			memcpy(scr->code+scr->code_pos, &dval, ival);
			scr->code_pos += ival;
			break;

		case EMIT_UNDEF:
		case EMIT_NONE:
			break;

		default:
			error("unknown arg1 type");
	}
/*
		case EMIT_INT:
			error("can't emit 'int' as arg1");
			break;

		case EMIT_STR:
			error("can't emit 'string' as arg1");
			break;

		case EMIT_DOUBLE:
			error("can't emit 'double' as arg1");
			break;

		case EMIT_IDENT:
		case EMIT_LABEL:
			sval = (char *)arg1;
			ival = scr_string_add(scr, sval);
			scr_expand_code_buf(scr, 4);
			scr->code[scr->code_pos++] = (char)ival;
			scr->code[scr->code_pos++] = ival >> 8;
			scr->code[scr->code_pos++] = ival >> 16;
			scr->code[scr->code_pos++] = ival >> 24;
			break;

		default:
			error("unknown arg1 type");
	}*/

	scr_expand_code_buf(scr, 1);
	scr->code[scr->code_pos++] = typ2;

	//warn("emit: typ2 = %i", typ2);

	switch(typ2) {
		case EMIT_INT:
			ival = *(int *)arg2;
			scr_expand_code_buf(scr, 4);
			scr->code[scr->code_pos++] = (char)ival;
			scr->code[scr->code_pos++] = ival >> 8;
			scr->code[scr->code_pos++] = ival >> 16;
			scr->code[scr->code_pos++] = ival >> 24;
			break;

		case EMIT_STR:
		case EMIT_IDENT:
		case EMIT_LABEL:
			sval = (char *)arg2;
			ival = scr_string_add(scr, sval);
			scr_expand_code_buf(scr, 4);
			scr->code[scr->code_pos++] = (char)ival;
			scr->code[scr->code_pos++] = ival >> 8;
			scr->code[scr->code_pos++] = ival >> 16;
			scr->code[scr->code_pos++] = ival >> 24;
			break;

		case EMIT_DOUBLE: /* BAD BAD BAD */
			dval = *(double *)arg2;
			ival = sizeof(double);
			scr_expand_code_buf(scr, ival);
			memcpy(scr->code+scr->code_pos, &dval, ival);
			scr->code_pos += ival;
			break;

		case EMIT_UNDEF:
		case EMIT_NONE:
			break;

		default:
			error("unknown arg2 type");
	}
}

/* end compiler api */

charsym_t tok_tbl[] = {
	{ "ABS",      TOK_ABS     },
	{ "ADD",		  TOK_ADD     },
	{ "CALL",     TOK_CALL    },
	{ "CMP",      TOK_CMP     },
	{ "COS",		  TOK_COS     },
	{ "COT",      TOK_COT     },
	{ "CSC",      TOK_CSC     },
	{ "DEF",      TOK_DEF     },
	{ "DELETE",	  TOK_DELETE  },
	{ "DIV",		  TOK_DIV     },
	{ "DONE",     TOK_DONE    },
	{ "FUNCTION", TOK_FUNC    },
	{ "JE",       TOK_JE      },
	{ "JG",       TOK_JG      },
	{ "JGE",      TOK_JGE     },
	{ "JL",       TOK_JL      },
	{ "JLE",      TOK_JLE     },
	{ "JMP",      TOK_JMP     },
	{ "JNE",      TOK_JNE     },
	{ "LABEL",    TOK_LABEL   },
	{ "MOD",		  TOK_MOD     },
	{ "MUL",	 	  TOK_MUL     },
	{ "NEW",		  TOK_NEW     },
	{ "POW",      TOK_POW     },
	{ "PRINT",    TOK_PRINT   },
	{ "RET",      TOK_RET     },
	{ "SEC",      TOK_SEC     },
	{ "SET",		  TOK_SET     },
	{ "SIN",		  TOK_SIN     },
	{ "SUB",		  TOK_SUB     },
	{ "TAN",		  TOK_TAN     },
	{ "UNDEF",	  TOK_UNDEF   },
//	{ '\0',		  0           },
};

static int bsearch_helper(const void *elm1, const void *elm2)
{
   const char *s1 = elm1;
   const charsym_t *s2 = elm2;

   return strcasecmp(s1,s2->sym);
}

static int resize_tok_str(script_t *scr, unsigned int len)
{
	Assert(scr);

   if(scr->token) free(scr->token);
   if(len<sizeof(tok_t)) len = sizeof(tok_t);
   scr->token = calloc(1,len);
   if(!scr->token) error("Insuficient memory");
   return 1;
}

#define eq(scr, c) ((c)==scr->cur_char)
#define fwd(scr) (scr->cur_char = scr->text[scr->text_pos++])

static int ffwd(script_t *scr)
{
	Assert(scr);

	if(scr->text_pos >= scr->text_len-1) {
		warn("got to end of text: pos:%i, len:%i", scr->text_pos, scr->text_len);
		scr->cur_char = EOF;
		return 0;
	}

	fwd(scr);
	if(eq(scr, '\n')) { scr->cur_line_num++; }
	else
	if(eq(scr, '#')) {
		while(!eq(scr, '\n') && !eq(scr, EOF) && scr->text_pos <= scr->text_len+1) { fwd(scr); }
		if(eq(scr, '\n')) scr->cur_line_num++;
		if(scr->text_pos >= scr->text_len-1) {
			scr->cur_char = EOF;
			return 0;
		}
	}


	return 1;
}

#undef fwd

int is_token(script_t *scr)
{
	charsym_t *kw = NULL;

	Assert(scr);

   kw = bsearch(scr->token->str, tok_tbl, TOK_MAX, sizeof(charsym_t), bsearch_helper);
   if(kw)
   	return kw->id;

   return TOK_IDENT;
}

static void skip_white(script_t *scr)
{
	Assert(scr);
	while(eq(scr, ' ') || eq(scr, '\t') || eq(scr, '\r') || eq(scr, '\n'))
		ffwd(scr);
}

static int get_str(script_t *scr)
{
	int cont=0, buf_len = 25;
	int end_pos = 0;
	char *buf = NULL;

	Assert(scr);

	buf = realloc(buf, buf_len);
	if(!buf)
		error("Out of memory");

	memset(buf, 0, buf_len);

	ffwd(scr);

	while((!eq(scr, '"') && !eq(scr, '\'')) || cont) {
      if(!cont && eq(scr, '\\')) { cont=1; scr->cur_char = scr->text[scr->text_pos++]; continue; }
      if(cont) {
			cont=0;
			if(eq(scr, 'n')) {
				scr->cur_char = '\n';
			} else
			if(eq(scr, 'r')) {
				scr->cur_char = '\r';
			} else
			if(eq(scr, 't')) {
				scr->cur_char = '\t';
			}
      }
      if(scr->cur_char==EOF)
			error("unterminated string");

		buf[end_pos++] = scr->cur_char;

		if(end_pos >= buf_len-1) {
			buf = realloc(buf, buf_len + 25);
			memset(buf+buf_len, 0, 25);
			buf_len += 25;
		}

      ffwd(scr);
	}

	if(eq(scr, '\'') || eq(scr, '"'))
		ffwd(scr);

	resize_tok_str(scr, end_pos+1);
	strcpy(scr->token->str, buf);

	free(buf);
	skip_white(scr);
	return 1;
}

static int get_ident(script_t *scr)
{
	int buf_len = 25;
	int end_pos = 0;
	char *buf = NULL;

	Assert(scr);

	buf = realloc(buf, buf_len);
	if(!buf)
		error("Out of memory");

	memset(buf, 0, buf_len);

	while(is_ident_char(scr->cur_char)) {
      if(scr->cur_char==EOF)
			break;

		buf[end_pos++] = scr->cur_char;

		if(end_pos >= buf_len-1) {
			buf = realloc(buf, buf_len + 25);
			memset(buf+buf_len, 0, 25);
			buf_len += 25;
		}

      ffwd(scr);
	}

	resize_tok_str(scr, end_pos+1);
	strcpy(scr->token->str, buf);

	free(buf);

	return 1;
}

static int get_number(script_t *scr)
{
	int buf_len = 25;
	int end_pos = 0;
	int neg=0, dbl=0, ret = 0;
	char *buf = NULL;

	Assert(scr);

	buf = realloc(buf, buf_len);
	if(!buf)
		error("Out of memory");

	memset(buf, 0, buf_len);

	if(eq(scr, '-')) {
		neg = 1;
		ffwd(scr);
	} else
	if(eq(scr, '+'))
		ffwd(scr);

	while(is_num_char(scr->cur_char) || eq(scr, '.')) {
      if(scr->cur_char==EOF)
			break;

		if(eq(scr, '.'))
			dbl = 1;

		buf[end_pos++] = scr->cur_char;

		if(end_pos >= buf_len-1) {
			buf = realloc(buf, buf_len + 25);
			memset(buf+buf_len, 0, 25);
			buf_len += 25;
		}

      ffwd(scr);
	}

	resize_tok_str(scr, sizeof(double));

	if(dbl) {
		if(neg)
			scr->token->d = -strtod(buf, NULL);
		else
			scr->token->d = strtod(buf, NULL);
		ret = TOK_DBL;
	} else {
		if(neg)
			scr->token->num = -strtol(buf, NULL, 10);
		else
			scr->token->num = strtol(buf, NULL, 10);
		ret = TOK_INT;
	}

	free(buf);

	return ret;
}

static int lex(script_t *scr)
{
	Assert(scr);

	skip_white(scr);

	if(eq(scr, '\'') || eq(scr, '"')) {
		get_str(scr);
		scr->ctok = TOK_STR;
	} else
	if(is_alpha(scr->cur_char)) {
		get_ident(scr);
		scr->ctok = is_token(scr);
	} else
	if(is_num_char(scr->cur_char) || eq(scr, '+') || eq(scr, '-')) {
		scr->ctok = get_number(scr);
	} else
	if(scr->cur_char == EOF) {
		scr->ctok = TOK_END;
		return scr->ctok;
	} else
	if(eq(scr, ';')) {
		scr->ctok = TOK_SEMI;
		ffwd(scr);
	} else
	if(eq(scr, ',')) {
		scr->ctok = TOK_COMMA;
		ffwd(scr);
	}
	skip_white(scr);

	scr->line_num = scr->cur_line_num;

	return scr->ctok;
}

int scr_parse(script_t *scr)
{
	char *tmp_str = NULL, tmp[9];
	int op = 0, func = 0;
	label_t *lab = NULL;
	Assert(scr);

	scr->cur_char = scr->text[scr->text_pos++];
	scr->line_num = 1;

	if(!scr->code_pos) {
		/* system setup code */
		scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack", EMIT_IDENT, "scrlib.stack");
	}

	lex(scr);

	do {

		if(tmp_str!=NULL) { free(tmp_str); tmp_str = NULL; }
		switch(scr->ctok) {

			/* special one arg ops */
			case TOK_RET:
			case TOK_DONE:
				scr_emit_code(scr, scr->ctok, EMIT_NONE, NULL, EMIT_NONE, NULL);
				break;

			/* label op */
			case TOK_FUNC:
				func = 1;
			case TOK_LABEL:
				strcpy(tmp, scr->token->str);
				lex(scr);
				if(scr->ctok != TOK_IDENT)
					parse_error(scr, "missing Identifier");

				if(hash_lookup((void **)scr->labeltab, scr->token->str))
					parse_error(scr, "function '%s' already exists", scr->token->str);

				lab = calloc(1, sizeof(label_t));
				if(!lab)
					error("Out of memory");

				lab->name = strdup(scr->token->str);
				lab->pos  = scr->code_pos;
				if(func)
					lab->func = 1;

				warn("%s: '%s'", tmp, lab->name);
				hash_insert((void **)scr->labeltab, (void *)lab);
				break;

			/* jump ops */
			case TOK_JE:
			case TOK_JNE:
			case TOK_JL:
			case TOK_JG:
			case TOK_JLE:
			case TOK_JGE:
			case TOK_JMP:
			case TOK_CALL:
				strcpy(tmp, scr->token->str);
				op = scr->ctok;
				lex(scr);
				if(scr->ctok != TOK_IDENT)
					parse_error(scr, "missing Identifier");

				if(strchr(scr->token->str, '.'))
					scr_emit_code(scr, op, EMIT_IDENT, scr->token->str, EMIT_NONE, NULL);
				else
					scr_emit_code(scr, op, EMIT_LABEL, scr->token->str, EMIT_NONE, NULL);

				parse_warn(scr, "emited a %s to '%s'", tmp, scr->token->str);
				break;

			/* single arg ops. */
			case TOK_NEW:
			case TOK_DELETE:
				op = scr->ctok;
				lex(scr);
				if(scr->ctok != TOK_IDENT)
					parse_error(scr, "missing Identifier");

				scr_emit_code(scr, op, EMIT_IDENT, scr->token->str, EMIT_NONE, NULL);
				break;

			/* print op */
			case TOK_PRINT:
				op = scr->ctok;
				lex(scr);
				if(scr->ctok == TOK_IDENT)
					scr_emit_code(scr, op, EMIT_IDENT, scr->token->str, EMIT_NONE, NULL);
				else
				if(scr->ctok == TOK_STR) {
					scr_emit_code(scr, op, EMIT_STR, scr->token->str, EMIT_NONE, NULL);
				}
				else
				if(scr->ctok == TOK_INT)
					scr_emit_code(scr, op, EMIT_INT, &scr->token->num, EMIT_NONE, NULL);
				else
				if(scr->ctok == TOK_DBL)
					scr_emit_code(scr, op, EMIT_DOUBLE, &scr->token->d, EMIT_NONE, NULL);
				else
				if(scr->ctok == TOK_UNDEF)
					scr_emit_code(scr, op, EMIT_UNDEF, NULL, EMIT_NONE, NULL);
				else
					parse_error(scr, "found unknown token, instead of an ident, string, int, double, or undef for arg1");
				break;

			/* two arg ops */
			case TOK_ADD:
			case TOK_SUB:
			case TOK_MUL:
			case TOK_DIV:
			case TOK_MOD:
			case TOK_SIN:
			case TOK_COS:
			case TOK_TAN:
			case TOK_COT:
			case TOK_CSC:
			case TOK_SEC:
			case TOK_POW:
			case TOK_ABS:
			case TOK_SET:
			case TOK_DEF:
			case TOK_CMP:
				op = scr->ctok;
				lex(scr);
				if(scr->ctok != TOK_IDENT)
					parse_error(scr, "missing Identifier for arg1");

				tmp_str = strdup(scr->token->str);
				if(!tmp_str)
					error("Out of memory");

				lex(scr);
				if(scr->ctok == TOK_IDENT)
					scr_emit_code(scr, op, EMIT_IDENT, tmp_str, EMIT_IDENT, scr->token->str);
				else
				if(scr->ctok == TOK_STR) {
					scr_emit_code(scr, op, EMIT_IDENT, tmp_str, EMIT_STR, scr->token->str);
				}
				else
				if(scr->ctok == TOK_INT)
					scr_emit_code(scr, op, EMIT_IDENT, tmp_str, EMIT_INT, &scr->token->num);
				else
				if(scr->ctok == TOK_DBL)
					scr_emit_code(scr, op, EMIT_IDENT, tmp_str, EMIT_DOUBLE, &scr->token->d);
				else
				if(scr->ctok == TOK_UNDEF)
					scr_emit_code(scr, op, EMIT_IDENT, tmp_str, EMIT_UNDEF, NULL);
				else
					parse_error(scr, "found unknown token [%i/%s], instead of an ident, string, int, double, or undef for arg2", scr->ctok, scr->token->str);
				free(tmp_str);
				tmp_str = NULL;
				break;

			case TOK_IDENT: {
				char *fnam = strdup(scr->token->str);
// use the 'system.stack' stack.

				do {
					lex(scr);
					warn("ctok: %i", scr->ctok);
					if(scr->ctok == TOK_IDENT) {
						scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack.push", EMIT_IDENT, scr->token->str);
						warn("arg ident");
					} else
					if(scr->ctok == TOK_STR) {
						scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack.push", EMIT_STR, scr->token->str);
						warn("arg str");
					}
					else
					if(scr->ctok == TOK_INT) {
						scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack.push", EMIT_INT, &scr->token->num);
						warn("arg int");
					} else
					if(scr->ctok == TOK_DBL) {
						scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack.push", EMIT_DOUBLE, &scr->token->d);
						warn("arg dbl");
					} else
					if(scr->ctok == TOK_UNDEF) {
						scr_emit_code(scr, TOK_SET, EMIT_IDENT, "system.stack.push", EMIT_UNDEF, NULL);
						warn("arg undef");
					} else
					if(scr->ctok == TOK_SEMI) {
						warn("semi");
						break;
					} else {
						parse_error(scr, "found unknown token [%i/%s], instead of an ident, string, int, double, or undef for extended function arg", scr->ctok, scr->token->str);
					}
					warn("ctok: %i", scr->ctok);
					lex(scr);
					warn("ctok: %i", scr->ctok);
				} while(scr->ctok == TOK_COMMA);

				if(scr->ctok != TOK_SEMI)
					parse_error(scr, "missing semi-colon after extended function call");

				if(strchr(fnam, '.'))
					scr_emit_code(scr, TOK_CALL, EMIT_IDENT, fnam, EMIT_NONE, NULL);
				else
					scr_emit_code(scr, TOK_CALL, EMIT_LABEL, fnam, EMIT_NONE, NULL);

				free(fnam);
			}	break;

			default:
				parse_error(scr, "unknown token: %s [%i]\n", scr->token->str, scr->ctok);
		}
	} while(lex(scr) != TOK_END);

	return 1;
}

int scr_parse_text(script_t *scr, char *name, char *data, int length)
{
	scr->text = realloc(scr->text, length);
	if(!scr->text)
		error("out of memory");

	memcpy(scr->text, data, length);

	scr->text_len = length;

	if(scr->name)
		free(scr->name);

	scr->name = strdup(name);
	if(!scr->name)
		error("out of memory");

	return scr_parse(scr);
}

int scr_parse_pak_item(script_t *scr, pak_t *pak, char *name)
{
	pak_item_t *item = NULL;
	pak_data_t *data = NULL;
	char *text = NULL;
	int text_len;
	Assert(name);

	item = pak_item_get(pak, name);
	if(!item) {
		warn("script '%s' doesn't exist", name);
		return -1;
	}

	data = pak_item_data_get(item);
	if(!data) {
		warn("script '%s' is empty", name);
		return -1;
	}

	text = data->data;
	text_len = data->length;

	scr->name = strdup(name);
	if(!scr->name)
		error("Out of memory");

	scr->text = text;
	scr->text_len = text_len;

	return scr_parse(scr);
}


unsigned char *scr_code(script_t *scr)
{
	Assert(scr);
	return scr->code;
}
