/*
 * param.c - Parameters and values
 *
 * Copyright 2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdlib.h>
#include <glib.h>

#include "util.h"
#include "lang.h"
#include "param.h"


static GTree *tree;


static int duplicate(const char *a, const struct equiv *b)
{
	while (b) {
		if (a == b->name)
			return 1;
		b = b->next;
	}
	return 0;
}


static void check_duplicates(const struct names *n)
{
	const struct names *na, *nb;
	const struct equiv *ea;

	for (na = n; na; na = na->next) {
		for (ea = na->equiv; ea; ea = ea->next) {
			if (duplicate(ea->name, ea->next))
				yyerrorf("duplicate name \"%s\"", ea->name);
			for (nb = na->next; nb; nb = nb->next)
				if (duplicate(ea->name, nb->equiv))
					yyerrorf("duplicate name \"%s\"",
					    ea->name);
		}
	}
}


static gint comp(gconstpointer a, gconstpointer b)
{
	return a == b ? 0 : a < b ? -1 : 1;
}


void register_nameset(const char *name, const struct names *set)
{
	if (!tree)
		tree = g_tree_new(comp);
	if (g_tree_lookup(tree, name))
		yyerrorf("duplicate name set \"%s\"", name);
	check_duplicates(set);
	g_tree_insert(tree, (void *) name, (void *) set);
}


const struct names *find_nameset(const char *name)
{
	return tree ? g_tree_lookup(tree, name) : NULL;
}


struct lookup_data {
	const struct names *first_name;
	const char *name;
};


static gboolean rev_traverse(gpointer key, gpointer value, gpointer data)
{
	struct lookup_data *d = data;

	if (d->first_name == value) {
		d->name = key;
		return TRUE;
	}
	return FALSE;
}


const char *nameset_rev_lookup(const struct names *first_name)
{
	struct lookup_data data = {
		.first_name = first_name,
		.name = NULL,
	};

	g_tree_foreach(tree, rev_traverse, (void *) &data);
	return data.name;
}


#define	MKOPS(type)					\
	const struct param_ops param_ops_##type = {	\
		.eval	= eval_##type,			\
		.comp	= comp_##type,			\
		.dump	= dump_##type,			\
	}


MKOPS(name);
MKOPS(set);
MKOPS(abs);
MKOPS(rel);


/* ----- Parameters as general-purpose (string) variables ------------------ */


struct param *make_var(const char *name, enum relop op, const char *val)
{
	struct param *var;

	var = alloc_type(struct param);
	var->u.name = unique(name);
	var->op = op;
	var->value.u.s = unique(val);
	var->next = NULL;
	return var;
}


const char *var_lookup(const struct param *vars, const char *name)
{
	while (vars) {
		if (vars->u.name == name)
			return vars->value.u.s;
		vars = vars->next;
	}
	return NULL;
}


/*
 * The variables in list "a" have lower priority than those in list "b",
 * i.e., a variable in "a" is only picked if there is no variable with the
 * same name in "b".
 */

struct param *merge_vars(const struct param *a, const struct param *b)
{
	const struct param *v;
	struct param *res, **last = &res;

	while (a) {
		for (v = b; v; v = v->next)
			if (a->u.name == v->u.name)
				break;
		if (!v) {
			*last = alloc_type(struct param);
			**last = *a;
			last = &(*last)->next;
		}
		a = a->next;
	}
	for (v = b; v; v = v->next) {
		*last = alloc_type(struct param);
		**last = *v;
		last = &(*last)->next;
	}
	*last = NULL;
	return res;
}


void free_vars(struct param *vars)
{
	struct param *next;

	while (vars) {
		next = vars->next;
		free(vars);
		vars = next;
	}
}