/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2003 
 *					All rights reserved
 *
 *  This file is part of GPAC / common tools sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */

#include <gpac/m4_tools.h>

/* Chain modes, ONLY ONE CAN BE DEFINED

	linked-list chain
	#define M4_CHAIN_LINKED

	double navigation linked-list chain
	#define M4_CHAIN_DOUBLE_LINKED

	single step memory array
	#define M4_CHAIN_ARRAY

	default mode is array with step aloc of M4_CHAIN_STEP_ALLOC
*/

/*after some tuning, this seems to be the fastest mode on WINCE*/
#ifdef _WIN32_WCE
#define M4_CHAIN_LINKED
#else
#define M4_CHAIN_LINKED
#endif

#define M4_CHAIN_STEP_ALLOC	10


#if defined(M4_CHAIN_LINKED)

typedef struct tagIS
{
	struct tagIS *next;
	void *data;
} ItemSlot;

struct tagChain
{
	struct tagIS *head;
	struct tagIS *tail;
	u32 entryCount;
	s32 foundEntryNumber;
	struct tagIS *foundEntry;
};


Chain * NewChain()
{
	Chain *newChain = (Chain *) malloc(sizeof(Chain));
	if (! newChain) return NULL;
	newChain->head = newChain->foundEntry = NULL;
	newChain->tail = NULL;
	newChain->foundEntryNumber = -1;
	newChain->entryCount = 0;
	return newChain;
}

void DeleteChain(Chain *ptr)
{
	if (!ptr) return;
	while (ptr->entryCount) ChainDeleteEntry(ptr, 0);
	free(ptr);
}

void ChainReset(Chain *ptr)
{
	while (ptr && ptr->entryCount) ChainDeleteEntry(ptr, 0);
}

M4Err ChainAddEntry(Chain *ptr, void* item)
{
	ItemSlot *entry;
    if (! ptr) return M4BadParam;
	entry = (ItemSlot *) malloc(sizeof(ItemSlot));
	if (!entry) return M4OutOfMem;
	entry->data = item;
	entry->next = NULL;
	if (! ptr->head) {
		ptr->head = entry;
		ptr->entryCount = 1;
	} else {
		ptr->entryCount += 1;
		ptr->tail->next = entry;
	}
	ptr->tail = entry;
	ptr->foundEntryNumber = ptr->entryCount - 1;
	ptr->foundEntry = entry;
	return M4OK;
}

u32 ChainGetCount(Chain *ptr)
{
	if (! ptr) return 0;
	return ptr->entryCount;
}

void *ChainGetEntry(Chain *ptr, u32 itemNumber)
{
	ItemSlot *entry;
	u32 i;

	if ((! ptr) || (itemNumber >= ptr->entryCount) ) return NULL;

	if ( itemNumber < (u32) ptr->foundEntryNumber ) {
		ptr->foundEntryNumber = 0;
		ptr->foundEntry = ptr->head;
	}
	entry = ptr->foundEntry;
	for (i = ptr->foundEntryNumber; i < itemNumber; i++ ) {
		entry = entry->next;
	}
	ptr->foundEntryNumber = itemNumber;
	ptr->foundEntry = entry;
	return (void *) entry->data;
}

M4Err ChainDeleteEntry(Chain *ptr, u32 itemNumber)
{
	ItemSlot *tmp, *tmp2;
	u32 i;

	/* !! if head is null (empty list)*/
	if ( (! ptr) || (! ptr->head) || (ptr->head && !ptr->entryCount) || (itemNumber >= ptr->entryCount) ) 
		return M4BadParam;

	/*we delete the head*/
	if (! itemNumber) {
		tmp = ptr->head;
		ptr->head = ptr->head->next;
		ptr->entryCount --;
		ptr->foundEntry = ptr->head;
		ptr->foundEntryNumber = 0;
		free(tmp);
		/*that was the last entry, reset the tail*/
		if (!ptr->entryCount) {
			ptr->tail = ptr->head = ptr->foundEntry = NULL;
			ptr->foundEntryNumber = -1;
		}
		return M4OK;
	}

	tmp = ptr->head;
	i = 0;
	while (i < itemNumber - 1) {
		tmp = tmp->next;
		i++;
	}
	tmp2 = tmp->next;
	tmp->next = tmp2->next;
	/*if we deleted the last entry, update the tail !!!*/
	if (! tmp->next || (ptr->tail == tmp2) ) {
		ptr->tail = tmp;
		tmp->next = NULL;
	}

	free(tmp2);
	ptr->entryCount --;
	ptr->foundEntry = ptr->head;
	ptr->foundEntryNumber = 0;

	return M4OK;
}

