/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <ctype.h>
#include <stdlib.h>
#include <syslog.h>

#include "array.h"
#include "cras_expr.h"

static const char *copy_str(const char *begin, const char *end)
{
	char *s = malloc(end - begin + 1);
	memcpy(s, begin, end - begin);
	s[end - begin] = '\0';
	return s;
}

static void value_set_boolean(struct cras_expr_value *value, char boolean)
{
	cras_expr_value_free(value);
	value->type = CRAS_EXPR_VALUE_TYPE_BOOLEAN;
	value->u.boolean = !!boolean;
}

static void value_set_integer(struct cras_expr_value *value, int integer)
{
	cras_expr_value_free(value);
	value->type = CRAS_EXPR_VALUE_TYPE_INT;
	value->u.integer = integer;
}

static void value_set_string2(struct cras_expr_value *value, const char *begin,
			      const char *end)
{
	cras_expr_value_free(value);
	value->type = CRAS_EXPR_VALUE_TYPE_STRING;
	value->u.string = copy_str(begin, end);
}

static void value_set_string(struct cras_expr_value *value, const char *str)
{
	value_set_string2(value, str, str + strlen(str));
}

static void cras_expr_value_set_function(struct cras_expr_value *value,
					 cras_expr_function_type function)
{
	cras_expr_value_free(value);
	value->type = CRAS_EXPR_VALUE_TYPE_FUNCTION;
	value->u.function = function;
}

static void copy_value(struct cras_expr_value *value,
		       struct cras_expr_value *original)
{
	cras_expr_value_free(value);  /* free the original value first */
	value->type = original->type;
	switch (value->type) {
	case CRAS_EXPR_VALUE_TYPE_NONE:
		break;
	case CRAS_EXPR_VALUE_TYPE_BOOLEAN:
		value->u.boolean = original->u.boolean;
		break;
	case CRAS_EXPR_VALUE_TYPE_INT:
		value->u.integer = original->u.integer;
		break;
	case CRAS_EXPR_VALUE_TYPE_STRING:
		value->u.string = strdup(original->u.string);
		break;
	case CRAS_EXPR_VALUE_TYPE_FUNCTION:
		value->u.function = original->u.function;
		break;
	}
}

void cras_expr_value_free(struct cras_expr_value *value)
{
	switch (value->type) {
	case CRAS_EXPR_VALUE_TYPE_STRING:
		free((char *)value->u.string);
		value->u.string = NULL;
		break;
	case CRAS_EXPR_VALUE_TYPE_NONE:
	case CRAS_EXPR_VALUE_TYPE_BOOLEAN:
	case CRAS_EXPR_VALUE_TYPE_INT:
	case CRAS_EXPR_VALUE_TYPE_FUNCTION:
		break;
	}
	value->type = CRAS_EXPR_VALUE_TYPE_NONE;
}

static struct cras_expr_value *find_value(struct cras_expr_env *env,
					  const char *name)
{
	int i;
	const char **key;

	FOR_ARRAY_ELEMENT(&env->keys, i, key) {
		if (strcmp(*key, name) == 0)
			return ARRAY_ELEMENT(&env->values, i);
	}
	return NULL;
}

/* Insert a (key, value) pair to the environment. The value is
 * initialized to zero. Return the pointer to value so it can be set
 * to the proper value. */
static struct cras_expr_value *insert_value(struct cras_expr_env *env,
					    const char *key)
{
	*ARRAY_APPEND_ZERO(&env->keys) = strdup(key);
	return ARRAY_APPEND_ZERO(&env->values);
}

static struct cras_expr_value *find_or_insert_value(struct cras_expr_env *env,
						    const char *key)
{
	struct cras_expr_value *value = find_value(env, key);
	if (!value)
		value = insert_value(env, key);
	return value;
}

static void function_not(cras_expr_value_array *operands,
			 struct cras_expr_value *result)
{
	struct cras_expr_value *value;
	int is_false;

	if (ARRAY_COUNT(operands) != 2) {
		cras_expr_value_free(result);
		syslog(LOG_ERR, "not takes one argument");
		return;
	}

