/*
 * Copyright (C) 2012-2015 Robin Haberkorn
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include 
#include "sciteco.h"
#include "error.h"
#include "expressions.h"
namespace SciTECO {
Expressions expressions;
tecoInt
Expressions::push(tecoInt number)
{
	while (operators.items() && operators.peek() == OP_NEW)
		pop_op();
	push(OP_NUMBER);
	if (num_sign < 0) {
		set_num_sign(1);
		number *= -1;
	}
	numbers.undo_pop();
	return numbers.push(number);
}
tecoInt
Expressions::pop_num(guint index)
{
	tecoInt n = 0;
	Operator op = pop_op();
	g_assert(op == OP_NUMBER);
	if (numbers.items()) {
		n = numbers.pop(index);
		numbers.undo_push(n, index);
	}
	return n;
}
tecoInt
Expressions::pop_num_calc(guint index, tecoInt imply)
{
	eval();
	if (num_sign < 0)
		set_num_sign(1);
	return args() > 0 ? pop_num(index) : imply;
}
tecoInt
Expressions::add_digit(gchar digit)
{
	tecoInt n = args() > 0 ? pop_num() : 0;
	return push(n*radix + (n < 0 ? -1 : 1)*(digit - '0'));
}
Expressions::Operator
Expressions::push(Expressions::Operator op)
{
	operators.undo_pop();
	return operators.push(op);
}
Expressions::Operator
Expressions::push_calc(Expressions::Operator op)
{
	gint first = first_op();
	/* calculate if op has lower precedence than op on stack */
	if (first >= 0 && operators.peek(first) <= op)
		calc();
	return push(op);
}
Expressions::Operator
Expressions::pop_op(guint index)
{
	Operator op = OP_NIL;
	if (operators.items()) {
		op = operators.pop(index);
		operators.undo_push(op, index);
	}
	return op;
}
void
Expressions::calc(void)
{
	tecoInt result;
	tecoInt vright;
	Operator op;
	tecoInt vleft;
	if (operators.peek() != OP_NUMBER)
		throw Error("Missing right operand");
	vright = pop_num();
	op = pop_op();
	if (operators.peek() != OP_NUMBER)
		throw Error("Missing left operand");
	vleft = pop_num();
	switch (op) {
	case OP_POW:
		for (result = 1; vright--; result *= vleft);
		break;
	case OP_MUL:
		result = vleft * vright;
		break;
	case OP_DIV:
		if (!vright)
			throw Error("Division by zero");
		result = vleft / vright;
		break;
	case OP_MOD:
		if (!vright)
			throw Error("Remainder of division by zero");
		result = vleft % vright;
		break;
	case OP_ADD:
		result = vleft + vright;
		break;
	case OP_SUB:
		result = vleft - vright;
		break;
	case OP_AND:
		result = vleft & vright;
		break;
	case OP_XOR:
		result = vleft ^ vright;
		break;
	case OP_OR:
		result = vleft | vright;
		break;
	default:
		/* shouldn't happen */
		g_assert_not_reached();
	}
	push(result);
}
void
Expressions::eval(bool pop_brace)
{
	for (;;) {
		gint n = first_op();
		Operator op;
		if (n < 0)
			break;
		op = operators.peek(n);
		if (op == OP_LOOP)
			break;
		if (op == OP_BRACE) {
			if (pop_brace)
				pop_op(n);
			break;
		}
		if (n < 1)
			break;
		calc();
	}
}
guint
Expressions::args(void)
{
	guint n = 0;
	guint items = operators.items();
	while (n < items && operators.peek(n) == OP_NUMBER)
		n++;
	return n;
}
gint
Expressions::find_op(Operator op)
{
	guint items = operators.items();
	for (guint i = 0; i < items; i++)
		if (operators.peek(i) == op)
			return i;
	return -1; /* not found */
}
gint
Expressions::first_op(void)
{
	guint items = operators.items();
	for (guint i = 0; i < items; i++) {
		switch (operators.peek(i)) {
		case OP_NUMBER:
		case OP_NEW:
			break;
		default:
			return i;
		}
	}
	return -1; /* no operator */
}
void
Expressions::discard_args(void)
{
	eval();
	for (guint i = args(); i; i--)
		pop_num_calc();
}
const gchar *
Expressions::format(tecoInt number)
{
	/* maximum length if radix = 2 */
	static gchar buf[1+sizeof(number)*8+1];
	gchar *p = buf + sizeof(buf);
	tecoInt v = ABS(number);
	*--p = '\0';
	do {
		*--p = '0' + (v % radix);
		if (*p > '9')
			*p += 'A' - '9' - 1;
	} while ((v /= radix));
	if (number < 0)
		*--p = '-';
	return p;
}
} /* namespace SciTECO */