M4Err ChainInsertEntry(Chain *ptr, void *item, u32 position)
{
	u32 i;
	ItemSlot *tmp, *tmp2;

	if (!ptr || !item) return M4BadParam;
	/*if last entry or first of an empty chain...*/
	if (position >= ptr->entryCount) return ChainAddEntry(ptr, item);

	tmp2 = (ItemSlot *) malloc(sizeof(ItemSlot));
	tmp2->data = item;
	tmp2->next = NULL;
	/*special case for the head*/
	if (position == 0) {
		tmp2->next = ptr->head;
		ptr->head = tmp2;
		ptr->entryCount ++;
		ptr->foundEntry = tmp2;
		ptr->foundEntryNumber = 0;
		return M4OK;
	}
	tmp = ptr->head;
	for (i = 1; i < position; i++) {
		tmp = tmp->next;
		if (!tmp)
			break;
	}
	tmp2->next = tmp->next;
	tmp->next = tmp2;
	ptr->entryCount ++;
	ptr->foundEntry = tmp2;
	ptr->foundEntryNumber = i;
	return M4OK;
}

#elif defined(M4_CHAIN_DOUBLE_LINKED)


typedef struct tagIS
{
	struct tagIS *next;
	struct tagIS *prev;
	void *data;
} ItemSlot;

struct tagChain
{
	struct tagIS *head;
	struct tagIS *tail;
	u32 entryCount;
	s32 foundEntryNumber;
	struct tagIS *foundEntry;
};


Chain * NewChain()
{
	Chain *newChain = (Chain *) malloc(sizeof(Chain));
	if (! newChain) return NULL;
	newChain->head = newChain->foundEntry = NULL;
	newChain->tail = NULL;
	newChain->foundEntryNumber = -1;
	newChain->entryCount = 0;
	return newChain;
}

void DeleteChain(Chain *ptr)
{
	if (!ptr) return;
	while (ptr->entryCount) {
		ChainDeleteEntry(ptr, 0);
	}
	free(ptr);
}

void ChainReset(Chain *ptr)
{
	while (ptr && ptr->entryCount) ChainDeleteEntry(ptr, 0);
}

M4Err ChainAddEntry(Chain *ptr, void* item)
{
	ItemSlot *entry;
    if (! ptr) return M4BadParam;
	entry = (ItemSlot *) malloc(sizeof(ItemSlot));
	if (!entry) return M4OutOfMem;
	entry->data = item;
	entry->next = entry->prev = NULL;

	if (! ptr->head) {
		ptr->head = entry;
		ptr->entryCount = 1;
	} else {
		ptr->entryCount += 1;
		entry->prev = ptr->tail;
		ptr->tail->next = entry;
	}
	ptr->tail = entry;
	ptr->foundEntryNumber = ptr->entryCount - 1;
	ptr->foundEntry = entry;
	return M4OK;
}


u32 ChainGetCount(Chain *ptr)
{
	if (! ptr) return 0;
	return ptr->entryCount;
}

void *ChainGetEntry(Chain *ptr, u32 itemNumber)
{
	ItemSlot *entry;
	u32 i;

	if (!ptr || !ptr->head || (itemNumber >= ptr->entryCount) ) return NULL;

	if (!itemNumber) {
		ptr->foundEntry = ptr->head;
		ptr->foundEntryNumber = 0;
		return ptr->head->data;
	}

	entry = ptr->foundEntry;
	if ( itemNumber < (u32) ptr->foundEntryNumber ) {
		for (i = ptr->foundEntryNumber; i > itemNumber; i-- ) {
			entry = entry->prev;
		}
	} else {
		for (i = ptr->foundEntryNumber; i < itemNumber; i++ ) {
			entry = entry->next;
		}
	}
	ptr->foundEntryNumber = itemNumber;
	ptr->foundEntry = entry;
	return (void *) entry->data;
}

