/*
 * Copyright 2009 Red Hat Inc., Durham, North Carolina.
 * All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software 
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 * Authors:
 *      Lukas Kuklinek <lkuklinek@redhat.com>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#include "list.h"
static inline bool _oscap_iterator_has_more_internal(const struct oscap_iterator *it);

struct oscap_list *oscap_list_new(void)
{
	struct oscap_list *list = calloc(1, sizeof(struct oscap_list));
	return list;
}

void oscap_create_lists(struct oscap_list **first, ...)
{
	va_list ap;
	va_start(ap, first);
	for (struct oscap_list **cur = first; cur != NULL; cur = va_arg(ap, struct oscap_list **))
		*cur = oscap_list_new();
	va_end(ap);
}

bool oscap_list_add(struct oscap_list * list, void *value)
{
	__attribute__nonnull__(list);
	if (value == NULL) return false;

	struct oscap_list_item *item = malloc(sizeof(struct oscap_list_item));
	item->next = NULL;
	item->data = value;
	++list->itemcount;

	if (list->last == NULL)
		list->first = list->last = item;
	else {
		list->last->next = item;
		list->last = item;
	}
	return true;
}

bool oscap_list_push(struct oscap_list *list, void *value)
{
	return oscap_list_add(list,value);
}

bool oscap_list_pop(struct oscap_list *list, oscap_destruct_func destructor)
{
	if (list == NULL || list->first == NULL) return false;
	struct oscap_list_item *cur = list->first, *prev = NULL;

	while (cur != list->last) {
		prev = cur;
		cur = cur->next;
	}

	if (destructor) destructor(cur->data);
	free(cur);

	list->last = prev;
	if (prev) prev->next = NULL;
	else list->first = NULL;

	--list->itemcount;

	return true;
}

bool oscap_list_remove(struct oscap_list *list, void *value, oscap_cmp_func compare, oscap_destruct_func destructor)
{
	if (list == NULL || list->first == NULL) return false;
	struct oscap_list_item *cur = list->first, *prev = NULL;

	while (cur != list->last && !compare(cur->data, value)) {
		prev = cur;
		cur = cur->next;
	}

	if (compare(cur->data, value)) {
		if (prev)
			prev->next = cur->next;
		else
			list->first = cur->next;

		if (cur == list->last)
			list->last = prev;

		if (destructor) destructor(cur->data);
		free(cur);

		--list->itemcount;
		return true;
	}

	return false;
}

struct oscap_list * oscap_list_clone(const struct oscap_list * list, oscap_clone_func cloner)
{
        if (list == NULL) 
            return NULL;

        struct oscap_list       * copy = oscap_list_new();
        struct oscap_list_item  * item;

        item = list->first;
        while (item != NULL) {
                if (cloner)
                    oscap_list_add(copy, cloner(item->data));
                else oscap_list_add(copy, item->data);
                item = item->next;
        }

        return copy;
}

struct oscap_list *oscap_list_destructive_join(struct oscap_list *list1, struct oscap_list *list2)
{
	if (list1->first == NULL)
		list1->first = list2->first;
	else list1->last->next = list2->first;
	if (list2->last != NULL) list1->last = list2->last;
	list1->itemcount += list2->itemcount;
	free(list2);
	return list1;
}

int oscap_list_get_itemcount(struct oscap_list *list)
{
	__attribute__nonnull__(list);
	return list->itemcount;
}

void oscap_list_free(struct oscap_list *list, oscap_destruct_func destructor)
{
	struct oscap_list_item *item, *to_del;
	if (list) {
		item = list->first;
		while (item != NULL) {
			to_del = item;
			item = item->next;
			if (destructor)
				destructor(to_del->data);
			free(to_del);
		}
		free(list);
	}
}

void oscap_list_free0(struct oscap_list *list)
{
	oscap_list_free(list, NULL);
}

bool oscap_ptr_cmp(void *node1, void *node2)
{
	return node1 == node2;
}

void* oscap_list_find(struct oscap_list *list, void *what, oscap_cmp_func compare)
{
	if (list == NULL) return false;
	if (compare == NULL) compare = oscap_ptr_cmp;

	struct oscap_iterator *it = oscap_iterator_new(list);

	while (_oscap_iterator_has_more_internal(it)) {
		void *item = oscap_iterator_next(it);
		if (compare(item, what)) {
			oscap_iterator_free(it);
			return item;
		}
	}

	oscap_iterator_free(it);
	return NULL;
}

bool oscap_list_contains(struct oscap_list *list, void *what, oscap_cmp_func compare)
{
	return oscap_list_find(list, what, compare) != NULL;
}

void oscap_list_dump(struct oscap_list *list, oscap_dump_func dumper, int depth)
{
	if (list == NULL) {
		printf(" (NULL list)\n");
		return;
	}
	printf(" (list, %u item%s)\n", (unsigned)list->itemcount, (list->itemcount == 1 ? "" : "s"));
	struct oscap_list_item *item = list->first;
	while (item) {
		dumper(item->data, depth);
		item = item->next;
	}
}

static bool oscap_iterator_no_filter(void *foo, void *bar)
{
	return true;
}

static inline void oscap_iterator_find_nearest(struct oscap_iterator *it)
{
	__attribute__nonnull__(it);
	__attribute__nonnull__(it->list);

	do {
		it->cur = (it->cur ? it->cur->next : it->list->first);
	} while (it->cur && !it->filter(it->cur->data, it->user_data) && _oscap_iterator_has_more_internal(it));
}

void *oscap_iterator_new(struct oscap_list *list)
{
	struct oscap_iterator *it = calloc(1, sizeof(struct oscap_iterator));
	it->cur = NULL;
	it->filter = oscap_iterator_no_filter;
	it->list = list;
	return it;
}

void *oscap_iterator_new_filter(struct oscap_list *list, oscap_filter_func filter, void *user_data)
{
	struct oscap_iterator *it = oscap_iterator_new(list);
	it->filter = filter;
	it->user_data = user_data;
	return it;
}

size_t oscap_iterator_get_itemcount(const struct oscap_iterator * it)
{
	__attribute__nonnull__(it);
	return it->list->itemcount;
}

void *oscap_iterator_detach(struct oscap_iterator *it)
{
	__attribute__nonnull__(it);

	if (!it->cur)
		return NULL;

	struct oscap_list_item *item = it->cur;
	void *value = item->data;

	assert(it->list->first != NULL && it->list->last != NULL);

	if (it->list->first == it->list->last) {
		assert(it->list->first == it->list->last);
		assert(it->list->first == item);
		it->list->first = it->list->last = NULL;
		it->cur = NULL;
	} else if (item == it->list->first) {
		assert(it->list->first != it->list->last);
		assert(item->next != NULL);
		it->list->first = item->next;
		it->cur = NULL;
	} else {
		struct oscap_list_item *cur = it->list->first;
		while (cur->next != item) {
			assert(cur->next != NULL);
			cur = cur->next;
		}
		assert(cur->next == item);
		cur->next = item->next;
		if (item == it->list->last)
			it->list->last = cur;
		it->cur = cur;
	}

	free(item);
	--it->list->itemcount;
	return value;
}

void oscap_iterator_free(struct oscap_iterator *it)
{
	free(it);
}

void *oscap_iterator_next(struct oscap_iterator *it)
{
	__attribute__nonnull__(it);
	oscap_iterator_find_nearest(it);
	return it->cur->data;
}

static inline bool _oscap_iterator_has_more_internal(const struct oscap_iterator *it)
{
	return (!it->cur && it->list->first) || (it->cur && it->cur->next);
}

bool oscap_iterator_has_more(struct oscap_iterator *it)
{
	if (!it || !it->list || !it->list->first)
		return false;
	if (it->cur == NULL) {
		if (it->filter(it->list->first->data, it->user_data))
			return true;
		else
			it->cur = it->list->first;
	}
	while (it->cur->next && !it->filter(it->cur->next->data, it->user_data))
		it->cur = it->cur->next;
	return it->cur->next != NULL;
}

void oscap_iterator_reset(struct oscap_iterator * it)
{
    it->cur = NULL;
}

struct oscap_stringlist *oscap_stringlist_clone(struct oscap_stringlist *list)
{
    void *clone = oscap_list_new(); // oscap_stringlist (or oscap_list)
    OSCAP_FOR_STR(str, oscap_stringlist_get_strings(list))
        oscap_list_add(clone, oscap_strdup(str));
    return clone;
}

struct oscap_string_iterator *oscap_stringlist_get_strings(const struct oscap_stringlist* list)
{
	return oscap_iterator_new((struct oscap_list *) list);
}

bool oscap_stringlist_add_string(struct oscap_stringlist* list, const char *str)
{
	return oscap_list_add((struct oscap_list *) list, oscap_strdup(str));
}

struct oscap_stringlist * oscap_stringlist_new(void)
{
    return (struct oscap_stringlist *) oscap_list_new();
}

void oscap_stringlist_free(struct oscap_stringlist *list)
{
	oscap_list_free((struct oscap_list *) list, free);
}

OSCAP_ITERATOR_GEN_T(const char *, oscap_string)
OSCAP_ITERATOR_REMOVE_T(const char *, oscap_string, free)
OSCAP_ITERATOR_GEN(oscap_stringlist)
OSCAP_ITERATOR_REMOVE(oscap_stringlist, oscap_stringlist_free)
    /*OSCAP_ITERATOR_RESET(oscap_string)*/


