#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include "arrayList.h"

int arrayListCreate(LIST *l, int elemSize, void (*printFn)(void *), void (*freeFn)(void *)){
	int i;
	
	if(l == NULL || elemSize==0){
		printf("arrayListCreate: either NULL list ptr or zero elemSize");
		return -1;
	}

	l->elements = malloc( LIST_INITIAL_CAPACITY * elemSize);
	l->next = (int *)malloc(LIST_INITIAL_CAPACITY * sizeof(int));
	l->previous = (int *)malloc(LIST_INITIAL_CAPACITY * sizeof(int));

	l->next[0] = 0; /*first node is a dummy node whose next and prev links points to itself */
	l->previous[0] = 0;

	for(i = 1; i< LIST_INITIAL_CAPACITY -1; i++) /*initializing for the free list */
		l->next[i] = i+1;
	l->next[LIST_INITIAL_CAPACITY -1] = -1;
	l->freeList = 1;

	l->elemSize = elemSize;
	l->capacity = LIST_INITIAL_CAPACITY;
       	/* list is initialized with a dummy node */
	 /* head and tail points to the dummy node */
	
	l->size = 1;
	l->freeFn = freeFn;
	l->printFn = printFn;
	return 0;
}

int arrayListGetNode(LIST *l, int pos){ /* returns the node address (index) of the node in position pos */
	int count =0; int node = 0;
	
	if( pos > l->size - 1){
		printf("No such position as %d; there are a total %d nodes in the list\n", pos, l->size);
		return(-1);
	}

	if( pos == 0) return node;

	if(pos < l->size/2){ /* starting from the head */
	        count =0;	
		node =0;
		while(count < pos){
			node = l->next[node];
			count++;
		}
		return node;
	}
	else{ /* starting from tail */
		count =0;
		node =0;
		while(count < l->size - pos){
			node = l->previous[node];
			count ++;
		}
	}

	return node;
}


int arrayListGet( LIST *l, int pos, void *d){
	int node = arrayListGetNode( l, pos);
	if(node <= 0){
		printf("arrayListGet: attempt to get either dummy or nonexistant node \n");
		return -1;
	}
	memcpy(d, (char *)l->elements + node * l->elemSize, l->elemSize);
	return 0;
}

int arrayListSet( LIST *l,  int pos, void *toSet, void *toReturn){
	int node = arrayListGetNode(l, pos);
	if(node <= 0){
		printf("arrayListSet: attempt to set either dummy or nonexistant node \n");
		return -1;
	}
	memcpy(toReturn, (char *)l->elements + node * l->elemSize, l->elemSize);
	memcpy((char *)l->elements + node * l->elemSize, toSet, l->elemSize);
	return 0;
}

int arrayListResize( LIST *l){
	void *temp;
	void *tempNext;
	void *tempPrevious;
	int oldCapacity = l->capacity;
	int i;

	temp = realloc(l->elements, 2 * oldCapacity * l->elemSize);

	if(temp == NULL){
		printf("arrayListResize: out of memory\n");
		return -1;
	}

	tempNext = realloc(l->next, 2 * oldCapacity * sizeof(int));

	if(tempNext == NULL){
		printf("arrayListResize: out of memory, could not allocate next\n");
		return -1;
	}

	tempPrevious = realloc(l->previous, 2 * oldCapacity * sizeof(int));

	if(tempPrevious == NULL){
		printf("arrayListResize: out of memory, could not allocate previous\n");
		return -1;
	}

	l->elements = temp; l->next = tempNext; l->previous = tempPrevious;

	for(i = oldCapacity; i < 2 * oldCapacity -1 ; i++)
		l->next[i] = i+1;

         l->next[2 * oldCapacity - 1] = -1;
	 l->freeList = oldCapacity;
	 l->capacity = 2 * oldCapacity;
	 return 0;
}


int arrayListAddBefore(LIST *l, int node, void *d){
	int previousNode = l->previous[node];
	//int nextNode = l->next[node];
	int nextFree; 

	if( l->size == l->capacity){
		if( arrayListResize(l) == -1){
			printf("arrayListAddBefore: resizing failed for full list, could not add\n");
			return -1;
		}
	}
        nextFree =  l->next[l->freeList];
	memcpy((char *)l->elements + l->freeList * l->elemSize, d, l->elemSize);
	l->next[l->freeList] = node;
	l->previous[l->freeList] = previousNode;
	l->next[previousNode] = l->freeList;
	l->previous[node] = l->freeList;
	l->freeList = nextFree;
	l->size++;
	return 0;
}

int arrayListAdd(LIST *l, int pos, void *d){
	int node = arrayListGetNode(l, pos);
	//printf("arrayListAdd: returned node is %d\n", node);
	arrayListAddBefore(l, node, d);
	return 0;
}

int arrayListRemoveNode(LIST *l, int node, void *removed){
	int previousNode = l->previous[node];
	int nextNode = l->next[node];

	if( node == 0){
	       	printf("arrayListRemoveNode: attempted to remove dummy\n");
		return -1;
	}

	memcpy(removed, (char *)l->elements + node * l->elemSize, l->elemSize); 

	l->next[previousNode] = nextNode;
	l->previous[nextNode] = previousNode;
	l->size--;

        l->next[node] = l->freeList; /* updating free list */
	l->freeList = node;
	return 0;
}

int arrayListRemoveAtPosition(LIST *l, int pos, void *removed){
	int node = arrayListGetNode(l, pos);
	//printf("arrayListRemoveAtPosition: returned node is %d\n", node);
	return arrayListRemoveNode(l, node, removed);
}

int arrayListPrint( LIST *l){
	if(l->printFn == NULL){
		printf("arrayListPrint:print function not defined \n");
		return -1;
	}

	printf("printing list of size %d:\n", l->size -1);

	int i = l->next[0];
	while( i != 0){
		l->printFn((char *)l->elements + i * l->elemSize);
		i = l->next[i];
	}
	printf("\n");
	return 0;
}

int arrayListNextBigger(LIST *l, void *d, int (*cmpFn)(void *, void *)){
	int i = 0;

	if(l->size == 1) return 0;

	i = l->next[0];

	while( i != 0) { 
		if (cmpFn((char *)l->elements + i * l->elemSize, d) > 0)
			break;
		i = l->next[i];
	}
	return i;
}



int arrayListDispose( LIST *l){
	int i;

	if( l == NULL){
		printf(" arrayListDispose: attempting disposing  NULL ptr\n");
		return -1;
	}

	if(l->freeFn !=NULL){
		i = l->next[0];
		while( i != 0){
			l->freeFn((char *)l->elements + i * l->elemSize);
			i = l->next[i];
		}
	}

	free(l->next);
	free(l->elements);
	free(l->previous);
	return 0;
}
	


	



