/* 
 * Copyright (C) 2003 Tim Martin
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "map.h"
#include "function.h"

struct func_s {
    enum {
	VARIABLE = 0,
	NUMBER,
	IF,
	OP_MULTIPLY,
	OP_DIVIDE,
	OP_ADD,
	OP_SUBTRACT,
	OP_MOD,
	LPAREN,
	RPAREN,
    } type;

    char *variablename;    
    int value;
    
    struct func_s *arg1;
    struct func_s *arg2;

};

static func_t *
parse_op(char *str)
{
    char *end = NULL;
    func_t *ret = calloc(1, sizeof(func_t));
    if (!ret) return NULL;

    if (!str) {
	printf("Op is NULL\n");
	return NULL;
    }

    switch(str[0]) {	
    case '(':
	ret->type = LPAREN;
	return ret;
    case ')':
	ret->type = RPAREN;
	return ret;
    case '$':
	ret->type = VARIABLE;
	ret->variablename = strdup(str+1);
	return ret;
    case '*':
	ret->type = OP_MULTIPLY;
	return ret;
    case '/':
	ret->type = OP_DIVIDE;
	return ret;
    case '+':
	ret->type = OP_ADD;
	return ret;
    case '-':
	if (isdigit(str[1])) {
	    goto number;
	}
	ret->type = OP_SUBTRACT;
	return ret;
    case '%':
	ret->type = OP_MOD;
	return ret;
    case 'i':
    case 'I':
	if (strcasecmp(str,"IF") == 0) {
	    ret->type = IF;
	    return ret;
	} else {
	    return NULL;
	}
    default:

    number:
	ret->type = NUMBER;
	ret->value = strtol(str, &end, 10);

	if ((ret->value == LONG_MIN) || (ret->value == LONG_MAX) ||
	    (*end)) {
	    printf(_("Error parsing [%s]\n"), str);
	    return NULL;
	}

	return ret;
    }
}

extern func_t *
function_parse(char **argv, int argc, int *parsed)
{
    int i;
    func_t *cur = NULL;

    for (i = 0; i < argc; i++) {
	func_t *new = parse_op(argv[i]);

	if (!new) {
	    printf(_("Parse error parsing %s\n"), argv[i]);
	    return NULL;
	}

	if (new->type == RPAREN) {
	    if (parsed) *parsed = i+1;
	    return cur;
	}

	if (new->type == LPAREN) {
	    int did = 0;

	    new = function_parse(argv+i+1, argc - i - 1, &did);
	    
	    i += did;
	}

	if (!cur) {
	    cur = new;
	} else if (cur->type == IF) {
	    cur->arg1 = new;
	    cur->arg2 = parse_op(argv[i+1]);

	    if (cur->arg2->type == LPAREN) {
		int did = 0;		    
		cur->arg2 = function_parse(argv+i+2, argc - i - 2, &did);		    
		i += did;
	    }
	    i++;
	} else {
	    switch(new->type) 
	    {
	    case OP_MULTIPLY:
	    case OP_DIVIDE:
	    case OP_ADD:
	    case OP_SUBTRACT:
	    case OP_MOD:
		new->arg1 = cur;
		new->arg2 = parse_op(argv[i+1]);

		if (new->arg2->type == LPAREN) {
		    int did = 0;		    
		    new->arg2 = function_parse(argv+i+2, argc - i - 2, &did);		    
		    i += did;
		}
		i++;
		cur = new;
		break;
	    default:
		printf(_("Can't have those 2 args next to eachother: %d and %d\n"), 
		       cur->type, new->type);
		return NULL;
		break;
	    }
	}
    }

    return cur;
}

/*
 * Make into a args structure
 *
 * XXX doesn't honor maxargs
 */
static int
make_args(char *str, char **argv, int maxargs, int *argc)
{
    int numargs = 0;

    while (str) {
	char *end;

	while ((*str) && (isspace((int)*str))) str++;
	if (*str == '\0') break;

	end = str;

	while ((*end) && (!isspace((int)*end))) end++;
	if (*end) {
	    *end = '\0';
	    end++;
	}

	argv[numargs++] = str;
	
	str = end;
    }

    *argc = numargs;
    argv[numargs] = NULL;

    return 0;
}

extern func_t *
function_parse_string(char *str)
{
    char *argv[100];
    int argc;

    make_args(str, argv, 10, &argc);

    return function_parse(argv, argc, NULL);
}

extern int
function_evaluate(func_t *func, func_getvariable_cb *getvar_cb, void *getvar_rock)
{
    int mod;

    if (!func) return 0;

    switch(func->type) 
	{
	case IF:

	    if (function_evaluate(func->arg1, getvar_cb, getvar_rock)) {
		return function_evaluate(func->arg2, getvar_cb, getvar_rock);
	    } else {
		return 0;
	    }

	case VARIABLE:
	    if (strcasecmp(func->variablename,"random") == 0) {
		return rand();
	    } else {		
		int val;
		if (getvar_cb) {
		    val = getvar_cb(func->variablename, getvar_rock);
		} else {
		    val = 0;
		}
		return val;
	    }
	    break;

	case NUMBER:
	    return func->value;
	    
	case OP_MULTIPLY:
	    return function_evaluate( func->arg1, getvar_cb, getvar_rock) * 
		   function_evaluate( func->arg2, getvar_cb, getvar_rock);
	case OP_DIVIDE:
	    return function_evaluate( func->arg1, getvar_cb, getvar_rock) / 
		   function_evaluate( func->arg2, getvar_cb, getvar_rock);
	case OP_ADD:
	    return function_evaluate( func->arg1, getvar_cb, getvar_rock) + 
		   function_evaluate( func->arg2, getvar_cb, getvar_rock);
	case OP_SUBTRACT:
	    return function_evaluate( func->arg1, getvar_cb, getvar_rock) -
		   function_evaluate( func->arg2, getvar_cb, getvar_rock);
	case OP_MOD:
	    mod = function_evaluate( func->arg2, getvar_cb, getvar_rock);
	    if (mod == 0) {
		return 0;
	    } else {
		return function_evaluate( func->arg1, getvar_cb, getvar_rock) % mod;
	    }
		   
	default:
	    printf(_("Type not understood: %d\n"), func->type);
	    return 0;
    }
}