	value = ARRAY_ELEMENT(operands, 1);
	is_false = (value->type == CRAS_EXPR_VALUE_TYPE_BOOLEAN &&
		    !value->u.boolean);
	value_set_boolean(result, is_false);
}

static void function_and(cras_expr_value_array *operands,
			 struct cras_expr_value *result)
{
	int i;
	struct cras_expr_value *value;
	int n = ARRAY_COUNT(operands);

	/* no operands -- return #t */
	if (n <= 1) {
		value_set_boolean(result, 1);
		return;
	}

	/* if there is any #f, return it */
	FOR_ARRAY_ELEMENT(operands, i, value) {
		if (i == 0)
			continue;  /* ignore "and" itself */
		if (value->type == CRAS_EXPR_VALUE_TYPE_BOOLEAN &&
		    !value->u.boolean) {
			value_set_boolean(result, 0);
			return;
		}
	}

	/* otherwise return the last element */
	copy_value(result, ARRAY_ELEMENT(operands, n - 1));
}

static void function_or(cras_expr_value_array *operands,
			struct cras_expr_value *result)
{
	int i;
	struct cras_expr_value *value;

	FOR_ARRAY_ELEMENT(operands, i, value) {
		if (i == 0)
			continue;  /* ignore "or" itself */
		if (value->type != CRAS_EXPR_VALUE_TYPE_BOOLEAN ||
		    value->u.boolean) {
			copy_value(result, value);
			return;
		}
	}

	value_set_boolean(result, 0);
}

static char function_equal_real(cras_expr_value_array *operands)
{
	int i;
	struct cras_expr_value *value, *prev;

	FOR_ARRAY_ELEMENT(operands, i, value) {
		if (i <= 1)
			continue;  /* ignore equal? and first operand */
		/* compare with the previous operand */

		prev = ARRAY_ELEMENT(operands, i - 1);

		if (prev->type != value->type)
			return 0;

		switch (prev->type) {
		case CRAS_EXPR_VALUE_TYPE_NONE:
			break;
		case CRAS_EXPR_VALUE_TYPE_BOOLEAN:
			if (prev->u.boolean != value->u.boolean)
				return 0;
			break;
		case CRAS_EXPR_VALUE_TYPE_INT:
			if (prev->u.integer != value->u.integer)
				return 0;
			break;
		case CRAS_EXPR_VALUE_TYPE_STRING:
			if (strcmp(prev->u.string, value->u.string) != 0)
				return 0;
			break;
		case CRAS_EXPR_VALUE_TYPE_FUNCTION:
			if (prev->u.function != value->u.function)
				return 0;
			break;
		}
	}

	return 1;
}

static void function_equal(cras_expr_value_array *operands,
			   struct cras_expr_value *result)
{
	value_set_boolean(result, function_equal_real(operands));
}

static void env_set_variable(struct cras_expr_env *env, const char *name,
			     struct cras_expr_value *new_value)
{
	struct cras_expr_value *value = find_or_insert_value(env, name);
	copy_value(value, new_value);
}

void cras_expr_env_install_builtins(struct cras_expr_env *env)
{
	struct cras_expr_value value = CRAS_EXPR_VALUE_INIT;

	/* initialize env with builtin functions */
	cras_expr_value_set_function(&value, &function_not);
	env_set_variable(env, "not", &value);

	cras_expr_value_set_function(&value, &function_and);
	env_set_variable(env, "and", &value);

	cras_expr_value_set_function(&value, &function_or);
	env_set_variable(env, "or", &value);

	cras_expr_value_set_function(&value, &function_equal);
	env_set_variable(env, "equal?", &value);

	cras_expr_value_free(&value);
}

void cras_expr_env_set_variable_boolean(struct cras_expr_env *env,
					const char *name, char boolean)
{
	struct cras_expr_value *value = find_or_insert_value(env, name);
	value_set_boolean(value, boolean);
}

void cras_expr_env_set_variable_integer(struct cras_expr_env *env,
					const char *name, int integer)
{
	struct cras_expr_value *value = find_or_insert_value(env, name);
	value_set_integer(value, integer);
}

