/*
 * (c) 2009 Arnaldo Carvalho de Melo <acme@redhat.com>
 *
 * Licensed under the GPLv2.
 */

#include "strlist.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static struct str_node *str_node__new(const char *s, bool dupstr)
{
	struct str_node *self = malloc(sizeof(*self));

	if (self != NULL) {
		if (dupstr) {
			s = strdup(s);
			if (s == NULL)
				goto out_delete;
		}
		self->s = s;
	}

	return self;

out_delete:
	free(self);
	return NULL;
}

static void str_node__delete(struct str_node *self, bool dupstr)
{
	if (dupstr)
		free((void *)self->s);
	free(self);
}

int strlist__add(struct strlist *self, const char *new_entry)
{
	struct rb_node **p = &self->entries.rb_node;
	struct rb_node *parent = NULL;
	struct str_node *sn;

	while (*p != NULL) {
		int rc;

		parent = *p;
		sn = rb_entry(parent, struct str_node, rb_node);
		rc = strcmp(sn->s, new_entry);

		if (rc > 0)
			p = &(*p)->rb_left;
		else if (rc < 0)
			p = &(*p)->rb_right;
		else
			return -EEXIST;
	}

	sn = str_node__new(new_entry, self->dupstr);
	if (sn == NULL)
		return -ENOMEM;

	rb_link_node(&sn->rb_node, parent, p);
	rb_insert_color(&sn->rb_node, &self->entries);
	++self->nr_entries;

	return 0;
}

int strlist__load(struct strlist *self, const char *filename)
{
	char entry[1024];
	int err;
	FILE *fp = fopen(filename, "r");

	if (fp == NULL)
		return errno;

	while (fgets(entry, sizeof(entry), fp) != NULL) {
		const size_t len = strlen(entry);

		if (len == 0)
			continue;
		entry[len - 1] = '\0';

		err = strlist__add(self, entry);
		if (err != 0)
			goto out;
	}

	err = 0;
out:
	fclose(fp);
	return err;
}

void strlist__remove(struct strlist *self, struct str_node *sn)
{
	rb_erase(&sn->rb_node, &self->entries);
	str_node__delete(sn, self->dupstr);
}

struct str_node *strlist__find(struct strlist *self, const char *entry)
{
	struct rb_node **p = &self->entries.rb_node;
	struct rb_node *parent = NULL;

	while (*p != NULL) {
		struct str_node *sn;
		int rc;

		parent = *p;
		sn = rb_entry(parent, struct str_node, rb_node);
		rc = strcmp(sn->s, entry);

		if (rc > 0)
			p = &(*p)->rb_left;
		else if (rc < 0)
			p = &(*p)->rb_right;
		else
			return sn;
	}

	return NULL;
}

static int strlist__parse_list_entry(struct strlist *self, const char *s)
{
	if (strncmp(s, "file://", 7) == 0)
		return strlist__load(self, s + 7);

	return strlist__add(self, s);
}

int strlist__parse_list(struct strlist *self, const char *s)
{
	char *sep;
	int err;

	while ((sep = strchr(s, ',')) != NULL) {
		*sep = '\0';
		err = strlist__parse_list_entry(self, s);
		*sep = ',';
		if (err != 0)
			return err;
		s = sep + 1;
	}

	return *s ? strlist__parse_list_entry(self, s) : 0;
}

struct strlist *strlist__new(bool dupstr, const char *slist)
{
	struct strlist *self = malloc(sizeof(*self));

	if (self != NULL) {
		self->entries	 = RB_ROOT;
		self->dupstr	 = dupstr;
		self->nr_entries = 0;
		if (slist && strlist__parse_list(self, slist) != 0)
			goto out_error;
	}

	return self;
out_error:
	free(self);
	return NULL;
}

void strlist__delete(struct strlist *self)
{
	if (self != NULL) {
		struct str_node *pos;
		struct rb_node *next = rb_first(&self->entries);

		while (next) {
			pos = rb_entry(next, struct str_node, rb_node);
			next = rb_next(&pos->rb_node);
			strlist__remove(self, pos);
		}
		self->entries = RB_ROOT;
		free(self);
	}
}

struct str_node *strlist__entry(const struct strlist *self, unsigned int idx)
{
	struct rb_node *nd;

	for (nd = rb_first(&self->entries); nd; nd = rb_next(nd)) {
		struct str_node *pos = rb_entry(nd, struct str_node, rb_node);

		if (!idx--)
			return pos;
	}

	return NULL;
}