/*
 * Copyright (C) 2010 Dan Carpenter.
 *
 * 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
 */

/*
 * smatch_equiv.c is for tracking how variables are the same
 *
 * if (a == b) {
 * Or
 * x = y;
 *
 * When a variable gets modified all the old relationships are
 * deleted.  remove_equiv(expr);
 *
 */

#include "smatch.h"
#include "smatch_slist.h"
#include "smatch_extra.h"

ALLOCATOR(relation, "related variables");

static struct relation *alloc_relation(const char *name, struct symbol *sym)
{
	struct relation *tmp;

	tmp = __alloc_relation(0);
	tmp->name = alloc_string(name);
	tmp->sym = sym;
	return tmp;
}

struct related_list *clone_related_list(struct related_list *related)
{
	struct relation *rel;
	struct related_list *to_list = NULL;

	FOR_EACH_PTR(related, rel) {
		add_ptr_list(&to_list, rel);
	} END_FOR_EACH_PTR(rel);

	return to_list;
}

static int cmp_relation(struct relation *a, struct relation *b)
{
	int ret;

	if (a == b)
		return 0;

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

	ret = strcmp(a->name, b->name);
	if (ret)
		return ret;

	return 0;
}

struct related_list *get_shared_relations(struct related_list *one,
					      struct related_list *two)
{
	struct related_list *ret = NULL;
	struct relation *one_rel;
	struct relation *two_rel;

	PREPARE_PTR_LIST(one, one_rel);
	PREPARE_PTR_LIST(two, two_rel);
	for (;;) {
		if (!one_rel || !two_rel)
			break;
		if (cmp_relation(one_rel, two_rel) < 0) {
			NEXT_PTR_LIST(one_rel);
		} else if (cmp_relation(one_rel, two_rel) == 0) {
			add_ptr_list(&ret, one_rel);
			NEXT_PTR_LIST(one_rel);
			NEXT_PTR_LIST(two_rel);
		} else {
			NEXT_PTR_LIST(two_rel);
		}
	}
	FINISH_PTR_LIST(two_rel);
	FINISH_PTR_LIST(one_rel);

	return ret;
}

static void add_related(struct related_list **rlist, const char *name, struct symbol *sym)
{
	struct relation *rel;
	struct relation *new;
	struct relation tmp = {
		.name = (char *)name,
		.sym = sym
	};

	FOR_EACH_PTR(*rlist, rel) {
		if (cmp_relation(rel, &tmp) < 0)
			continue;
		if (cmp_relation(rel, &tmp) == 0)
			return;
		new = alloc_relation(name, sym);
		INSERT_CURRENT(new, rel);
		return;
	} END_FOR_EACH_PTR(rel);
	new = alloc_relation(name, sym);
	add_ptr_list(rlist, new);
}

static struct related_list *del_related(struct smatch_state *state, const char *name, struct symbol *sym)
{
	struct relation *tmp;
	struct relation remove = {
		.name = (char *)name,
		.sym = sym,
	};
	struct related_list *ret = NULL;

	FOR_EACH_PTR(estate_related(state), tmp) {
		if (cmp_relation(tmp, &remove) != 0)
			add_ptr_list(&ret, tmp);
	} END_FOR_EACH_PTR(tmp);

	return ret;
}

void remove_from_equiv(const char *name, struct symbol *sym)
{
	struct sm_state *orig_sm;
	struct relation *rel;
	struct smatch_state *state;
	struct related_list *to_update;

	// FIXME: is this the right permanent solution.  Perhaps it is, I guess.
	if (in_fake_env)
		return;
	orig_sm = get_sm_state(SMATCH_EXTRA, name, sym);
	if (!orig_sm || !get_dinfo(orig_sm->state)->related)
		return;

	state = clone_estate(orig_sm->state);
	to_update = del_related(state, name, sym);

	FOR_EACH_PTR(to_update, rel) {
		struct sm_state *old_sm, *new_sm;

		old_sm = get_sm_state(SMATCH_EXTRA, rel->name, rel->sym);
		if (!old_sm)
			continue;

		new_sm = clone_sm(old_sm);
		get_dinfo(new_sm->state)->related = to_update;
		__set_sm(new_sm);
	} END_FOR_EACH_PTR(rel);
}

void set_related(struct smatch_state *estate, struct related_list *rlist)
{
	if (!estate_related(estate) && !rlist)
		return;
	get_dinfo(estate)->related = rlist;
}

/*
 * set_equiv() is only used for assignments where we set one variable
 * equal to the other.  a = b;.  It's not used for if conditions where
 * a == b.
 */
void set_equiv(struct expression *left, struct expression *right)
{
	struct sm_state *right_sm, *left_sm, *other_sm;
	struct relation *rel;
	char *left_name;
	struct symbol *left_sym;
	struct related_list *rlist;
	char *other_name;
	struct symbol *other_sym;

	if (in_fake_env)
		return;
	left_name = expr_to_var_sym(left, &left_sym);
	if (!left_name || !left_sym)
		goto free;

	other_name = get_other_name_sym(left_name, left_sym, &other_sym);

	right_sm = get_sm_state_expr(SMATCH_EXTRA, right);
	if (!right_sm) {
		struct range_list *rl;

		if (!get_implied_rl(right, &rl))
			rl = alloc_whole_rl(get_type(right));
		right_sm = set_state_expr(SMATCH_EXTRA, right, alloc_estate_rl(rl));
	}
	if (!right_sm)
		goto free;

	/* This block is because we want to preserve the implications. */
	left_sm = clone_sm(right_sm);
	left_sm->name = alloc_string(left_name);
	left_sm->sym = left_sym;
	left_sm->state = clone_estate_cast(get_type(left), right_sm->state);
	/* FIXME: The expression we're passing is wrong */
	set_extra_mod_helper(left_name, left_sym, left, left_sm->state);
	__set_sm(left_sm);

	if (other_name && other_sym) {
		other_sm = clone_sm(right_sm);
		other_sm->name = alloc_string(other_name);
		other_sm->sym = other_sym;
		other_sm->state = clone_estate_cast(get_type(left), left_sm->state);
		set_extra_mod_helper(other_name, other_sym, NULL, other_sm->state);
		__set_sm(other_sm);
	}

	rlist = clone_related_list(estate_related(right_sm->state));
	add_related(&rlist, right_sm->name, right_sm->sym);
	add_related(&rlist, left_name, left_sym);
	if (other_name && other_sym)
		add_related(&rlist, other_name, other_sym);

	FOR_EACH_PTR(rlist, rel) {
		struct sm_state *old_sm, *new_sm;

		old_sm = get_sm_state(SMATCH_EXTRA, rel->name, rel->sym);
		if (!old_sm)  /* shouldn't happen */
			continue;
		new_sm = clone_sm(old_sm);
		new_sm->state = clone_estate(old_sm->state);
		get_dinfo(new_sm->state)->related = rlist;
		__set_sm(new_sm);
	} END_FOR_EACH_PTR(rel);
free:
	free_string(left_name);
}

void set_equiv_state_expr(int id, struct expression *expr, struct smatch_state *state)
{
	struct relation *rel;
	struct smatch_state *estate;

	estate = get_state_expr(SMATCH_EXTRA, expr);

	if (!estate)
		return;

	FOR_EACH_PTR(get_dinfo(estate)->related, rel) {
		set_state(id, rel->name, rel->sym, state);
	} END_FOR_EACH_PTR(rel);
}
