/*
** xCollection.c
** 
** xCollection is a generic container of xCollectionData.
** xCollectionData is a one-word value which is typically a pointer to a data 
**  structure holding specific data.
**
** Read the specs in xCollection.h
*/

#include <stdlib.h>   // malloc/free
#include <memory.h>   //memcpy
#include "xCollection.h"

#define MAGIC_VALUE  0x11223344 //arbitrary magic number for safetyFlag (4bytes)

# define OK     0
# define ERROR -1
 
typedef struct xCollectionEntry {
	int safetyFlag;
	xCollectionEntry* next;
	xCollectionEntry* prev;
	xCollectionData data;
} xCollectionEntry;

typedef struct xCollection {
	int safetyFlag;
	int count;
	xCollectionEntry* first;
	xCollectionEntry* last;
	xCollection_DestroyProc* elemDestroyFun;
} xCollection;


xCollection* xCollection_New(xCollection_DestroyProc* fun) {
	xCollection* p = malloc(sizeof(xCollection));
	if (!p) return NULL;
	p->safetyFlag = MAGIC_VALUE;
	p->count = 0;
	p->first = NULL;
	p->last = NULL;

	p->elemDestroyFun = fun;
	return p;
}


void xCollection_Reset(xCollection* store) {
	if (!store) return;
	if (store->safetyFlag != MAGIC_VALUE) return;

	xCollectionEntry* node = store->first;
	while (node) {
		if (store->elemDestroyFun) {
			store->elemDestroyFun(node->data);
		}
		xCollectionEntry* nextNode = node->next;

		node->safetyFlag = !MAGIC_VALUE;
		node->next = node->prev = NULL;   // just for safety

		free(node);
		node = nextNode;
	}
	store->first = store->last = NULL;
	store->count = 0;
}


void xCollection_Destroy(xCollection* store) {
	if (!store) return;
	if (store->safetyFlag != MAGIC_VALUE) return;

	xCollection_Reset(store);
	store->safetyFlag = !MAGIC_VALUE;
	free(store);
}


int xCollection_Size(const xCollection* store) {
	if (!store) return 0;
	if (store->safetyFlag != MAGIC_VALUE) return 0;
	return store->count;
}

xCollectionData xCollection_Value(const xCollectionCursor* cursor) {
	if (!cursor || !cursor->curr) return NULL;
	return cursor->curr->data;
}

xErrorCode xCollection_First(const xCollection* store, xCollectionCursor* cursor) {
	if (!store || !store->first) return ERROR;
	if (store->safetyFlag != MAGIC_VALUE) return ERROR;

	xCollectionEntry* node = store->first;

	cursor->store = store;
	cursor->curr = node;
	cursor->prev =cursor->next = NULL;

	return OK;
}

xErrorCode xCollection_Last(const xCollection* store, xCollectionCursor* cursor) {
	if (!store || !store->last) return ERROR;
	if (store->safetyFlag != MAGIC_VALUE) return ERROR;

	xCollectionEntry* node = store->last;

	cursor->store = store;
	cursor->curr = node;
	cursor->prev = cursor->next = NULL;

	return OK;
}

xErrorCode xCollection_Next(xCollectionCursor* cursor) {
	if (!cursor) return ERROR;
	if (cursor->curr) {
		cursor->curr = cursor->curr->next;
	} else {
		cursor->curr = cursor->next;
		cursor->prev = cursor->next = NULL;
	}
	if (!cursor->curr) return ERROR;

	if (cursor->curr->safetyFlag != MAGIC_VALUE) {
		// must issue an alert !!
		cursor->curr = cursor->prev = cursor->next = NULL;
		return ERROR;
	}
	return OK;
}


xErrorCode xCollection_Prev(xCollectionCursor* cursor) {
	if (!cursor) return ERROR;
	if (cursor->curr) {
		cursor->curr = cursor->curr->prev;
	}
	else {
		cursor->curr = cursor->prev;
		cursor->prev = cursor->next = NULL;
	}
	if (!cursor->curr) return ERROR;

	if (cursor->curr->safetyFlag != MAGIC_VALUE) {
		// must issue an alert !!
		cursor->curr = cursor->prev = cursor->next = NULL;
		return ERROR;
	}
	return OK;
}


xErrorCode xCollection_Add(xCollection* store, const xCollectionData data) {
	if (!store) return ERROR;
	if (store->safetyFlag != MAGIC_VALUE) return ERROR;

	xCollectionEntry* node = (xCollectionEntry*)malloc(sizeof(xCollectionEntry));
	if (!node) return ERROR;

	node->safetyFlag = MAGIC_VALUE;
	node->data = data;

	// append the node
	node->next = NULL;
	if (store->last) {
		xCollectionEntry* ex_last = store->last;
		ex_last->next = node;
		node->prev = ex_last;
	}
	else {
		// is !last  then first is NULL
		store->first = node;
		node->prev = NULL;
	}
	store->last = node;

	++(store->count);
	return OK;
}

xErrorCode xCollection_RemoveAt(xCollectionCursor* cursor) {
	if (cursor==NULL || cursor->curr==NULL) return ERROR;
	if (cursor->curr->safetyFlag != MAGIC_VALUE) return ERROR;  // must issue an alert !!

	xCollection* store = (xCollection*)(cursor->store);
	xCollectionEntry* node = cursor->curr;

	//  unlink the node

	 // first forward ptrs
	if (node == store->first) {
		store->first = node->next;
	} else {
		//  if it's not the first node, then node->prev is not null
		node->prev->next = node->next;
	}
	// first backward ptrs
	if (node == store->last) {
		store->last = node->prev;
	}
	else {
		//  if it's not the last node,  node->next is not null
		node->next->prev = node->prev;
	}

	 // update the cursor; invalidate cursor->curr and save prev/next
	cursor->curr = NULL;
	cursor->prev = node->prev;
	cursor->next = node->next;

	if (store->elemDestroyFun) {
		store->elemDestroyFun(node->data);
	}

	// just for (a sort of weak) safety, in case you try to reuse a deleted element!!
	node->next = node->prev = NULL;
	node->safetyFlag = !MAGIC_VALUE;

	free(node);  // remove also the extra data

	--(store->count);
	return OK;
}


int vxCollection_Apply(xCollection_ApplyProc* fun, const xCollection* store, va_list args) {
	if (!store) return 0;
	if (store->safetyFlag != MAGIC_VALUE) return 0;

	if (!(fun)) return 0;

	xCollectionEntry* node = store->first;
	while (node) {
		int res = fun(node->data, args);
		if (res != 0) return res;

		node = node->next;
	}
	return 0;
}

int xCollection_Apply(xCollection_ApplyProc* fun, const xCollection* store, ...) {
	va_list args;
	va_start(args, store);
	int res = vxCollection_Apply(fun, store, args);
	va_end(args);
	return res;
}


int xCollection_Match(const xCollection* store, xCollection_MatchProc* fun, const void* searchKey, xCollectionCursor* pCursor) {
	if (!store) return 0;
	if (store->safetyFlag != MAGIC_VALUE) return 0;

	if (!(fun)) return 0;

    if (pCursor) {
        pCursor->store = store;
        pCursor->curr = NULL;
        pCursor->next = pCursor->prev = NULL;
    }

	xCollectionEntry* node = store->first;
	while (node) {
		int res = fun(node->data, searchKey);
		if (res != 0) {
            if (pCursor) {
                pCursor->curr = node;
            }
            return res;
        }
		node = node->next;
	}
	return 0;

}