M4Err ChainDeleteEntry(Chain *ptr, u32 itemNumber)
{
	ItemSlot *tmp;
	u32 i;

	/* !! if head is null (empty list)*/
	if ( (! ptr) || (! ptr->head) || (ptr->head && !ptr->entryCount) || (itemNumber >= ptr->entryCount) ) 
		return M4BadParam;

	/*we delete the head*/
	if (! itemNumber) {
		tmp = ptr->head;
		ptr->head = ptr->head->next;

		ptr->entryCount --;
		ptr->foundEntry = ptr->head;
		ptr->foundEntryNumber = 0;
		free(tmp);

		/*that was the last entry, reset the tail*/
		if (!ptr->entryCount) {
			ptr->tail = ptr->head = ptr->foundEntry = NULL;
			ptr->foundEntryNumber = -1;
		} else {
			ptr->head->prev = NULL;
		}
		return M4OK;
	}
	else if (itemNumber==ptr->entryCount-1) {
		tmp = ptr->tail;
		ptr->tail = tmp->prev;
		ptr->tail->next = NULL;
		ptr->entryCount--;
		if (ptr->foundEntry==tmp) {
			ptr->foundEntry = ptr->tail;
			ptr->foundEntryNumber = ptr->entryCount-1;
		}
		free(tmp);
		return M4OK;
	}

	tmp = ptr->foundEntry;
	if ( itemNumber < (u32) ptr->foundEntryNumber ) {
		for (i = ptr->foundEntryNumber; i > itemNumber; i-- ) {
			tmp = tmp->prev;
		}
	} else {
		for (i = ptr->foundEntryNumber; i < itemNumber; i++ ) {
			tmp = tmp->next;
		}
	}
	tmp->prev->next = tmp->next;
	tmp->next->prev = tmp->prev;
	if (tmp==ptr->foundEntry) ptr->foundEntry = tmp->next;
	free(tmp);
	ptr->entryCount--;
	return M4OK;
}

M4Err ChainInsertEntry(Chain *ptr, void *item, u32 position)
{
	u32 i;
	ItemSlot *tmp, *tmp2;

	if (!ptr || !item) return M4BadParam;
	/*if last entry or first of an empty chain...*/
	if (position >= ptr->entryCount) return ChainAddEntry(ptr, item);
	tmp2 = (ItemSlot *) malloc(sizeof(ItemSlot));
	tmp2->data = item;
	tmp2->next = tmp2->prev = NULL;
	/*special case for the head*/
	if (position == 0) {
		ptr->head->prev = tmp2;
		tmp2->next = ptr->head;
		ptr->head = tmp2;
		ptr->entryCount ++;
		ptr->foundEntry = tmp2;
		ptr->foundEntryNumber = 0;
		return M4OK;
	}

	tmp = ptr->foundEntry;
	if ( position < (u32) ptr->foundEntryNumber ) {
		for (i = ptr->foundEntryNumber; i >= position; i-- ) {
			tmp = tmp->prev;
		}
		tmp = tmp->prev;
	} else {
		for (i = ptr->foundEntryNumber; i < position; i++ ) {
			tmp = tmp->next;
		}
	}
	tmp2->next = tmp->next;
	tmp2->next->prev = tmp2;
	tmp2->prev = tmp;
	tmp2->prev->next = tmp2;
	ptr->entryCount ++;
	ptr->foundEntry = tmp2;
	ptr->foundEntryNumber = position;
	return M4OK;
}

#elif defined(M4_CHAIN_ARRAY)

struct tagChain
{
	void **slots;
	u32 entryCount;
};


Chain * NewChain()
{
	Chain *newChain = (Chain *) malloc(sizeof(Chain));
	if (! newChain) return NULL;
	newChain->slots = NULL;
	newChain->entryCount = 0;
	return newChain;
}

void DeleteChain(Chain *ptr)
{
	if (!ptr) return;
	free(ptr->slots);
	free(ptr);
}

M4Err ChainAddEntry(Chain *ptr, void* item)
{
    if (! ptr) return M4BadParam;

	ptr->entryCount ++;
	if (ptr->slots) {
		ptr->slots = realloc(ptr->slots, ptr->entryCount*sizeof(void*));
	} else {
		ptr->slots = malloc(sizeof(void*)*ptr->entryCount);
	}
	if (!ptr->slots) {
		ptr->entryCount = 0;
		return M4OutOfMem;
	}
	ptr->slots[ptr->entryCount-1] = item;
	return M4OK;
}

void ChainExpand(Chain *ptr, u32 nbItems)
{
}

u32 ChainGetCount(Chain *ptr)
{
	return ptr ? ptr->entryCount : 0;
}

void *ChainGetEntry(Chain *ptr, u32 itemNumber)
{
	if(!ptr || (itemNumber >= ptr->entryCount)) return NULL;
	return ptr->slots[itemNumber];
}

/*WARNING: itemNumber is from 0 to entryCount - 1*/
M4Err ChainDeleteEntry(Chain *ptr, u32 itemNumber)
{
	u32 i;
	if ( !ptr || !ptr->slots || !ptr->entryCount) return M4BadParam;
	i = ptr->entryCount - itemNumber - 1;
	if (i) memmove(&ptr->slots[itemNumber], & ptr->slots[itemNumber +1], sizeof(void *)*i);
	ptr->slots[ptr->entryCount-1] = NULL;
	ptr->entryCount -= 1;
	ptr->slots = realloc(ptr->slots, sizeof(void*)*ptr->entryCount);
	return M4OK;
}


