#include <stdio.h>
#include <math.h>
#include "MEM.h"
#include "DBG.h"
#include "calc.h"

static Value
eval_int_expression(int int_value)
{
    Value	v;

    v.type = INT_VALUE;
    v.u.int_value = int_value;

    return v;
}

static Value
eval_double_expression(double double_value)
{
    Value	v;

    v.type = DOUBLE_VALUE;
    v.u.double_value = double_value;

    return v;
}

static Value
eval_identifier_expression(LocalEnvironment *env, char *identifier)
{
    Value	v;
    Value	*vp;

    vp = clc_search_local_variable(env, identifier);
    if (vp != NULL) {
	v = *vp;
	return v;
    }
    vp = clc_search_global_variable(identifier);
    if (vp != NULL) {
	v = *vp;
	return v;
    }
    clc_runtime_error(VARIABLE_NOT_FOUND_ERR, "(%s)\n", identifier);
    return v; /* dummy. make compiler happy. */
}

static Value eval_expression(LocalEnvironment *env, Expression *expr);

static Value
eval_expression_list_expression(LocalEnvironment *env,
				Expression *expression, Expression *next)
{
    Value	v;

    v = eval_expression(env, expression);
    if (next) {
	v = eval_expression(env, next);
    }

    return v;
}

static Value
eval_assign_expression(LocalEnvironment *env,
		       char *identifier, Expression *expression)
{
    Value	v;
    Value	*left;

    v = eval_expression(env, expression);

    left = clc_search_local_variable(env, identifier);
    if (left == NULL) {
	left = clc_search_global_variable(identifier);
    }
    if (left != NULL) {
	/* Notice! Overwriting variable by pointer. */
	*left = v;
    } else {
	if (env != NULL) {
	    clc_add_local_variable(env, identifier, &v);
	} else {
	    clc_add_global_variable(identifier, &v);
	}
    }
    return v;
}

static int
eval_binary_int(ExpressionType operator, int left, int right)
{
    int	result;

    switch (operator) {
      case INT_EXPRESSION:	/* FALLTHRU */
      case DOUBLE_EXPRESSION:	/* FALLTHRU */
      case IDENTIFIER_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_LIST_EXPRESSION:	/* FALLTHRU */
      case ASSIGN_EXPRESSION:
	DBG_assert(0, ("bad case...%d", operator));
	break;
      case ADD_EXPRESSION:
	result = left + right;
	break;
      case SUB_EXPRESSION:
	result = left - right;
	break;
      case MUL_EXPRESSION:
	result = left * right;
	break;
      case DIV_EXPRESSION:
	result = left / right;
	break;
      case MOD_EXPRESSION:
	result = left % right;
	break;
      case EQ_EXPRESSION:
	result = left == right;
	break;
      case NE_EXPRESSION:
	result = left != right;
	break;
      case GT_EXPRESSION:
	result = left > right;
	break;
      case GE_EXPRESSION:
	result = left >= right;
	break;
      case LT_EXPRESSION:
	result = left < right;
	break;
      case LE_EXPRESSION:
	result = left <= right;
	break;
      case MINUS_EXPRESSION:	/* FALLTHRU */
      case IF_EXPRESSION:	/* FALLTHRU */
      case WHILE_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad case...%d", operator));
    }
    return result;
}

