/*
 * Copyright (C) 2012-2022 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 "undo.h"
#include "expressions.h"
/*
 * Number and operator stacks are static, so
 * they can be passed to the undo token constructors.
 * This is OK since we're currently singleton.
 */
static GArray *teco_numbers;
TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_numbers, teco_int_t);
TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_numbers);
static GArray *teco_operators;
TECO_DEFINE_ARRAY_UNDO_INSERT_VAL(teco_operators, teco_operator_t);
TECO_DEFINE_ARRAY_UNDO_REMOVE_INDEX(teco_operators);
static gboolean teco_expressions_calc(GError **error);
static void __attribute__((constructor))
teco_expressions_init(void)
{
	teco_numbers = g_array_sized_new(FALSE, FALSE, sizeof(teco_int_t), 1024);
	teco_operators = g_array_sized_new(FALSE, FALSE, sizeof(teco_operator_t), 1024);
}
/** Get operator precedence */
static inline gint
teco_expressions_precedence(teco_operator_t op)
{
	return op >> 4;
}
gint teco_num_sign = 1;
gint teco_radix = 10;
void
teco_expressions_push_int(teco_int_t number)
{
	while (teco_operators->len > 0 && teco_expressions_peek_op(0) == TECO_OP_NEW)
		teco_expressions_pop_op(0);
	teco_expressions_push_op(TECO_OP_NUMBER);
	if (teco_num_sign < 0) {
		teco_set_num_sign(1);
		number *= -1;
	}
	g_array_append_val(teco_numbers, number);
	undo__remove_index__teco_numbers(teco_numbers->len-1);
}
teco_int_t
teco_expressions_peek_num(guint index)
{
	return g_array_index(teco_numbers, teco_int_t, teco_numbers->len - 1 - index);
}
teco_int_t
teco_expressions_pop_num(guint index)
{
	teco_int_t n = 0;
	teco_operator_t op = teco_expressions_pop_op(0);
	g_assert(op == TECO_OP_NUMBER);
	if (teco_numbers->len > 0) {
		n = teco_expressions_peek_num(index);
		undo__insert_val__teco_numbers(teco_numbers->len - 1 - index, n);
		g_array_remove_index(teco_numbers, teco_numbers->len - 1 - index);
	}
	return n;
}
gboolean
teco_expressions_pop_num_calc(teco_int_t *ret, teco_int_t imply, GError **error)
{
	if (!teco_expressions_eval(FALSE, error))
		return FALSE;
	if (teco_num_sign < 0)
		teco_set_num_sign(1);
	teco_int_t v = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : imply;
	if (ret)
		*ret = v;
	return TRUE;
}
void
teco_expressions_add_digit(gchar digit)
{
	teco_int_t n = teco_expressions_args() > 0 ? teco_expressions_pop_num(0) : 0;
	teco_expressions_push(n*teco_radix + (n < 0 ? -1 : 1)*(digit - '0'));
}
void
teco_expressions_push_op(teco_operator_t op)
{
	g_array_append_val(teco_operators, op);
	undo__remove_index__teco_operators(teco_operators->len-1);
}
gboolean
teco_expressions_push_calc(teco_operator_t op, GError **error)
{
	gint first = teco_expressions_first_op();
	/* calculate if op has lower precedence than op on stack */
	if (first >= 0 &&
	    teco_expressions_precedence(op) <= teco_expressions_precedence(teco_expressions_peek_op(first)) &&
	    !teco_expressions_calc(error))
		return FALSE;
	teco_expressions_push_op(op);
	return TRUE;
}
teco_operator_t
teco_expressions_peek_op(guint index)
{
	return g_array_index(teco_operators, teco_operator_t, teco_operators->len - 1 - index);
}
teco_operator_t
teco_expressions_pop_op(guint index)
{
	teco_operator_t op = TECO_OP_NIL;
	if (teco_operators->len > 0) {
		op = teco_expressions_peek_op(index);
		undo__insert_val__teco_operators(teco_operators->len - 1 - index, op);
		g_array_remove_index(teco_operators, teco_operators->len - 1 - index);
	}
	return op;
}
static gboolean
teco_expressions_calc(GError **error)
{
	teco_int_t result;
	if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) {
		g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
		                    "Missing right operand");
		return FALSE;
	}
	teco_int_t vright = teco_expressions_pop_num(0);
	teco_operator_t op = teco_expressions_pop_op(0);
	if (!teco_operators->len || teco_expressions_peek_op(0) != TECO_OP_NUMBER) {
		g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
		                    "Missing left operand");
		return FALSE;
	}
	teco_int_t vleft = teco_expressions_pop_num(0);
	switch (op) {
	case TECO_OP_POW:
		for (result = 1; vright--; result *= vleft);
		break;
	case TECO_OP_MUL:
		result = vleft * vright;
		break;
	case TECO_OP_DIV:
		if (!vright) {
			g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
			                    "Division by zero");
			return FALSE;
		}
		result = vleft / vright;
		break;
	case TECO_OP_MOD:
		if (!vright) {
			g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
			                    "Remainder of division by zero");
			return FALSE;
		}
		result = vleft % vright;
		break;
	case TECO_OP_ADD:
		result = vleft + vright;
		break;
	case TECO_OP_SUB:
		result = vleft - vright;
		break;
	case TECO_OP_AND:
		result = vleft & vright;
		break;
	case TECO_OP_XOR:
		result = vleft ^ vright;
		break;
	case TECO_OP_OR:
		result = vleft | vright;
		break;
	default:
		/* shouldn't happen */
		g_assert_not_reached();
	}
	teco_expressions_push(result);
	return TRUE;
}
gboolean
teco_expressions_eval(gboolean pop_brace, GError **error)
{
	for (;;) {
		gint n = teco_expressions_first_op();
		if (n < 0)
			break;
		teco_operator_t op = teco_expressions_peek_op(n);
		if (op == TECO_OP_BRACE) {
			if (pop_brace)
				teco_expressions_pop_op(n);
			break;
		}
		if (n < 1)
			break;
		if (!teco_expressions_calc(error))
			return FALSE;
	}
	return TRUE;
}
guint
teco_expressions_args(void)
{
	guint n = 0;
	while (n < teco_operators->len && teco_expressions_peek_op(n) == TECO_OP_NUMBER)
		n++;
	return n;
}
gint
teco_expressions_first_op(void)
{
	for (guint i = 0; i < teco_operators->len; i++) {
		switch (teco_expressions_peek_op(i)) {
		case TECO_OP_NUMBER:
		case TECO_OP_NEW:
			break;
		default:
			return i;
		}
	}
	return -1; /* no operator */
}
gboolean
teco_expressions_discard_args(GError **error)
{
	if (!teco_expressions_eval(FALSE, error))
		return FALSE;
	for (guint i = teco_expressions_args(); i; i--)
		if (!teco_expressions_pop_num_calc(NULL, 0, error))
			return FALSE;
	return TRUE;
}
/** The nesting level of braces */
guint teco_brace_level = 0;
void
teco_expressions_brace_open(void)
{
	teco_expressions_push_op(TECO_OP_BRACE);
	teco_undo_guint(teco_brace_level)++;
}
gboolean
teco_expressions_brace_return(guint keep_braces, guint args, GError **error)
{
	/*
	 * FIXME: Allocating on the stack might be dangerous.
	 */
	teco_int_t return_numbers[args];
	for (guint i = args; i; i--)
		return_numbers[i-1] = teco_expressions_pop_num(0);
	teco_undo_guint(teco_brace_level);
	while (teco_brace_level > keep_braces) {
		if (!teco_expressions_discard_args(error) ||
		    !teco_expressions_eval(TRUE, error))
			return FALSE;
		teco_brace_level--;
	}
	for (guint i = 0; i < args; i++)
		teco_expressions_push(return_numbers[i]);
	return TRUE;
}
gboolean
teco_expressions_brace_close(GError **error)
{
	if (!teco_brace_level) {
		g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
		                    "Missing opening brace");
		return FALSE;
	}
	teco_undo_guint(teco_brace_level)--;
	return teco_expressions_eval(TRUE, error);
}
void
teco_expressions_clear(void)
{
	g_array_set_size(teco_numbers, 0);
	g_array_set_size(teco_operators, 0);
	teco_brace_level = 0;
}
/**
 * Format a TECO integer as the `\` command would.
 *
 * @param buffer The output buffer of at least TECO_EXPRESSIONS_FORMAT_LEN characters.
 *               The output string will be null-terminated.
 * @param number The number to format.
 * @return A pointer into buffer to the beginning of the formatted number.
 */
gchar *
teco_expressions_format(gchar *buffer, teco_int_t number)
{
	gchar *p = buffer + TECO_EXPRESSIONS_FORMAT_LEN;
	teco_int_t v = ABS(number);
	*--p = '\0';
	do {
		*--p = '0' + (v % teco_radix);
		if (*p > '9')
			*p += 'A' - '9' - 1;
	} while ((v /= teco_radix));
	if (number < 0)
		*--p = '-';
	return p;
}
#ifndef NDEBUG
static void __attribute__((destructor))
teco_expressions_cleanup(void)
{
	g_array_free(teco_numbers, TRUE);
	g_array_free(teco_operators, TRUE);
}
#endif