/*WARNING: position is from 0 to entryCount - 1*/
M4Err ChainInsertEntry(Chain *ptr, void *item, u32 position)
{
	u32 i;
	if (!ptr || !item) return M4BadParam;
	/*if last entry or first of an empty chain...*/
	if (position >= ptr->entryCount) return ChainAddEntry(ptr, item);
	ptr->slots = realloc(ptr->slots, (ptr->entryCount+1)*sizeof(void*));
	i = ptr->entryCount - position;
	memmove(&ptr->slots[position + 1], &ptr->slots[position], sizeof(void *)*i);
	ptr->entryCount++;
	ptr->slots[position] = item;
	return M4OK;
}

void ChainReset(Chain *ptr)
{
	if (ptr) {
		ptr->entryCount = 0;
		free(ptr->slots);
		ptr->slots = NULL;
	}
}

#else


struct tagChain
{
	void **slots;
	u32 entryCount;
	u32 allocSize;
};

Chain * NewChain()
{
	Chain *newChain;

	newChain = (Chain *) malloc(sizeof(Chain));
	if (! newChain) return NULL;
	
	newChain->slots = NULL;
	newChain->entryCount = 0;
	newChain->allocSize = 0;
	return newChain;
}

void DeleteChain(Chain *ptr)
{
	if (!ptr) return;
	free(ptr->slots);
	free(ptr);
}

static void realloc_chain(Chain *ptr)
{
	ptr->allocSize += M4_CHAIN_STEP_ALLOC;
	if (ptr->slots) {
		ptr->slots = realloc(ptr->slots, ptr->allocSize*sizeof(void*));
	} else {
		ptr->slots = malloc(sizeof(void*)*ptr->allocSize);
	}
}

M4Err ChainAddEntry(Chain *ptr, void* item)
{
    if (! ptr) return M4BadParam;
	if (ptr->allocSize==ptr->entryCount) realloc_chain(ptr);
	if (!ptr->slots) return M4OutOfMem;

	ptr->slots[ptr->entryCount] = item;
	ptr->entryCount ++;
	return M4OK;
}

void ChainExpand(Chain *ptr, u32 nbItems)
{
	if (ptr->entryCount+nbItems>ptr->allocSize) {
		ptr->allocSize = ptr->entryCount+nbItems;
		if (ptr->slots) {
			ptr->slots = realloc(ptr->slots, ptr->allocSize*sizeof(void*));
		} else {
			ptr->slots = malloc(sizeof(void*)*ptr->allocSize);
		}
	}
}

u32 ChainGetCount(Chain *ptr)
{
	if (!ptr) return 0;
	return ptr->entryCount;
}

void *ChainGetEntry(Chain *ptr, u32 itemNumber)
{
	if(!ptr || (itemNumber >= ptr->entryCount)) return NULL;
	return ptr->slots[itemNumber];
}


/*WARNING: itemNumber is from 0 to entryCount - 1*/
M4Err ChainDeleteEntry(Chain *ptr, u32 itemNumber)
{
	u32 i;
	if ( !ptr || !ptr->slots || !ptr->entryCount) return M4BadParam;
	i = ptr->entryCount - itemNumber - 1;
	if (i) memmove(&ptr->slots[itemNumber], & ptr->slots[itemNumber +1], sizeof(void *)*i);
	ptr->slots[ptr->entryCount-1] = NULL;
	ptr->entryCount -= 1;
	return M4OK;
}


/*WARNING: position is from 0 to entryCount - 1*/
M4Err ChainInsertEntry(Chain *ptr, void *item, u32 position)
{
	u32 i;
	if (!ptr || !item) return M4BadParam;
	/*if last entry or first of an empty chain...*/
	if (position >= ptr->entryCount) return ChainAddEntry(ptr, item);
	if (ptr->allocSize==ptr->entryCount) realloc_chain(ptr);

	i = ptr->entryCount - position;
	memmove(&ptr->slots[position + 1], &ptr->slots[position], sizeof(void *)*i);
	ptr->entryCount++;
	ptr->slots[position] = item;
	return M4OK;
}

void ChainReset(Chain *ptr)
{
	if (ptr) ptr->entryCount = 0;
}

#endif

s32 ChainFindEntry(Chain *ptr, void *item)
{
	u32 i;
	for (i=0; i<ChainGetCount(ptr); i++) {
		if (ChainGetEntry(ptr, i) == item) return (s32) i;
	}
	return -1;
}

s32 ChainDeleteItem(Chain *ptr, void *item)
{
	s32 i = ChainFindEntry(ptr, item);
	if (i>=0) ChainDeleteEntry(ptr, (u32) i);
	return i;
}

