/*
 * eval.c - String to parameter
 *
 * 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"


int eval_name(const struct format *fmt, const char *s, struct value *res)
{
	(void) fmt;
	res->u.s = s;
	return 1;
}


static int lookup_name(const struct names *name, const char *s)
{
	const struct equiv *eq;
	int n;

	for (n = 0; name; n++) {
		for (eq = name->equiv; eq; eq = eq->next)
			if (eq->name == s)
				return n;
		name = name->next;
	}
	return -1;
}


int eval_set(const struct format *fmt, const char *s, struct value *res)
{
	int n;

	bitset_zero(&res->u.set);
	n = lookup_name(fmt->u.set, s);
	if (n < 0)
		return 0;
	bitset_set(&res->u.set, n);
	return 1;
}


static int decode_mult(char c, double *mult)
{
	switch (c) {
	case 'f':
		*mult = 1e-15;
		return 1;
	case 'p':
		*mult = 1e-12;
		return 1;
	case 'n':
		*mult = 1e-9;
		return 1;
	case 'u':
		*mult = 1e-6;
		return 1;
	case 'm':
		*mult = 1e-3;
		return 1;
	case 'k':
		*mult = 1e3;
		return 1;
	case 'M':
		*mult = 1e6;
		return 1;
	case 'G':
		*mult = 1e9;
		return 1;
	default:
		return 0;
	}
}


static int strip_unit(const char *s, const char **stop, const char *unit,
    double *mult)
{
	int n;

	if (unit) {
		n = strlen(unit);
		if (*stop-n < s)
			return 0;
		if (!strcmp(*stop-n, unit))
			*stop -= n;
	}
	if (*stop > s) {
		if (decode_mult((*stop)[-1], mult))
			(*stop)--;
		else
			*mult = 1;
	} else {
		*mult = 1;
	}
	return 1;
}


int eval_abs(const struct format *fmt, const char *s,
    struct value *res)
{
	const char *slash, *stop;
	char *end;
	double mult, a, b;

	stop = strchr(s, 0);
	if (!strip_unit(s, &stop, fmt->u.abs, &mult))
		return 0;
	slash = strchr(s, '/');
	if (!slash) {
		res->u.abs = strtod(s, &end)*mult;
		return end == stop;
	}
	a = strtod(s, &end);
	if (end != slash)
		return 0;
	b = strtod(slash+1, &end);
	if (end != stop)
		return 0;
	res->u.abs = a/b*mult;
	return 1;
}


static int plusminus(const char *s, const char *slash, const char *stop,
    double *plus, double *minus)
{
	char *end;

	if (s == slash || slash == stop)
		return 0;

	/* -M/+P */
	if (*s == '-') {
		if (slash[1] != '+')
			return 0;
		*minus = strtod(s+1, &end);
		if (end != slash)
			return 0;
		*plus = strtod(slash+2, &end);
		return end == stop;
	}

	/* +P/-M */
	if (*s == '+') {
		if (slash[1] != '-')
			return 0;
		*plus = strtod(s+1, &end);
		if (end != slash)
			return 0;
		*minus = strtod(slash+2, &end);
		return end == stop;
	}

	/* M/P */
	*minus = strtod(s, &end);
	if (end != slash)
		return 0;
	*plus = strtod(slash+1, &end);
	return end == stop;
}


static int relative(const char *s, const char *stop, double *plus,
    double *minus)
{
	const char *slash;
	char *end;

	slash = strchr(s, '/');
	if (slash >= stop)
		return 0;
	if (slash)
		return plusminus(s, slash, stop, plus, minus);
	*plus = *minus = strtod(s, &end);
	return *plus >= 0 && end == stop;
}


int eval_rel(const struct format *fmt, const char *s,
    struct value *res)
{
	const char *perc, *stop;
	double mult = 1;

	perc = strchr(s, '%');
	res->u.rel.fract = !!perc;
	if (perc) {
		if (perc[1])
			return 0;
		if (!relative(s, perc, &res->u.rel.plus, &res->u.rel.minus))
			return 0;
		res->u.rel.plus /= 100;
		res->u.rel.minus /= 100;
		return 1;
	}
	assert(fmt->u.rel->ops == &param_ops_abs);
	stop = strchr(s, 0);
	if (!strip_unit(s, &stop, fmt->u.rel->u.abs, &mult))
		return 0;
	if (!relative(s, stop, &res->u.rel.plus, &res->u.rel.minus))
		return 0;
	res->u.rel.plus *= mult;
	res->u.rel.minus *= mult;
	return 1;
}


int evaluate(const struct format *fmt, const char *s, struct value *res)
{
	return fmt->ops->eval(fmt, s, res);
}