
// you need to modify this file
// 1. implement a basic hash table (with buckets)
// 2. make your hash table thread-safe

#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);
    }
}

/**************************************************/
/* Insert a number x, if x is not in the hash     */
/* table.                                         */
/* Return 0: if successful                        */
/* Return 1: if x already exists                  */
/**************************************************/
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;


}

/**************************************************/
/* Return 0: if successful                        */
/* Return 1: if x does not exist                  */
/**************************************************/
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;
}

/**************************************************/
/* Print the content of the hash table.           */
/* Use your own format. We will not grade this    */
/* function. It is for your own debugging method. */
/**************************************************/
void Hash_Dump()
{

	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");
	}

}

/**************************************************/
/* Count the total number of elements in the      */
/* hash table                                     */
/**************************************************/
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;
}

/**************************************************/
/* Count the total number of elements in a        */
/* specific bucket                                */
/**************************************************/
int Hash_CountBucketElements(int bucketNumber)
{
   return hash_buckets[bucketNumber].num_elements; 
}