static void
eval_binary_double(ExpressionType operator, double left, double right,
		   Value *result)
{
    if (operator == ADD_EXPRESSION || operator == SUB_EXPRESSION
	|| operator == MUL_EXPRESSION || operator == DIV_EXPRESSION
	|| operator == MOD_EXPRESSION) {
	result->type = DOUBLE_VALUE;
    } else {
	DBG_assert(operator == EQ_EXPRESSION || operator == NE_EXPRESSION
		   || operator == GT_EXPRESSION || operator == GE_EXPRESSION
		   || operator == LT_EXPRESSION || operator == LE_EXPRESSION,
		   ("operator..%d\n", operator));
	result->type = INT_VALUE;
    }
    switch (operator) {
      case INT_EXPRESSION:	/* FALLTHRU */
      case DOUBLE_EXPRESSION:	/* FALLTHRU */
      case IDENTIFIER_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_LIST_EXPRESSION:	/* FALLTHRU */
      case ASSIGN_EXPRESSION:
	DBG_assert(0, ("bad case...%d", operator));
	break;
      case ADD_EXPRESSION:
	result->u.double_value = left + right;
	break;
      case SUB_EXPRESSION:
	result->u.double_value = left - right;
	break;
      case MUL_EXPRESSION:
	result->u.double_value = left * right;
	break;
      case DIV_EXPRESSION:
	result->u.double_value = left / right;
	break;
      case MOD_EXPRESSION:
	result->u.double_value = fmod(left, right);
	break;
      case EQ_EXPRESSION:
	result->u.int_value = left == right;
	break;
      case NE_EXPRESSION:
	result->u.int_value = left != right;
	break;
      case GT_EXPRESSION:
	result->u.int_value = left > right;
	break;
      case GE_EXPRESSION:
	result->u.int_value = left >= right;
	break;
      case LT_EXPRESSION:
	result->u.int_value = left < right;
	break;
      case LE_EXPRESSION:
	result->u.int_value = left <= right;
	break;
      case MINUS_EXPRESSION:	/* FALLTHRU */
      case IF_EXPRESSION:	/* FALLTHRU */
      case WHILE_EXPRESSION:	/* FALLTHRU */
      case FUNCTION_CALL_EXPRESSION:	/* FALLTHRU */
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad default...%d", operator));
    }
}

Value
clc_eval_binary_expression(LocalEnvironment *env,
			   ExpressionType operator,
			   Expression *left, Expression *right)
{
    Value	left_val;
    Value	right_val;
    Value	result;

    left_val = eval_expression(env, left);
    right_val = eval_expression(env, right);

    if (left_val.type == INT_VALUE
	&& right_val.type == INT_VALUE) {
	result.type = INT_VALUE;
	result.u.int_value
	    = eval_binary_int(operator,
			      left_val.u.int_value, right_val.u.int_value);
    } else if (left_val.type == DOUBLE_VALUE
	       && right_val.type == DOUBLE_VALUE) {
	eval_binary_double(operator,
			   left_val.u.double_value, right_val.u.double_value,
			   &result);
    } else {
	/* cast int to double */
	if (left_val.type == INT_VALUE) {
	    left_val.u.double_value = left_val.u.int_value;
	} else {
	    right_val.u.double_value = right_val.u.int_value;
	}
	eval_binary_double(operator,
			   left_val.u.double_value, right_val.u.double_value,
			   &result);
    }
    return result;
}

Value
clc_eval_minus_expression(LocalEnvironment *env, Expression *operand)
{
    Value	operand_val;
    Value	result;

    operand_val = eval_expression(env, operand);
    if (operand_val.type == INT_VALUE) {
	result.type = INT_VALUE;
	result.u.int_value = -operand_val.u.int_value;
    } else {
	DBG_assert(operand_val.type == DOUBLE_VALUE,
		   ("operand_val.type..%d", operand_val.type));
	result.type = DOUBLE_VALUE;
	result.u.double_value = -operand_val.u.double_value;
    }
    return result;
}

static Value
eval_if_expression(LocalEnvironment *env,
		   Expression *condition, Expression *then_expression,
		   Expression *else_expression)
{
    Value	condition_val;
    Value	result;

    condition_val = eval_expression(env, condition);
    if (condition_val.type != INT_VALUE) {
	clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
    }
    if (condition_val.u.int_value) {
	result = eval_expression(env, then_expression);
    } else {
	result = eval_expression(env, else_expression);
    }
    return result;
}

static Value
eval_while_expression(LocalEnvironment *env,
		      Expression *condition, Expression *expression_list)
{
    Value	condition_val;
    Value	result;

    for (;;) {
	condition_val = eval_expression(env, condition);
	if (condition_val.type != INT_VALUE) {
	    clc_runtime_error(BOOLEAN_EXPECTED_ERR, NULL);
	}
	if (!condition_val.u.int_value)
	    break;

	result = eval_expression(env, expression_list);
    }
    return result;
}

static LocalEnvironment *
alloc_local_environment()
{
    LocalEnvironment *ret;

    ret = MEM_malloc(sizeof(LocalEnvironment));
    ret->variable = NULL;

    return ret;
}

static void
dispose_local_environment(LocalEnvironment *env)
{
    while (env->variable) {
	Variable	*temp;
	temp = env->variable->next;
	MEM_free(env->variable);
	env->variable = temp;
    }
    MEM_free(env);
}