void cras_expr_env_set_variable_string(struct cras_expr_env *env,
				       const char *name, const char *str)
{
	struct cras_expr_value *value = find_or_insert_value(env, name);
	value_set_string(value, str);
}

void cras_expr_env_free(struct cras_expr_env *env)
{
	int i;
	const char **key;
	struct cras_expr_value *value;

	FOR_ARRAY_ELEMENT(&env->keys, i, key) {
		free((char *)*key);
	}

	FOR_ARRAY_ELEMENT(&env->values, i, value) {
		cras_expr_value_free(value);
	}

	ARRAY_FREE(&env->keys);
	ARRAY_FREE(&env->values);
}

static struct cras_expr_expression *new_boolean_literal(char boolean)
{
	struct cras_expr_expression *expr;
	expr = calloc(1, sizeof(struct cras_expr_expression));
	expr->type = EXPR_TYPE_LITERAL;
	value_set_boolean(&expr->u.literal, boolean);
	return expr;
}

static struct cras_expr_expression *new_integer_literal(int integer)
{
	struct cras_expr_expression *expr;
	expr = calloc(1, sizeof(struct cras_expr_expression));
	expr->type = EXPR_TYPE_LITERAL;
	value_set_integer(&expr->u.literal, integer);
	return expr;
}

static struct cras_expr_expression *new_string_literal(const char *begin,
						       const char *end)
{
	struct cras_expr_expression *expr;
	expr = calloc(1, sizeof(struct cras_expr_expression));
	expr->type = EXPR_TYPE_LITERAL;
	value_set_string2(&expr->u.literal, begin, end);
	return expr;
}

static struct cras_expr_expression *new_variable(const char *begin,
						 const char *end)
{
	struct cras_expr_expression *expr;
	expr = calloc(1, sizeof(struct cras_expr_expression));
	expr->type = EXPR_TYPE_VARIABLE;
	expr->u.variable = copy_str(begin, end);
	return expr;
}

static struct cras_expr_expression *new_compound_expression()
{
	struct cras_expr_expression *expr;
	expr = calloc(1, sizeof(struct cras_expr_expression));
	expr->type = EXPR_TYPE_COMPOUND;
	return expr;
}

static void add_sub_expression(struct cras_expr_expression *expr,
			       struct cras_expr_expression *sub)
{
	ARRAY_APPEND(&expr->u.children, sub);
}

static int is_identifier_char(char c)
{
	if (isspace(c))
		return 0;
	if (c == '\0')
		return 0;
	if (isalpha(c))
		return 1;
	if (c == '_' || c == '-' || c == '?')
		return 1;
	return 0;
}

static struct cras_expr_expression *parse_one_expr(const char **str)
{
	/* skip whitespace */
	while (isspace(**str))
		(*str)++;

	if (**str == '\0')
		return NULL;

	/* boolean literal: #t, #f */
	if (**str == '#') {
		(*str)++;
		char c = **str;
		if (c == 't' || c == 'f') {
			(*str)++;
			return new_boolean_literal(c == 't');
		} else {
			syslog(LOG_ERR, "unexpected char after #: '%c'",
			       c);
		}
		return NULL;
	}

	/* integer literal: (-)[0-9]+ */
	if (isdigit(**str) || (**str == '-' && isdigit((*str)[1])))
		return new_integer_literal(strtol(*str, (char **)str, 10));

	/* string literal: "..." */
	if (**str == '"') {
		const char *begin = *str + 1;
		const char *end = strchr(begin, '"');
		if (end == NULL) {
			syslog(LOG_ERR, "no matching \"");
			end = begin;
			*str = begin;
		} else {
			*str = end + 1;
		}
		return new_string_literal(begin, end);
	}

	/* compound expression: (expr1 expr2 ...) */
	if (**str == '(') {
		(*str)++;
		struct cras_expr_expression *expr = new_compound_expression();
		while (1) {
			struct cras_expr_expression *next = parse_one_expr(str);
			if (next == NULL)
				break;
			add_sub_expression(expr, next);
		}
		if (**str != ')') {
			syslog(LOG_ERR, "no matching ): found '%c'", **str);
			cras_expr_expression_free(expr);
			return NULL;
		} else {
			(*str)++;
		}
		return expr;
	}

