
#include "lru.h"
#include "hash.h"
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>


/***********************************************************************/
struct hash_element
{
	int number;
	struct hash_element *next;
};

struct hash_bucket
{
	int num_elements;
	struct hash_element *head;
};

static struct hash_bucket *hash_buckets;
static int g_numOfBuckets;
static pthread_mutex_t *mutexArray;

/**************************************************/
/*                                                */
/*                                                */
/**************************************************/
void Hash_Init(int buckets)
{

	int i = 0;

	hash_buckets = malloc(buckets * sizeof(struct hash_bucket));
	mutexArray = malloc(buckets * sizeof(pthread_mutex_t));
	g_numOfBuckets = buckets;

	for (i = 0; i < buckets; i++) {
		hash_buckets[i].head = NULL;
		hash_buckets[i].num_elements = 0;
		pthread_mutex_init(&(mutexArray[i]), NULL);
	}
}

int Hash_Contains(int value){
	int bucket = value % g_numOfBuckets;

	pthread_mutex_lock(&(mutexArray[bucket]));

	struct hash_element *curr = hash_buckets[bucket].head;

	while(curr != NULL){
		if(curr->number == value){
			pthread_mutex_unlock(&(mutexArray[bucket]));
			return 1;
		}
		curr = curr->next;
	}
	pthread_mutex_unlock(&(mutexArray[bucket]));
	return 0;
}


int Hash_Insert(int x)
{
	int bucket = x % g_numOfBuckets;

	pthread_mutex_lock(&(mutexArray[bucket]));

	struct hash_element *element = (struct hash_element *)hash_buckets[bucket].head;
	struct hash_element *new_element = (struct hash_element *)malloc(sizeof(struct hash_element));

	new_element->number = x;
	new_element->next = NULL;

	if(element == NULL){
		hash_buckets[bucket].head = new_element;
		hash_buckets[bucket].num_elements++;
		pthread_mutex_unlock(&(mutexArray[bucket]));
		return 0;
	}
	if(element->number == x){
		pthread_mutex_unlock(&(mutexArray[bucket]));
		return 1;
	}


	while(element->next != NULL){
		if(element->next->number == x){

			pthread_mutex_unlock(&(mutexArray[bucket]));
			return 1;
		}
		else{
			element = element->next;
		}
	}


	element->next = new_element;
	hash_buckets[bucket].num_elements++;

	pthread_mutex_unlock(&(mutexArray[bucket]));


	return 0;
}




int Hash_Remove(int x)
{
	int bucket = x % g_numOfBuckets;

	pthread_mutex_lock(&(mutexArray[bucket]));

	struct hash_element *curr_element = hash_buckets[bucket].head;
	struct hash_element *prev_element = NULL;

	if(curr_element == NULL){
		pthread_mutex_unlock(&mutexArray[bucket]);

		return 1;
	}

	if(curr_element->number == x){
		hash_buckets[bucket].head = hash_buckets[bucket].head->next;
		hash_buckets[bucket].num_elements--;
		pthread_mutex_unlock(&mutexArray[bucket]);

		return 0;
	}
	prev_element = curr_element;
	curr_element = curr_element->next;

	while(curr_element != NULL){
		if(curr_element->number == x){
			prev_element->next = curr_element->next;
			hash_buckets[bucket].num_elements--;
			pthread_mutex_unlock(&mutexArray[bucket]);

			return 0;
		}
		prev_element = curr_element;
		curr_element = curr_element->next;
	}
	pthread_mutex_unlock(&(mutexArray[bucket]));

	return 1;
}

int Hash_CountElements()
{
	int total_count = 0;
	int i;

	for(i = 0; i < g_numOfBuckets; i++){
		total_count = total_count + hash_buckets[i].num_elements;
	}

	return total_count;
}

void Hash_Dump()
{
	printf("-----------------------DUMP-------------------------\n");

	int i;
	for(i = 0; i < g_numOfBuckets; i++){
		printf("------bucket %d--------\n", i);
		struct hash_element *element = hash_buckets[i].head;
		while(element != NULL){
			printf("%d  |  ", element->number);
			element = element->next;
		}
		printf("\n\n");
	}

	printf("--------------------END DUMP-------------------------\n");

}


/***************************************************************/

static int ll_size;

static struct ll_element *head;
static struct ll_element *tail;

struct ll_element{
	int number;
	struct ll_element *prev;
	struct ll_element *next;
};

void LinkedList_Init()
{

	ll_size = 0;

	head = NULL;
	tail = head;
}

