/* radare - LGPL - Copyright 2013 - pancake */

#include <r_anal.h>
#include <r_types.h>

#define NARGS 64
typedef struct r_anal_esil_t {
	RAnal *anal;
	int rightside;
	int opsize;
	RList *stack;
	RList *opstack;
	int (*set)(struct r_anal_esil_t *c, const char *str, ut64 n);
	ut64 (*get)(struct r_anal_esil_t *c, const char *str);
	ut64 (*num)(struct r_anal_esil_t *c, const char *str);
	int (*iterate)(struct r_anal_esil_t *c, char *buf, int *tkns);
} RAnalEsil;

#if 0

mov ecx, 3      ecx=3
rep push ebx    esp-=4,[esp]=ebx,ecx--,@ecx
jb 0x804840     ?cf,eip=4[eip+1]
cmovc eax, 3    ?cf,eax=3
add eax, 44     cf=eax+44<eax,eax+=44
int 0x80        $0x80

#endif

static int token(char c) {
	switch (c) {
	case '$': // syscall
	case '@': // repeat if condition matches
	case '?': // conditional
	case '[': // store
	case ']': // store
	case '.':
		return 1;
	case ',':
		return 4;
	case '+':
	case '-':
	case '=':
	case '*':
	case '/':
	case '|':
	case '&':
	case '!':
	case '>':
	case '<':
		return 2;
	default:
		if ((c>='a' && c<='z')
			|| (c>='A' && c<='Z')
			|| (c>='0' && c<='9'))
			return 3;
	}
	return 0; // unknown
}

static int r_anal_esil (RAnalEsil *c, const char *str) {
	char buf[128];
	const char *chr = str;
	int t, bufi, tok, tknsi, tkns[128];
	bufi = tok = tknsi = 0;
	for (; *chr; chr++) {
		t = token (*chr);
		if (!t) {
			printf ("unknown!\n");
			return -1;
		}
		if (tok != t) {
			buf[bufi++] = 0;
			tkns[tknsi++] = t;
			tkns[tknsi++] = bufi;
			tok = t;
		}
		buf[bufi++] = *chr;
	}
	tkns[tknsi++] = 0;
	buf[bufi] = 0;

	return c->iterate (c, buf, tkns);
}

static int iterate (RAnalEsil *c, char *buf, int *tkns) {
	char *str;
	int i, type;
	for (i=0; tkns[i]; i+=2) {
		type = tkns[i];
		str = buf+tkns[i+1];
		eprintf ("(%d) (%s)\n", type, str);
		if (type==1) {
			if (!strcmp (str, "?")) {
				printf ("   CONDITIONAL\n");
			}
		}
	}
	return 0;
}

#define TOKEN_IS(x,y,z) (x[i]==y && !strcmp (x[i+1]==z))
#define TOKEN_GET(x,y) x=tkns[i]; y=buf+tkns[i+1]
#define IS(x) (!strcmp(x,op))

static int esil_set (RAnalEsil *e, const char *s, ut64 n) {
	if (e->anal && e->anal->reg) {
		RRegItem *item;
		item = r_reg_get (e->anal->reg, s, 0); // GPR only wtf?
		eprintf ("SET (%p)\n", item);
		if (item) return r_reg_set_value (e->anal->reg, item, n);
	}
	return R_TRUE;
}

static ut64 esil_get (RAnalEsil *e, const char *s) {
	RRegItem *item;
	// check for register
	if (!s) return 0LL;
	item = r_reg_get (e->anal->reg, s, 0); // GPR only wtf?
	if (item) return r_reg_get_value (e->anal->reg, item);
	return r_num_get (NULL, s);
}

#define OPUSH(x) r_list_push(c->opstack,x)
#define PUSH(x) r_list_push (c->stack,(void*)x)
#define OPOP() r_list_pop (c->opstack)
#define POP() r_list_pop (c->stack)

static int esil_commit (RAnalEsil *c, const char *op) {
	const char *q = POP();
	const char *p = POP();
	//const char *o = op;
	int ss = c->opsize;
	if (ss) {
//		eprintf (";; GET %d[%s]\n", ss, q);
//		eprintf ("PSUH %s %s\n", p, q);
eprintf (";; -> this means that we have to resolve before accessing memory %d\n", c->opsize);
		c->opsize = 0;
		PUSH (p);
		PUSH (q);
		return 0;
	}
	if (!op) {
		eprintf ("COMMIT UNKNOWN OP.. THIs IS []\n");
		return 0;
	}
	//eprintf (";;; COMMIT ;;; (%s) %s (%s)\n", p, o, q);
	if (IS ("[=")) {
		eprintf ("EQUAL------SET\n");
	} else
	if (IS ("+")) {
		// push (get (p)+get (q));
		ut64 n = esil_get (c, p) + esil_get (c, q);
		char *ns = malloc (32); // XXX memleak
		sprintf (ns, "0x%"PFMT64x, n);
		PUSH (ns);
		eprintf (";;; %s %s\n", p, q);
		//eprintf (" (((0x%llx)))\n", esil_get (c, p));
		eprintf (";;; +EQUAL! (%s)\n", ns);
	} else
	if (IS ("-")) {
		// push (get (p)+get (q));
		ut64 n = esil_get (c, p) - esil_get (c, q);
		char *ns = malloc (32); // XXX memleak
		sprintf (ns, "0x%"PFMT64x, n);
		PUSH (ns);
		eprintf (";;; %s %s\n", p, q);
		eprintf (";;; -EQUAL! (%s)\n", ns);
	} else
	if (IS ("*")) {
		// push (get (p)+get (q));
		ut64 n = esil_get (c, p) * esil_get (c, q);
		char *ns = malloc (32); // XXX memleak
		sprintf (ns, "0x%"PFMT64x, n);
		PUSH(ns);
		eprintf (";;; %s %s\n", p, q);
		eprintf (";;; *EQUAL! (%s)\n", ns);
	}
	if (IS ("=")) {
		if (p == NULL || q == NULL) {
			eprintf ("Invalid construction\n");
			return -1;
		}
		// set (p, get (q))
		c->set (c, p, c->get (c, q));
		eprintf (";;; EQUAL! (%s=%s)\n", p, q);
	}
	return 0;
}

