/*
    Lightweight C Container Library
   
	Copyright (c) 2002 Tobias Koenig <tokoe@kde.org>

	Original code was written by Chris Schlaeger <cs@kde.org>
    
    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#include "ccont.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

struct container_info {
	INDEX count;
	CONTAINER currentNode;
};

typedef struct container_info T_CONTAINER_INFO;
typedef struct container_info* CONTAINER_INFO;

#define rpterr(x) fprintf(stderr, "%s\n", x)

CONTAINER new_ctnr(void)
{
	CONTAINER_INFO info;
	CONTAINER rootNode;

	rootNode = (CONTAINER)malloc(sizeof(T_CONTAINER));

	info = (CONTAINER_INFO)malloc(sizeof(T_CONTAINER_INFO));
	info->count = 0;
	info->currentNode = rootNode;

	rootNode->next = rootNode;
	rootNode->prev = rootNode;
	rootNode->data = info;

	return rootNode;
}

/* O(n) */
void zero_destr_ctnr(CONTAINER rootNode, DESTR_FUNC destr_func)
{
	INDEX counter;

	if (rootNode == NIL || destr_func == NIL) {
		rpterr("destr_ctnr: NIL argument");
		return;
	}

	for (counter = level_ctnr(rootNode); counter > -1; --counter)
		destr_func(pop_ctnr(rootNode));

	assert(rootNode->data);
	free(rootNode->data);

	free(rootNode);
	rootNode = 0;

	return;
}

/* O(n) */
void empty_ctnr(CONTAINER rootNode)
{
	INDEX i;

	if (rootNode == NIL) {
		rpterr("empty_ctnr: NIL argument");
		return;
	}

	for ( i = level_ctnr( rootNode ); i >= 0; --i )
		free( pop_ctnr( rootNode ) );

	return;
}

/* O(1) */
INDEX level_ctnr(CONTAINER rootNode)
{
	if (rootNode == NIL) {
		rpterr("level_ctnr: NIL argument");
		return -1;
	}

	return ((CONTAINER_INFO)rootNode->data)->count;
}

/* This function is never used */
/* O(n) */
void insert_ctnr(CONTAINER rootNode, void* object, INDEX pos)
{
	CONTAINER it;
	INDEX counter = 0;
	
	if (rootNode == NIL || object == NIL) {
		rpterr("insert_ctnr: NIL argument");
		return;
	}

	for (it = rootNode->next; it != rootNode; it = it->next) {
		if (counter == pos) {
			CONTAINER newNode = (CONTAINER)malloc(sizeof(T_CONTAINER));

			newNode->prev = it;
			newNode->next = it->next;
			it->next->prev = newNode;
			it->next = newNode;

			newNode->data = object;
			((CONTAINER_INFO)rootNode->data)->count++;
			return;
		}

		counter++;
	}
}

/* O(1) */
void push_ctnr(CONTAINER rootNode, void* object)
{
	CONTAINER newNode;

	if (rootNode == NIL || object == NIL) {
		rpterr("push_ctnr: NIL argument");
		return;
	}

	newNode = (CONTAINER)malloc(sizeof(T_CONTAINER));
	newNode->next = rootNode;
	newNode->prev = rootNode->prev;
	rootNode->prev->next = newNode;
	rootNode->prev = newNode;

	newNode->data = object;
	((CONTAINER_INFO)rootNode->data)->count++;
}

/* This function is never used */
/* O(n) */
void* remove_at_ctnr(CONTAINER rootNode, INDEX pos)
{
	CONTAINER it;
	INDEX counter = 0;
	void* retval;
	
	if (rootNode == NIL) {
		rpterr("remove_ctnr: NIL argument");
		return NIL;
	}

	for (it = rootNode->next; it != rootNode; it = it->next) {
		if (counter == pos) {
			retval = it->data;

			it->prev->next = it->next;
			it->next->prev = it->prev;

			free(it);

			((CONTAINER_INFO)rootNode->data)->count--;
			return retval;
		}

		counter++;
	}

	return NIL;
}

/* This function is only used within ccont.c */
/* O(1) */
void* pop_ctnr(CONTAINER rootNode)
{
	CONTAINER ptr;
	void* retval;

	if (rootNode == NIL) {
		rpterr("pop_ctnr: NIL argument");
		return NIL;
	}

	if (rootNode->next == rootNode)
		return NIL;

	ptr = rootNode->next;	
	retval = ptr->data;

	rootNode->next = ptr->next;
	ptr->next->prev = rootNode;

	((CONTAINER_INFO)rootNode->data)->count--;

	free(ptr);

	return retval;
}

/* O(n) */
void* get_ctnr(CONTAINER rootNode, INDEX pos)
{
	CONTAINER it;
	INDEX counter = 0;

	if (rootNode == NIL) {
		rpterr("get_ctnr: NIL argument");
		return NIL;
	}

	for (it = rootNode->next; it != rootNode; it = it->next) {
		if (counter == pos)
			return it->data;

		counter++;
	}

	return NIL;
}

/* This should return a reference to the node found instead of its index.
   The index is never used, except as an argument to a O(n) function to
   get a reference to the node. */