/**********
* return 0 on success
* return 1 on failure
**********/
int LL_Insert(int value){


	struct ll_element *new_element = malloc(sizeof(struct ll_element));
	new_element->number = value;

	if(ll_size == 0){
		head = new_element;
		new_element->next == NULL;
		new_element->prev == NULL;
		tail = new_element;
		ll_size++;
		return 0;
	}
	else{
		new_element->next = NULL;
		new_element->prev = tail;
		tail->next = new_element;
		tail = new_element;
		ll_size++;
		return 0;
	}
	return 1;
}

int LL_Remove_Element(int value){


	struct ll_element *curr = head;

	if(curr->number == value){

		head = curr->next;
		ll_size--;
		return 0;

	}

	curr = curr->next;

	while(curr != NULL){
		if(curr->number == value){
			if(curr->next == NULL){
				tail = curr->prev;
				tail->next = NULL;
			}
			else{
				(curr->next)->prev = curr->prev;
				if(curr->prev != NULL){
					(curr->prev)->next = curr->next;
				}
			}
			ll_size--;
			return 0;
		}
		else{
			curr = curr->next;
		}
	}
	return -1;
}

/**********
* return the value that was deleted
*
**********/
int LL_Remove_First(){


	int deleted = head->number;

	if(Hash_Contains(deleted) == 0){
		printf("something wrong....can't find the deleted element\n");
		LL_Dump();
		Hash_Dump();
		exit(1);
	}

//	printf("head = %d\n", head->number);

	head = head->next;
	head->prev = NULL;
	ll_size--;

	//		printf("the deleted element = %d\n", deleted);
	return deleted;

}

int LL_Size(){
	return ll_size;
}

void
LL_Dump(){
	struct ll_element *curr = head;

	printf("------------------------------------------------------\n");

	while(curr != NULL){
		if(curr->number == NULL){
			printf("number is equal to NULL\n");
		}
		printf("%d  |", curr->number);
		curr = curr->next;
	}
	printf("\n");

	printf("-------------------------------------------------------\n");

}


/****************************************************************/

static volatile int max_size;
static volatile int curr_size = 0;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
static int lock_entered = 0;

void
LRU_Init(int size){


	curr_size = 0;
	max_size = size;
	Hash_Init(size / 10);
	LinkedList_Init();


}

int
LRU_Insert(int element){

	if(element <= 0){
		return -1;
	}

	pthread_mutex_lock(&mutex2);

//	printf("Inserting element = %d\n", element);

	if(Hash_Contains(element) == 1){
		pthread_mutex_unlock(&mutex2);
		return -1;
	}

	if(curr_size < max_size){
		if(Hash_Insert(element) == 1){
			printf("could not insert %s\n", element);
		}
		LL_Insert(element);
		curr_size++;

//		LL_Dump();
//		Hash_Dump();

		pthread_mutex_unlock(&mutex2);
		return 0;
	}
	else if(curr_size == max_size){
		int delete;
		delete = LL_Remove_First();
		
		if(Hash_Remove(delete) == 1){
//			printf("delete = %d\n", delete);
//			LL_Dump();
//			Hash_Dump();
			printf("Could not find the element in the has\n");
		}

		LL_Insert(element);
		if(Hash_Insert(element) == 1){
			printf("could not insert %s\n", element);
		}

//		LL_Dump();
//		Hash_Dump();

		pthread_mutex_unlock(&mutex2);
		return delete;
	}
	else{
		pthread_mutex_unlock(&mutex2);
		printf("ERROR: curr_size > total_size!!!\n");
		return -9999;
	}

}

int LRU_Access(int element){
	pthread_mutex_lock(&mutex2);

//	printf("Doing an Access on element = %d\n", element);

	if(Hash_Contains(element) == 1){
		LL_Remove_Element(element);
		LL_Insert(element);

//		LL_Dump();
//		Hash_Dump();

		pthread_mutex_unlock(&mutex2);
		return 0;
	}
	else{
//		printf("Doesn't contain element = %d\n", element);
		pthread_mutex_unlock(&mutex2);
		return -1;
	}
}

int LRU_Remove(int element){
	pthread_mutex_lock(&mutex2);

//	printf("Remove element = %d\n", element);

	if(Hash_Contains(element) == 1){
		LL_Remove_Element(element);
		Hash_Remove(element);
		curr_size--;
		
//		Hash_Dump();
//		LL_Dump();

		pthread_mutex_unlock(&mutex2);
		return 0;
	}
	else{
		pthread_mutex_unlock(&mutex2);
		return -1;
	}

}

int LRU_Size(){
		return curr_size;
}