static int emulate (RAnalEsil *c, char *buf, int *tkns) {
	ut64 num = 0;
	char *op = NULL;
	char *str;
	int i, type;
	c->opstack = r_list_new ();
	c->stack = r_list_new ();
	c->opsize = 0;
	c->rightside = 0;
	for (i=0; tkns[i]; i+=2) {
		TOKEN_GET (type, str);
		eprintf ("(%d) (%s)\n", type, str);
		switch (type) {
		// case 0 handled in for conditional
		case 1: /* special command */
			if (!strcmp (str, "[")) {
				int curstack = r_list_length (c->stack);
eprintf ("STACK POINTER %d\n", curstack);
				c->opsize = (int)num;
				// TODO: test for size
				// read tokens until ']'
				// TOKEN_UNTIL (1, "]");
				for (i+=2; tkns[i]; i+=2) {
					TOKEN_GET (type, str);
					eprintf ("--- %d (%s)\n", tkns[i], buf+tkns[i+1]);
					switch (tkns[i]) {
					case 1:
						if (!strcmp (str, "]")) {
							if (!c->opsize) c->opsize = 
								c->anal->bits==64?8:4;
							//int j, len = r_list_length (c->stack) - curstack;
							char *a;
							OPUSH (op);
							while ((a = OPOP ())) {
								// eprintf ("---> op %s\n", op);
								esil_commit (c, a);
							}
							//op = NULL;
							printf ("   %s (size %d)\n", c->rightside?"GET":"SET", (int)num);
							goto dungeon;
							// set destination for write
							// expect '='
						}
						break;
					case 2:
						op = str;
						OPUSH (op);
						break;
					case 3:
						PUSH (str);
						break;
					}
				}
				if (!tkns[i]) {
					printf ("Unexpected eof\n");
					return 1;
				}
			} else
			if (!strcmp (str, "?")) {
				printf ("   CONDITIONAL\n");
				i += 2;
				TOKEN_GET (type, str);
				if (!type) {
					eprintf ("   UNEXPECTED EOF\n");
					return 1;
				}
				if (type!=3) {
					printf ("   UNEXPECTED TOKEN\n");
					return 1;
				}
				//while () { i += 2; }
			}
			break;
		case 2:
			if (op) {
				//eprintf (" XXX Redefine op %s\n", op);
				if (!strcmp (op, "*")) { // prio
					esil_commit (c, op);
				} else OPUSH (op);
			}
			op = str;
			if (IS ("=")) {
				c->rightside = 1;
			}
			break;
		case 3:
			num = c->num (c, str); // 
	//		eprintf ("; push %s\n" , str);
			PUSH (str);
			break;
		case 4:
			esil_commit (c, op);
			op = NULL;
			break;
		}
		dungeon:
		{/*trick*/int/*label*/x/*parsing*/=/*fix*/0;}
	}
	eprintf (";;; COMMIT (%s) (%s)\n", op, str);
	esil_commit (c, op);
	if (r_list_length (c->opstack)>0) {
		char *a;
		while ((a = OPOP ())) {
			esil_commit (c, a);
		}
	}
	op = NULL;
	return 0;
}

static ut64 num(struct r_anal_esil_t *c, const char *str) {
	return r_num_get (NULL, str);
}

#define C(x) r_anal_esil(&c,x)

#ifdef MAIN
int main() {
	RAnalEsil c = {
		.num = num,
		.iterate = emulate //iterate
	};
	//C ("esp=32,eax++");
	C ("4[eax+3]=123");
	//C ("?cf,esp=32+2,eax++");
	//C ("cf=eax+44<eax,eax+=44");
	//C ("esp-=4,[esp]=ebx,ecx--,@ecx");
	return 0;
}
#endif

R_API int r_anal_esil_eval(RAnal *anal, const char *str) {
	RAnalEsil c = {
		.anal = anal,
		.get = esil_get,
		.set = esil_set,
		.num = num,
		.iterate = emulate //iterate
	};
	C (str);
	return 0;
}