#define OSCAP_DEFAULT_HSIZE 389
static inline unsigned int oscap_htable_hash(const char *str, size_t htable_size)
{
	unsigned h = 0;
	unsigned char *p;
	for (p = (unsigned char *)str; *p != '\0'; p++)
		h = (97 * h) + *p;
	return h % htable_size;
}

struct oscap_htable *oscap_htable_new1(oscap_compare_func cmp, size_t hsize)
{
	struct oscap_htable *t;
    
    assert(hsize > 0);

	t = malloc(sizeof(struct oscap_htable));
	if (t == NULL)
		return NULL;
	t->hsize = hsize;
	t->itemcount = 0;
	t->table = calloc(hsize, sizeof(struct oscap_htable_item *));
	if (t->table == NULL) {
		free(t);
		return NULL;
	}
	t->cmp = cmp;
	return t;
}

struct oscap_htable * oscap_htable_clone(const struct oscap_htable * table, oscap_clone_func cloner)
{
	struct oscap_htable *t = oscap_htable_new();
	if (t == NULL)
		return NULL;

	for (size_t i = 0; i < table->hsize; ++i) {
		struct oscap_htable_item *item = table->table[i];
		while (item != NULL) {
			oscap_htable_add(t, item->key, (void *) cloner(item->value));
			item = item->next;
		}
	}
	
