/*
 * Copyright (C) 2006 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
 */

#include "smatch.h"

enum data_type {
	NO_DATA,
	EXPR_PTR,
	STMT_PTR,
	SYMBOL_PTR,
	SYM_LIST_PTR,
};

struct hook_container {
	int hook_type;
	int owner;
	void *fn;
};
ALLOCATOR(hook_container, "hook functions");
DECLARE_PTR_LIST(hook_func_list, struct hook_container);

typedef void (sym_list_func)(struct symbol_list *sym_list);
typedef void (array_init_hook)(struct expression *array, int nr);

static struct hook_func_list *merge_funcs;
static struct hook_func_list *unmatched_state_funcs;
static struct hook_func_list *array_init_hooks;
static struct hook_func_list *hook_array[NUM_HOOKS] = {};
static const enum data_type data_types[NUM_HOOKS] = {
	[EXPR_HOOK] = EXPR_PTR,
	[EXPR_HOOK_AFTER] = EXPR_PTR,
	[STMT_HOOK] = STMT_PTR,
	[STMT_HOOK_AFTER] = STMT_PTR,
	[SYM_HOOK] = EXPR_PTR,
	[STRING_HOOK] = EXPR_PTR,
	[DECLARATION_HOOK] = SYMBOL_PTR,
	[DECLARATION_HOOK_AFTER] = SYMBOL_PTR,
	[ASSIGNMENT_HOOK] = EXPR_PTR,
	[ASSIGNMENT_HOOK_AFTER] = EXPR_PTR,
	[RAW_ASSIGNMENT_HOOK] = EXPR_PTR,
	[GLOBAL_ASSIGNMENT_HOOK] = EXPR_PTR,
	[CALL_ASSIGNMENT_HOOK] = EXPR_PTR,
	[MACRO_ASSIGNMENT_HOOK] = EXPR_PTR,
	[BINOP_HOOK] = EXPR_PTR,
	[OP_HOOK] = EXPR_PTR,
	[LOGIC_HOOK] = EXPR_PTR,
	[PRELOOP_HOOK] = STMT_PTR,
	[AFTER_LOOP_NO_BREAKS] = STMT_PTR,
	[CONDITION_HOOK] = EXPR_PTR,
	[SELECT_HOOK] = EXPR_PTR,
	[WHOLE_CONDITION_HOOK] = EXPR_PTR,
	[FUNCTION_CALL_HOOK] = EXPR_PTR,
	[CALL_HOOK_AFTER_INLINE] = EXPR_PTR,
	[FUNCTION_CALL_HOOK_AFTER_DB] = EXPR_PTR,
	[DEREF_HOOK] = EXPR_PTR,
	[CASE_HOOK] = NO_DATA,
	[ASM_HOOK] = STMT_PTR,
	[CAST_HOOK] = EXPR_PTR,
	[SIZEOF_HOOK] = EXPR_PTR,
	[BASE_HOOK] = SYMBOL_PTR,
	[FUNC_DEF_HOOK] = SYMBOL_PTR,
	[AFTER_DEF_HOOK] = SYMBOL_PTR,
	[END_FUNC_HOOK] = SYMBOL_PTR,
	[AFTER_FUNC_HOOK] = SYMBOL_PTR,
	[RETURN_HOOK] = EXPR_PTR,
	[INLINE_FN_START] = EXPR_PTR,
	[INLINE_FN_END] = EXPR_PTR,
	[END_FILE_HOOK] = SYM_LIST_PTR,
};

void (**pre_merge_hooks)(struct sm_state *cur, struct sm_state *other);

struct scope_container {
	void *fn;
	void *data;
};
ALLOCATOR(scope_container, "scope hook functions");
DECLARE_PTR_LIST(scope_hook_list, struct scope_container);
DECLARE_PTR_LIST(scope_hook_stack, struct scope_hook_list);
static struct scope_hook_stack *scope_hooks;

void add_hook(void *func, enum hook_type type)
{
	struct hook_container *container = __alloc_hook_container(0);

	container->hook_type = type;
	container->fn = func;

	add_ptr_list(&hook_array[type], container);
}

void add_merge_hook(int client_id, merge_func_t *func)
{
	struct hook_container *container = __alloc_hook_container(0);
	container->owner = client_id;
	container->fn = func;
	add_ptr_list(&merge_funcs, container);
}

void add_unmatched_state_hook(int client_id, unmatched_func_t *func)
{
	struct hook_container *container = __alloc_hook_container(0);
	container->owner = client_id;
	container->fn = func;
	add_ptr_list(&unmatched_state_funcs, container);
}

void add_pre_merge_hook(int client_id, void (*hook)(struct sm_state *cur, struct sm_state *other))
{
	pre_merge_hooks[client_id] = hook;
}

static void pass_expr_to_client(void *fn, void *data)
{
	((expr_func *)fn)((struct expression *)data);
}

static void pass_stmt_to_client(void *fn, void *data)
{
	((stmt_func *)fn)((struct statement *)data);
}

static void pass_sym_to_client(void *fn, void *data)
{
	((sym_func *)fn)((struct symbol *)data);
}

static void pass_sym_list_to_client(void *fn, void *data)
{
	((sym_list_func *)fn)((struct symbol_list *)data);
}

void __pass_to_client(void *data, enum hook_type type)
{
	struct hook_container *container;

	FOR_EACH_PTR(hook_array[type], container) {
		switch (data_types[type]) {
		case EXPR_PTR:
			pass_expr_to_client(container->fn, data);
			break;
		case STMT_PTR:
			pass_stmt_to_client(container->fn, data);
			break;
		case SYMBOL_PTR:
			pass_sym_to_client(container->fn, data);
			break;
		case SYM_LIST_PTR:
			pass_sym_list_to_client(container->fn, data);
			break;
		}
	} END_FOR_EACH_PTR(container);
}