/* O(n) */
INDEX search_ctnr(CONTAINER rootNode, COMPARE_FUNC compare_func, void* pattern)
{
	CONTAINER it;
	INDEX counter = 0;

	if (rootNode == NIL || compare_func == NIL || pattern == NIL) {
		rpterr("search_ctnr: NIL argument");
		return -1;
	}
	
	for (it = rootNode->next; it != rootNode; it = it->next) {
		if (compare_func(pattern, it->data) == 0)
			return counter;

		counter++;
	}

	return -1;
}

/* This function is never used */
/* O(n) */
void swap_ctnr(CONTAINER rootNode, INDEX pos1, INDEX pos2)
{
	CONTAINER it, node1 = 0, node2 = 0;
	INDEX counter = 0;
	int found = 0;
	void* tmpData;

	if (rootNode == NIL) {
		rpterr("swap_ctnr: NIL argument");
		return;
	}

	if (pos1 == pos2)
		return;

	for (it = rootNode->next; it != rootNode; it = it->next) {
		if (counter == pos1) {
			node1 = it;
			found++;
			if (found == 2)
				break;
		}
		if (counter == pos2) {
			node2 = it;
			found++;
			if (found == 2)
				break;
		}

		counter++;
	}	

	if (found == 2) {
		tmpData = node1->data;
		node1->data = node2->data;
		node2->data = tmpData;
	}
}

/* O(n log n) */
void bsort_ctnr(CONTAINER rootNode, COMPARE_FUNC compare_func)
{
	/* Use mergesort adapted from https://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.c */
	
	CONTAINER p, q, e, tail, oldhead;
	INDEX insize, nmerges, psize, qsize, i;

	if (rootNode == NIL || compare_func == NIL) {
		rpterr("bsort_ctnr: NIL argument");
		return;
	}

	if (level_ctnr(rootNode) == 0) {
		/* Nothing to sort */
		return;
	}

	insize = 1;

	while (1) {
		p = rootNode->next;
		oldhead = rootNode;	  /* only used for circular linkage */
		rootNode->next = NULL;
		tail = NULL;

		nmerges = 0;  /* count number of merges we do in this pass */

		while (p) {
			nmerges++;  /* there exists a merge to be done */
			/* step `insize' places along from p */
			q = p;
			psize = 0;
			for (i = 0; i < insize; i++) {
				psize++;
				q = (q->next == oldhead ? NULL : q->next);
				if (!q) break;
			}

			/* if q hasn't fallen off end, we have two lists to merge */
			qsize = insize;

			/* now we have two lists; merge them */
			while (psize > 0 || (qsize > 0 && q)) {
				/* decide whether next element of merge comes from p or q */
				if (psize == 0) {
					assert(q);
					/* p is empty; e must come from q. */
					e = q; q = q->next; qsize--;
					if (q == oldhead) q = NULL;
				} else if (qsize == 0 || !q) {
					assert(p);
					/* q is empty; e must come from p. */
					e = p; p = p->next; psize--;
					if (p == oldhead) p = NULL;
				} else if (compare_func(p,q) <= 0) {
					/* First element of p is lower (or same);
					 * e must come from p. */
					e = p; p = p->next; psize--;
					if (p == oldhead) p = NULL;
				} else {
					/* First element of q is lower; e must come from q. */
					e = q; q = q->next; qsize--;
					if (q == oldhead) q = NULL;
				}

				/* add the next element to the merged list */
				if (tail) {
					tail->next = e;
					e->prev = tail;
				} else {
					rootNode->next = e;
					e->prev = rootNode;
				}

				tail = e;
			}

			/* now p has stepped `insize' places along, and q has too */
			p = q;
		}
		
		if (tail)
			tail->next = rootNode;
		rootNode->prev = tail;

		/* If we have done only one merge, we're finished. */
		if (nmerges <= 1) {  /* allow for nmerges==0, the empty list case */
			break;
		}

		/* Otherwise repeat, merging lists twice the size */
		insize *= 2;
	}
}

/* O(1) */
void* first_ctnr(CONTAINER rootNode)
{
	if (rootNode == NIL) {
		rpterr("first_ctnr: NIL argument");
		return NIL;
	}
	
	if (rootNode->next == rootNode)
		return NIL;

	((CONTAINER_INFO)rootNode->data)->currentNode = rootNode->next;

	return rootNode->next->data;
}

/* O(1) */
void* next_ctnr(CONTAINER rootNode)
{
	CONTAINER_INFO info;

	if (rootNode == NIL) {
		rpterr("next_ctnr: NIL argument");
		return NIL;
	}

	info = (CONTAINER_INFO)rootNode->data;

	if (info->currentNode->next == rootNode)
		return NIL;

	info->currentNode = info->currentNode->next;

	return info->currentNode->data;
}

/* O(1) */
void* remove_ctnr(CONTAINER rootNode)
{
	CONTAINER currentNode, tmp;
	CONTAINER_INFO info;
	void* retval;
	
	if (rootNode == NIL) {
		rpterr("remove_curr_ctnr: NIL argument");
		return NIL;
	}

	info = (CONTAINER_INFO)rootNode->data;
	currentNode = info->currentNode;

	if (currentNode == rootNode) { /* should never happen */
		rpterr("remove_curr_ctnr: delete root node");
		return NIL;
	}

	retval = currentNode->data;
	tmp = currentNode->prev;

	currentNode->prev->next = currentNode->next;
	currentNode->next->prev = currentNode->prev;

	free(currentNode);

	info->count--;
	info->currentNode = tmp;

	return retval;
}
