/*
 * comp.c - Parameter comparison
 *
 * 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 <string.h>
#include <assert.h>

#include "bitset.h"
#include "param.h"


#define	EQUAL(relop)	((relop) & (rel_le | rel_eq | rel_ge))
#define	LESS(relop)	((relop) & (rel_lt | rel_le))
#define	GREATER(relop)	((relop) & (rel_ge | rel_gt))


static int do_comp_name(const char *a, enum relop relop, const char *b)
{
	int cmp;

	if (EQUAL(relop))
		return a == b;
	cmp = strcmp(a, b);
	assert(cmp);
	if (cmp < 0)
		return LESS(relop);
	else
		return GREATER(relop);
}


int comp_name(const struct value *a, enum relop relop, const struct value *b)
{
	return do_comp_name(a->u.s, relop, b->u.s);
}


static int do_comp_set(const struct bitset *a, enum relop relop,
    const struct bitset *b)
{
	assert(!bitset_empty(a));
	assert(!bitset_empty(b));
	if (EQUAL(relop) && bitset_common(a, b))
		return 1;
	if (LESS(relop) && bitset_first(a) < bitset_last(b))
		return 1;
	if (GREATER(relop) && bitset_last(a) > bitset_first(b))
		return 1;
	return 0;
}


int comp_set(const struct value *a, enum relop relop, const struct value *b)
{
	return do_comp_set(&a->u.set, relop, &b->u.set);
}


static int do_comp_abs(double a, enum relop relop, double b)
{
	if (a == b)
		return EQUAL(relop);
	if (a < b)
		return LESS(relop);
	else
		return GREATER(relop);
}


int comp_abs(const struct value *a, enum relop relop, const struct value *b)
{
	return do_comp_abs(a->u.abs, relop, b->u.abs);
}


static int do_comp_rel(const struct rel_value *a, enum relop relop,
    const struct rel_value *b)
{
	if (a->fract != b->fract)
		return 0;
	if (a->plus == b->plus && a->minus == b->minus)
		return EQUAL(relop);
	if (a->plus <= b->plus && a->minus <= b->minus)
		return LESS(relop);
	if (a->plus >= b->plus && a->minus >= b->minus)
		return GREATER(relop);
	return 0;
}


int comp_rel(const struct value *a, enum relop relop, const struct value *b)
{
	return do_comp_rel(&a->u.rel, relop, &b->u.rel);
}


int compare(const struct format *fmt,
    const struct value *a, enum relop relop, const struct value *b)
{
	return fmt->ops->comp(a, relop, b);
}