	/* variable name */
	if (is_identifier_char(**str)) {
		const char *begin = *str;
		while (is_identifier_char(**str))
			(*str)++;
		return new_variable(begin, *str);
	}

	return NULL;
}

struct cras_expr_expression *cras_expr_expression_parse(const char *str)
{
	if (!str)
		return NULL;
	return parse_one_expr(&str);
}

void cras_expr_expression_free(struct cras_expr_expression *expr)
{
	if (!expr)
		return;

	switch (expr->type) {
	case EXPR_TYPE_NONE:
		break;
	case EXPR_TYPE_LITERAL:
		cras_expr_value_free(&expr->u.literal);
		break;
	case EXPR_TYPE_VARIABLE:
		free((char *)expr->u.variable);
		break;
	case EXPR_TYPE_COMPOUND:
	{
		int i;
		struct cras_expr_expression **psub;
		FOR_ARRAY_ELEMENT(&expr->u.children, i, psub) {
			cras_expr_expression_free(*psub);
		}
		ARRAY_FREE(&expr->u.children);
		break;
	}
	}
	free(expr);
}

void cras_expr_expression_eval(struct cras_expr_expression *expr,
			       struct cras_expr_env *env,
			       struct cras_expr_value *result)
{
	cras_expr_value_free(result);

	switch (expr->type) {
	case EXPR_TYPE_NONE:
		break;
	case EXPR_TYPE_LITERAL:
		copy_value(result, &expr->u.literal);
		break;
	case EXPR_TYPE_VARIABLE:
	{
		struct cras_expr_value *value = find_value(env,
							   expr->u.variable);
		if (value == NULL) {
			syslog(LOG_ERR, "cannot find value for %s",
			       expr->u.variable);
		} else {
			copy_value(result, value);
		}
		break;
	}
	case EXPR_TYPE_COMPOUND:
	{
		int i;
		struct cras_expr_expression **psub;
		cras_expr_value_array values = ARRAY_INIT;
		struct cras_expr_value *value;

		FOR_ARRAY_ELEMENT(&expr->u.children, i, psub) {
			value = ARRAY_APPEND_ZERO(&values);
			cras_expr_expression_eval(*psub, env, value);
		}

		if (ARRAY_COUNT(&values) > 0) {
			struct cras_expr_value *f = ARRAY_ELEMENT(&values, 0);
			if (f->type == CRAS_EXPR_VALUE_TYPE_FUNCTION)
				f->u.function(&values, result);
			else
				syslog(LOG_ERR,
				       "first element is not a function");
		} else {
			syslog(LOG_ERR, "empty compound expression?");
		}

		FOR_ARRAY_ELEMENT(&values, i, value) {
			cras_expr_value_free(value);
		}

		ARRAY_FREE(&values);
		break;
	}
	}
}

int cras_expr_expression_eval_int(struct cras_expr_expression *expr,
				  struct cras_expr_env *env,
				  int *integer)
{
	int rc = 0;
	struct cras_expr_value value = CRAS_EXPR_VALUE_INIT;

	cras_expr_expression_eval(expr, env, &value);
	if (value.type == CRAS_EXPR_VALUE_TYPE_INT) {
		*integer = value.u.integer;
	} else {
		syslog(LOG_ERR, "value type is not integer (%d)", value.type);
		rc = -1;
	}
	cras_expr_value_free(&value);
	return rc;
}

int cras_expr_expression_eval_boolean(struct cras_expr_expression *expr,
				      struct cras_expr_env *env,
				      char *boolean)
{
	int rc = 0;
	struct cras_expr_value value = CRAS_EXPR_VALUE_INIT;

	cras_expr_expression_eval(expr, env, &value);
	if (value.type == CRAS_EXPR_VALUE_TYPE_BOOLEAN) {
		*boolean = value.u.boolean;
	} else {
		syslog(LOG_ERR, "value type is not boolean (%d)", value.type);
		rc = -1;
	}
	cras_expr_value_free(&value);
	return rc;
}