void __pass_case_to_client(struct expression *switch_expr,
			   struct range_list *rl)
{
	typedef void (case_func)(struct expression *switch_expr,
				 struct range_list *rl);
	struct hook_container *container;

	FOR_EACH_PTR(hook_array[CASE_HOOK], container) {
		((case_func *)container->fn)(switch_expr, rl);
	} END_FOR_EACH_PTR(container);
}

int __has_merge_function(int client_id)
{
	struct hook_container *tmp;

	FOR_EACH_PTR(merge_funcs, tmp) {
		if (tmp->owner == client_id)
			return 1;
	} END_FOR_EACH_PTR(tmp);
	return 0;
}

struct smatch_state *__client_merge_function(int owner,
					     struct smatch_state *s1,
					     struct smatch_state *s2)
{
	struct smatch_state *tmp_state;
	struct hook_container *tmp;

	/* Pass NULL states first and the rest alphabetically by name */
	if (!s2 || (s1 && strcmp(s2->name, s1->name) < 0)) {
		tmp_state = s1;
		s1 = s2;
		s2 = tmp_state;
	}

	FOR_EACH_PTR(merge_funcs, tmp) {
		if (tmp->owner == owner)
			return ((merge_func_t *)tmp->fn)(s1, s2);
	} END_FOR_EACH_PTR(tmp);
	return &undefined;
}

struct smatch_state *__client_unmatched_state_function(struct sm_state *sm)
{
	struct hook_container *tmp;

	FOR_EACH_PTR(unmatched_state_funcs, tmp) {
		if (tmp->owner == sm->owner)
			return ((unmatched_func_t *)tmp->fn)(sm);
	} END_FOR_EACH_PTR(tmp);
	return &undefined;
}

void call_pre_merge_hook(struct sm_state *cur, struct sm_state *other)
{
	if (cur->owner >= num_checks)
		return;

	if (pre_merge_hooks[cur->owner])
		pre_merge_hooks[cur->owner](cur, other);
}

static struct scope_hook_list *pop_scope_hook_list(struct scope_hook_stack **stack)
{
	struct scope_hook_list *hook_list;

	hook_list = last_ptr_list((struct ptr_list *)*stack);
	delete_ptr_list_last((struct ptr_list **)stack);
	return hook_list;
}

static void push_scope_hook_list(struct scope_hook_stack **stack, struct scope_hook_list *l)
{
	add_ptr_list(stack, l);
}

void add_scope_hook(scope_hook *fn, void *data)
{
	struct scope_hook_list *hook_list;
	struct scope_container *new;

	if (!scope_hooks)
		return;
	hook_list = pop_scope_hook_list(&scope_hooks);
	new = __alloc_scope_container(0);
	new->fn = fn;
	new->data = data;
	add_ptr_list(&hook_list, new);
	push_scope_hook_list(&scope_hooks, hook_list);
}

void __push_scope_hooks(void)
{
	push_scope_hook_list(&scope_hooks, NULL);
}

void __call_scope_hooks(void)
{
	struct scope_hook_list *hook_list;
	struct scope_container *tmp;

	if (!scope_hooks)
		return;

	hook_list = pop_scope_hook_list(&scope_hooks);
	FOR_EACH_PTR(hook_list, tmp) {
		((scope_hook *)tmp->fn)(tmp->data);
		__free_scope_container(tmp);
	} END_FOR_EACH_PTR(tmp);
}

void add_array_initialized_hook(void (*hook)(struct expression *array, int nr))
{
	struct hook_container *container = __alloc_hook_container(0);

	container->fn = hook;

	add_ptr_list(&array_init_hooks, container);
}

void __call_array_initialized_hooks(struct expression *array, int nr)
{
	struct hook_container *tmp;

	FOR_EACH_PTR(array_init_hooks, tmp) {
		((array_init_hook *)tmp->fn)(array, nr);
	} END_FOR_EACH_PTR(tmp);
}

void allocate_hook_memory(void)
{
	pre_merge_hooks = malloc(num_checks * sizeof(*pre_merge_hooks));
	memset(pre_merge_hooks, 0, num_checks * sizeof(*pre_merge_hooks));
}

void call_void_fns(struct void_fn_list *list)
{
	void_fn *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)();
	} END_FOR_EACH_PTR(fn);
}

void call_expr_fns(struct expr_fn_list *list, struct expression *expr)
{
	expr_func *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)(expr);
	} END_FOR_EACH_PTR(fn);
}

void call_stmt_fns(struct stmt_fn_list *list, struct statement *stmt)
{
	stmt_func *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)(stmt);
	} END_FOR_EACH_PTR(fn);
}

void call_sym_fns(struct sym_fn_list *list, struct symbol *sym)
{
	sym_func *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)(sym);
	} END_FOR_EACH_PTR(fn);
}

void call_name_sym_fns(struct name_sym_fn_list *list, struct expression *expr, const char *name, struct symbol *sym)
{
	name_sym_hook *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)(expr, name, sym);
	} END_FOR_EACH_PTR(fn);
}

void call_string_hooks(struct string_hook_list *list, struct expression *expr, const char *str)
{
	string_hook *fn;

	FOR_EACH_PTR(list, fn) {
		(fn)(expr, str);
	} END_FOR_EACH_PTR(fn);
}