	return t;
}

static int oscap_htable_cmp(const char *s1, const char *s2)
{
	if (s1 == NULL)
		return -1;
	if (s2 == NULL)
		return 1;
	return strcmp(s1, s2);
}

struct oscap_htable *oscap_htable_new(void)
{
	return oscap_htable_new1(oscap_htable_cmp, OSCAP_DEFAULT_HSIZE);
}

static struct oscap_htable_item *oscap_htable_lookup(struct oscap_htable *htable, const char *key)
{
	__attribute__nonnull__(htable);
	if (key == NULL)
		return NULL;
	unsigned int hashcode = oscap_htable_hash(key, htable->hsize);
	struct oscap_htable_item *htitem = htable->table[hashcode];
	while (htitem != NULL) {
		if (htable->cmp(htitem->key, key) == 0)
			return htitem;
		htitem = htitem->next;
	}
	return NULL;
}

bool oscap_htable_add(struct oscap_htable * htable, const char *key, void *item)
{
	__attribute__nonnull__(htable);
	/*
	   unsigned int hashcode = oscap_htable_hash(key, htable->hsize);
	   struct oscap_htable_item* htitem = htable->table[hashcode];
	   while(htitem != NULL)
	   {
	   if( (*(htable->cmp))(htitem->key, key) == 0) return false;
	   if(htitem->next == NULL) break;
	   htitem = htitem->next;
	   }
	   // htitem points to the last item
	 */
	if (oscap_htable_lookup(htable, key) != NULL)
		return false;
	unsigned int hashcode = oscap_htable_hash(key, htable->hsize);
	struct oscap_htable_item *newhtitem;
	newhtitem = malloc(sizeof(struct oscap_htable_item));
	newhtitem->key = oscap_strdup(key);
	newhtitem->value = item;
	newhtitem->next = htable->table[hashcode];
	htable->table[hashcode] = newhtitem;
	htable->itemcount++;
	return true;
}

void *oscap_htable_detach(struct oscap_htable *htable, const char *key)
{
	struct oscap_htable_item *htitem = oscap_htable_lookup(htable, key);
	if (htitem) {
		void *val = htitem->value;
		free(htitem->key);
		htitem->key = NULL;
		htitem->value = NULL;
		htable->itemcount--;
		return val;
	}
	return NULL;
}

