/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License v2 as published by the Free Software Foundation.
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 021110-1307, USA.
 */

#include <strings.h>
#include <string.h>
#include <ctype.h>
#include "common/sort-utils.h"
#include "common/messages.h"

int compare_init(struct compare *comp, const struct sortdef *sortdef)
{
	memset(comp, 0, sizeof(struct compare));
	comp->sortdef = sortdef;
	return 0;
}

int compare_cmp_multi(const void *a, const void *b, const struct compare *comp)
{
	for (int i = 0; i < comp->count; i++) {
		int ret;

		ret = comp->comp[i](a,b);
		if (ret != 0)
			return (comp->invert_map & (1U << i)) ? -ret : ret;
	}
	return 0;
}

int compare_add_sort_key(struct compare *comp, const char *key, bool descending)
{
	int i;

	if (!comp->sortdef)
		return -1;

	for (i = 0; i < 32; i++) {
		if (comp->sortdef[i].name == NULL)
			return -1;
		if (strcasecmp(key, comp->sortdef[i].name) == 0) {
			comp->comp[comp->count] = comp->sortdef[i].comp;
			if (descending)
				comp->invert_map |= (1U << comp->count);
			comp->count++;
			break;
		}
	}
	return 0;
}

/*
 * Append given sort by its @id from associated sortdef.
 *
 * Return: 0  if id is valid
 *         -1 if id not in sortdef
 */
int compare_add_sort_id(struct compare *comp, int id, bool descending)
{
	int i;

	if (!comp->sortdef)
		return -1;

	if (id < 0)
		return -1;

	for (i = 0; i < SORT_MAX_KEYS; i++) {
		if (comp->sortdef[i].name == NULL)
			return -1;
		if (comp->sortdef[i].id == id) {
			comp->comp[comp->count] = comp->sortdef[i].comp;
			if (descending)
				comp->invert_map |= (1U << comp->count);
			comp->count++;
			break;
		}
	}
	return 0;
}

/*
 * Consume word-like list of key names (coma separated) and return its id if
 * found in sortdef. The @next pointer is advanced to the next expected key start.
 * Empty and NULL @next is accepted.
 *
 * Key lookup is case insensitive.
 *
 * Return: id from sortdef if a matching
 *         -1 on error
 *         -2 end of buffer
 */
int compare_parse_key_to_id(const struct compare *comp, const char **next, bool *descending)
{
	const char *tmp = *next, *start = *next;

	if (!comp->sortdef)
		return -1;

	/* No sort string (use defaults), or last. */
	if (!*next || !**next)
		return -2;

	*descending = false;
	do {
		/* End of word. */
		if (*tmp == ',' || *tmp == 0) {
			int desc = 0;

			/* Descending order. */
			if (start[0] == '-') {
				desc = 1;
				*descending = true;
			}

			/* Look up in sortdef. */
			for (int i = 0; comp->sortdef[i].name; i++) {
				int len = strlen(comp->sortdef[i].name);

				if (strncasecmp(start + desc, comp->sortdef[i].name, len) == 0) {
					/* Point to last NUL. */
					*next = tmp;
					/* Or the next valid char. */
					if (*tmp)
						(*next)++;
					return comp->sortdef[i].id;
				}
			}
			/* Not found, report which one. */
			*next = start;
			return -1;
		}
		/* Invalid char found. */
		if (start[0] != '-' && !isalnum(*tmp)) {
			*next = tmp;
			return -1;
		}
		tmp++;
	} while(1);

	/* Not found. */
	*next = start;
	return -1;
}

/* Read id of its associated sort @key.  Key lookup is case insensitive. */
int compare_key_id(const struct compare *comp, const char *key)
{
	if (!comp->sortdef)
		return -1;

	for (int i = 0; comp->sortdef[i].name; i++)
		if (strcasecmp(comp->sortdef[i].name, key) == 0)
			return comp->sortdef[i].id;
	return -1;
}

/* Read sort key name associated to @id. */
const char *compare_id_name(const struct compare *comp, int id)
{
	if (!comp->sortdef)
		return NULL;

	for (int i = 0; comp->sortdef[i].name; i++)
		if (comp->sortdef[i].id == id)
			return comp->sortdef[i].name;
	return NULL;
}

/*
 * Check if the given @id (must exist in the associated sortdef) enabled in
 * @comp.
 */
bool compare_has_id(const struct compare *comp, int id)
{
	int idx;

	if (!comp->sortdef)
		return false;

	idx = -1;
	for (int i = 0; comp->sortdef[i].name; i++)
		if (comp->sortdef[i].id == id)
			idx = i;

	if (idx < 0)
		return false;

	for (int i = 0; i < comp->count; i++)
		if (comp->comp[i] == comp->sortdef[idx].comp)
			return true;
	return false;
}

/*
 * Set up compare structure with associated sortdef from a user specified list
 * of keys.
 */
int compare_setup_sort(struct compare *comp, const struct sortdef *sdef, const char *def)
{
	const char *tmp;
	int id;

	tmp = def;
	do {
		bool descending;

		id = compare_parse_key_to_id(comp, &tmp, &descending);
		if (id == -1) {
			error("unknown sort key: %s", tmp);
			return -1;
		}
		compare_add_sort_id(comp, id, descending);
	} while (id >= 0);

	return 0;
}
