/*
 * Strictly-ordered, singly-linked list to represent disjoint node ranges
 * of the type 'a' (single node) or 'a-b' (range, with a < b).
 *
 * For example, '1,7-8,20,33-29'
 *
 * Copyright (c) 2009-2011 Centro Svizzero di Calcolo Scientifico (CSCS)
 * Licensed under the GPLv2.
 */
#include "../basil_alps.h"
#define CRAY_MAX_DIGITS	5	/* nid%05d format */

/* internal constructor */
static struct nodespec *ns_new(uint32_t start, uint32_t end)
{
	struct nodespec *new = calloc(1, sizeof(*new));

	if (new) {
		new->start = start;
		new->end   = end;
	}
	return new;
}

/**
 * ns_add_range  -  Insert/merge new range into existing nodespec list.
 * @head:       head of the ordered list
 * @new_start:  start value of node range to insert
 * @new_end:    end value of node range to insert
 *
 * Maintains @head as duplicate-free list, ordered in ascending order of
 * node-specifier intervals, with a gap of at least 2 between adjacent entries.
 * Returns 0 if ok, -1 on failure.
 */
static int ns_add_range(struct nodespec **head,
			uint32_t new_start,
			uint32_t new_end)
{
	struct nodespec *cur = *head, *next;

	assert(new_start <= new_end);

	if (cur == NULL || new_end + 1 < cur->start) {
		*head = ns_new(new_start, new_end);
		if (*head == NULL)
			return -1;
		(*head)->next = cur;
		return 0;
	}

	for (next = cur->next;
	     new_start > cur->end + 1;
	     cur = next, next = cur->next)
		if (next == NULL || new_end + 1 < next->start) {
			next = ns_new(new_start, new_end);
			if (next == NULL)
				return -1;
			next->next = cur->next;
			cur->next  = next;
			return 0;
		}

	/* new_start <= cur->end + 1 */
	if (new_start < cur->start)
		cur->start = new_start;

	if (new_end <= cur->end)
		return 0;
	cur->end = new_end;

	while ((next = cur->next) && next->start <= new_end + 1) {
		if (next->end > new_end)
			cur->end = next->end;
		cur->next = next->next;
		free(next);
	}
	/* next == NULL || next->start > new_end + 1 */

	return 0;
}

/** Add a single node (1-element range) */
int ns_add_node(struct nodespec **head, uint32_t node_id)
{
	return ns_add_range(head, node_id, node_id);
}

/* count the number of nodes starting at @head */
static int ns_count_nodes(const struct nodespec *head)
{
	const struct nodespec *cur;
	uint32_t node_count = 0;

	for (cur = head; cur; cur = cur->next)
		node_count += cur->end - cur->start + 1;

	return node_count;
}

/**
 * ns_ranged_string - Write compressed node specification to buffer.
 * @head:   start of nodespec list
 * @buf:    buffer to write to
 * @buflen: size of @buf
 * Returns number of characters written if successful, -1 on overflow.
 */
static ssize_t ns_ranged_string(const struct nodespec *head,
				char *buf, size_t buflen)
{
	const struct nodespec *cur;
	ssize_t n, len = 0;

	for (cur = head; cur; cur = cur->next) {
		if (cur != head) {
			n = snprintf(buf + len, buflen - len, ",");
			if (n < 0 || (len += n) >= buflen)
				return -1;
		}

		n = snprintf(buf + len, buflen - len, "%u", cur->start);
		if (n < 0 || (len += n) >= buflen)
			return -1;

		if (cur->start != cur->end) {
			n = snprintf(buf + len, buflen - len, "-%u", cur->end);
			if (n < 0 || (len += n) >= buflen)
				return -1;
		}
	}
	return len;
}

/* Compress @head into nodestring. Result must be free()d. */
char *ns_to_string(const struct nodespec *head)
{
	char *buf = NULL;
	size_t size = ns_count_nodes(head);

	if (size) {
		/* Over-estimation: using all digits, plus either '-' or '\0' */
		size *= CRAY_MAX_DIGITS + 1;

		buf = malloc(size);
		if (buf == NULL)
			fatal("can not allocate %d", (int)size);

		if (ns_ranged_string(head, buf, size) < 0)
			fatal("can not expand nodelist expression");
	}
	return buf;
}
