/*
 * Copyright (C) 2013 Oracle.
 *
 * 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, see http://www.gnu.org/copyleft/gpl.txt
 */

#include "smatch.h"

ALLOCATOR(var_sym, "var_sym structs");

struct smatch_state *alloc_var_sym_state(const char *var, struct symbol *sym)
{
	struct smatch_state *state;

	state = __alloc_smatch_state(0);
	state->name = alloc_string(var);
	state->data = alloc_var_sym(var, sym);
	return state;
}

struct var_sym *alloc_var_sym(const char *var, struct symbol *sym)
{
	struct var_sym *tmp;

	tmp = __alloc_var_sym(0);
	tmp->var = alloc_string(var);
	tmp->sym = sym;
	return tmp;
}

struct var_sym_list *expr_to_vsl(struct expression *expr)
{
	struct var_sym_list *ret = NULL;
	struct var_sym_list *more;
	char *var;
	struct symbol *sym;

	expr = strip_expr(expr);
	if (!expr)
		return NULL;

	if (expr->type == EXPR_BINOP ||
	    expr->type == EXPR_LOGICAL ||
	    expr->type == EXPR_COMPARE) {
		struct var_sym_list *left, *right;

		left = expr_to_vsl(expr->left);
		right = expr_to_vsl(expr->right);
		ret = combine_var_sym_lists(left, right);
		free_var_syms_and_list(&left);
		free_var_syms_and_list(&right);
		return ret;
	}

	var = expr_to_var_sym(expr, &sym);
	if (var && sym)
		add_var_sym(&ret, var, sym);


	// handle "*(foo[bar])" I guess */
	if (expr->type == EXPR_PREOP && expr->op == '*') {
		more = expr_to_vsl(expr->unop);
		return combine_var_sym_lists(ret, more);
	}

	if (expr->type == EXPR_DEREF) {
		more = expr_to_vsl(expr->deref);
		return combine_var_sym_lists(ret, more);
	}

	return ret;
}

int cmp_var_sym(const struct var_sym *a, const struct var_sym *b)
{
	int ret;

	if (a == b)
		return 0;
	if (!b)
		return -1;
	if (!a)
		return 1;

	ret = strcmp(a->var, b->var);
	if (ret < 0)
		return -1;
	if (ret > 0)
		return 1;

	if (!b->sym && a->sym)
		return -1;
	if (!a->sym && b->sym)
		return 1;
	if (a->sym < b->sym)
		return -1;
	if (a->sym > b->sym)
		return 1;

	return 0;
}

void add_var_sym(struct var_sym_list **list, const char *var, struct symbol *sym)
{
	struct var_sym *tmp, *new;

	if (in_var_sym_list(*list, var, sym))
		return;
	new = alloc_var_sym(var, sym);

	FOR_EACH_PTR(*list, tmp) {
		if (cmp_var_sym(tmp, new) < 0)
			continue;
		else if (cmp_var_sym(tmp, new) == 0) {
			return;
		} else {
			INSERT_CURRENT(new, tmp);
			return;
		}
	} END_FOR_EACH_PTR(tmp);
	add_ptr_list(list, new);
}

void add_var_sym_expr(struct var_sym_list **list, struct expression *expr)
{
	char *var;
	struct symbol *sym;

	var = expr_to_var_sym(expr, &sym);
	if (!var || !sym)
		goto free;
	add_var_sym(list, var, sym);
free:
	free_string(var);
}

static void free_var_sym(struct var_sym *vs)
{
	free_string(vs->var);
	__free_var_sym(vs);
}

void del_var_sym(struct var_sym_list **list, const char *var, struct symbol *sym)
{
	struct var_sym *tmp;

	FOR_EACH_PTR(*list, tmp) {
		if (tmp->sym == sym && strcmp(tmp->var, var) == 0) {
			DELETE_CURRENT_PTR(tmp);
			free_var_sym(tmp);
			return;
		}
	} END_FOR_EACH_PTR(tmp);
}

int in_var_sym_list(struct var_sym_list *list, const char *var, struct symbol *sym)
{
	struct var_sym *tmp;

	FOR_EACH_PTR(list, tmp) {
		if (tmp->sym == sym && strcmp(tmp->var, var) == 0)
			return 1;
	} END_FOR_EACH_PTR(tmp);
	return 0;
}

struct var_sym_list *clone_var_sym_list(struct var_sym_list *from_vsl)
{
	struct var_sym *tmp, *clone_vs;
	struct var_sym_list *to_vsl = NULL;

	FOR_EACH_PTR(from_vsl, tmp) {
		clone_vs = alloc_var_sym(tmp->var, tmp->sym);
		add_ptr_list(&to_vsl, clone_vs);
	} END_FOR_EACH_PTR(tmp);
	return to_vsl;
}

void merge_var_sym_list(struct var_sym_list **dest, struct var_sym_list *src)
{
	struct var_sym *tmp;

	FOR_EACH_PTR(src, tmp) {
		add_var_sym(dest, tmp->var, tmp->sym);
	} END_FOR_EACH_PTR(tmp);
}

struct var_sym_list *combine_var_sym_lists(struct var_sym_list *one, struct var_sym_list *two)
{
	struct var_sym_list *to_vsl;

	to_vsl = clone_var_sym_list(one);
	merge_var_sym_list(&to_vsl, two);
	return to_vsl;
}

int var_sym_lists_equiv(struct var_sym_list *one, struct var_sym_list *two)
{
	struct var_sym *one_tmp, *two_tmp;

	if (one == two)
		return 1;

	if (ptr_list_size((struct ptr_list *)one) != ptr_list_size((struct ptr_list *)two))
		return 0;

	PREPARE_PTR_LIST(one, one_tmp);
	PREPARE_PTR_LIST(two, two_tmp);
	for (;;) {
		if (!one_tmp && !two_tmp)
			return 1;
		if (one_tmp->sym != two_tmp->sym)
			return 0;
		if (strcmp(one_tmp->var, two_tmp->var) != 0)
			return 0;
		NEXT_PTR_LIST(one_tmp);
		NEXT_PTR_LIST(two_tmp);
	}
	FINISH_PTR_LIST(two_tmp);
	FINISH_PTR_LIST(one_tmp);

	return 1;
}

void free_var_sym_list(struct var_sym_list **list)
{
	__free_ptr_list((struct ptr_list **)list);
}

void free_var_syms_and_list(struct var_sym_list **list)
{
	struct var_sym *tmp;

	FOR_EACH_PTR(*list, tmp) {
		free_var_sym(tmp);
	} END_FOR_EACH_PTR(tmp);
	free_var_sym_list(list);
}