void *oscap_htable_get(struct oscap_htable *htable, const char *key)
{
	__attribute__nonnull__(htable);
	struct oscap_htable_item *htitem = oscap_htable_lookup(htable, key);
	return htitem ? htitem->value : NULL;
}

void oscap_print_depth(int);

void oscap_htable_dump(struct oscap_htable *htable, oscap_dump_func dumper, int depth)
{
	if (htable == NULL) {
		printf(" (NULL hash table)\n");
		return;
	}
	printf(" (hash table, %u item%s)\n", (unsigned)htable->itemcount, (htable->itemcount == 1 ? "" : "s"));
	int i;
	for (i = 0; i < (int)htable->hsize; ++i) {
		struct oscap_htable_item *item = htable->table[i];
		while (item) {
			oscap_print_depth(depth);
			printf("'%s':\n", item->key);
			dumper(item->value, depth + 1);
			item = item->next;
		}
	}
}

void oscap_htable_free(struct oscap_htable *htable, oscap_destruct_func destructor)
{
	if (htable) {
		size_t ht;
		struct oscap_htable_item *cur, *next;

		for (ht = 0; ht < htable->hsize; ++ht) {
			cur = htable->table[ht];
			while (cur) {
				next = cur->next;
				free(cur->key);
				if (destructor)
					destructor(cur->value);
				free(cur);
				cur = next;
			}
		}

		free(htable->table);
		free(htable);
	}
}

void oscap_htable_free0(struct oscap_htable *htable)
{
	oscap_htable_free(htable, NULL);
}

struct oscap_htable_iterator {
	struct oscap_htable *htable;	// Table we iterate through
	struct oscap_htable_item *cur;	// The current item
	size_t hpos;			// Line on which we are now
};

struct oscap_htable_iterator *
oscap_htable_iterator_new(struct oscap_htable *htable)
{
	struct oscap_htable_iterator *hit = calloc(1, sizeof(struct oscap_htable_iterator));
	hit->htable = htable;
	hit->cur = NULL;
	hit->hpos = 0;
	return hit;
}

bool
oscap_htable_iterator_has_more(struct oscap_htable_iterator *hit)
{
	__attribute__nonnull__(hit);
	if (hit->htable == NULL)
		return false;
	size_t i = hit->hpos;
	if (hit->cur != NULL) {
		if (hit->cur->next != NULL)
			return true;
		if (i + 1 >= hit->htable->hsize)
			return false;
		i++;
	}
	for (; i < hit->htable->hsize; i++) {
		if (hit->htable->table[i] == NULL)
			continue;
		if (i != hit->hpos)
			hit->hpos = i - 1;
		return true;
	}
	hit->hpos = i;
	return false;
}

const struct oscap_htable_item *
oscap_htable_iterator_next(struct oscap_htable_iterator *hit)
{
	__attribute__nonnull__(hit);
	if (hit->cur != NULL) {
		if (hit->cur->next != NULL) {
			hit->cur = hit->cur->next;
			return hit->cur;
		}
		if (hit->hpos + 1 >= hit->htable->hsize) {
			assert(false);
			return NULL;
		}
		hit->hpos++;
	}
	for (; hit->hpos < hit->htable->hsize; hit->hpos++) {
		if (hit->htable->table[hit->hpos] == NULL)
			continue;
		hit->cur = hit->htable->table[hit->hpos];
		return hit->cur;
	}
	assert(false); // no more item found
	return NULL;
}

const char *
oscap_htable_iterator_next_key(struct oscap_htable_iterator *hit)
{
	const struct oscap_htable_item *item = oscap_htable_iterator_next(hit);
	return (item == NULL) ? NULL : item->key;
}

void *
oscap_htable_iterator_next_value(struct oscap_htable_iterator *hit)
{
	const struct oscap_htable_item *item = oscap_htable_iterator_next(hit);
	return (item == NULL) ? NULL : item->value;
}

void oscap_htable_iterator_next_kv(struct oscap_htable_iterator *hit, const char **key, void **value)
{
	const struct oscap_htable_item *item = oscap_htable_iterator_next(hit);
	if (item == NULL)
		return;

	*key = item->key;
	*value = item->value;
}

void
oscap_htable_iterator_reset(struct oscap_htable_iterator *hit)
{
	__attribute__nonnull__(hit);
	hit->cur = NULL;
	hit->hpos = 0;
}

void
oscap_htable_iterator_free(struct oscap_htable_iterator *hit)
{
	free(hit);
}

void oscap_print_depth(int depth)
{
	while (depth--)
		printf("  ");
}