static Value
eval_function_call_expression(LocalEnvironment *env,
			      char *identifier, Expression *argument)
{
    Value	result;
    Expression		*arg_p;
    ParameterList	*param_p;
    LocalEnvironment	*local_env;
    FunctionDefinition	*func;
    
    func = clc_search_function(identifier);
    if (func == NULL) {
	clc_runtime_error(FUNCTION_NOT_FOUND_ERR, "name..%s\n", identifier);
    }
    local_env = alloc_local_environment();

    DBG_assert(argument->type == EXPRESSION_LIST_EXPRESSION,
	       ("type..%d\n", argument->type));

    for (arg_p = argument, param_p = func->parameter;
	 arg_p;
	 arg_p = arg_p->u.expression_list.next,
	 param_p = param_p->next) {
	Value arg_val;

	if (param_p == NULL) {
	    clc_runtime_error(ARGUMENT_TOO_MANY_ERR, NULL);
	}
	arg_val = eval_expression(env,
				  arg_p->u.expression_list.expression);
	clc_add_local_variable(local_env, param_p->name, &arg_val);
    }
    if (param_p) {
	clc_runtime_error(ARGUMENT_TOO_FEW_ERR, NULL);
    }
    result = eval_expression(local_env, func->expression_list);

    dispose_local_environment(local_env);

    return result;
}

static Value
eval_expression(LocalEnvironment *env, Expression *expr)
{
    Value	v;
    switch (expr->type) {
      case INT_EXPRESSION:
	v = eval_int_expression(expr->u.int_value);
	break;
      case DOUBLE_EXPRESSION:
	v = eval_double_expression(expr->u.double_value);
	break;
      case IDENTIFIER_EXPRESSION:
	v = eval_identifier_expression(env, expr->u.identifier);
	break;
      case EXPRESSION_LIST_EXPRESSION:
	v = eval_expression_list_expression
	    (env,
	     expr->u.expression_list.expression,
	     expr->u.expression_list.next);
	break;
      case ASSIGN_EXPRESSION:
	v = eval_assign_expression(env,
				   expr->u.assign_expression.variable,
				   expr->u.assign_expression.operand);
	break;
      case ADD_EXPRESSION:	/* FALLTHRU */
      case SUB_EXPRESSION:	/* FALLTHRU */
      case MUL_EXPRESSION:	/* FALLTHRU */
      case DIV_EXPRESSION:	/* FALLTHRU */
      case MOD_EXPRESSION:	/* FALLTHRU */
      case EQ_EXPRESSION:	/* FALLTHRU */
      case NE_EXPRESSION:	/* FALLTHRU */
      case GT_EXPRESSION:	/* FALLTHRU */
      case GE_EXPRESSION:	/* FALLTHRU */
      case LT_EXPRESSION:	/* FALLTHRU */
      case LE_EXPRESSION:	/* FALLTHRU */
	v = clc_eval_binary_expression(env,
				       expr->type,
				       expr->u.binary_expression.left,
				       expr->u.binary_expression.right);
	break;
      case MINUS_EXPRESSION:
	v = clc_eval_minus_expression(env, expr->u.minus_expression);
	break;
      case IF_EXPRESSION:
	v = eval_if_expression(env,
			       expr->u.if_expression.condition,
			       expr->u.if_expression.then_expression,
			       expr->u.if_expression.else_expression);
	break;
      case WHILE_EXPRESSION:
	v = eval_while_expression(env,
				  expr->u.while_expression.condition,
				  expr->u.while_expression.expression_list);
	break;
      case FUNCTION_CALL_EXPRESSION:
	v = eval_function_call_expression
	    (env,
	     expr->u.function_call_expression.identifier,
	     expr->u.function_call_expression.argument);
	break;
      case EXPRESSION_TYPE_NUM:	/* FALLTHRU */
      default:
	DBG_assert(0, ("bad case. type..%d\n", expr->type));
    }
    return v;
}

void
clc_eval_expression(Expression *expression)
{
    Value		v;
    RuntimeError	error_id;

    if ((error_id = (RuntimeError)setjmp(clc_current_interpreter
					 ->error_recovery_environment)) == 0) {
	v = eval_expression(NULL, expression);
	if (clc_current_interpreter->input_mode == CLC_TTY_INPUT_MODE) {
	    if (v.type == INT_VALUE) {
		printf(">>%d\n", v.u.int_value);
	    } else if (v.type == DOUBLE_VALUE) {
		printf(">>%f\n", v.u.double_value);
	    } else {
		printf("<void>\n");
	    }
	}
    } else {
    }
    clc_reopen_current_storage();
}