#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/socket.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/un.h>

#include "pthread.h"
#include "semaphore.h"

#define UINT unsigned int
#define BOOL char

#define TRUE 1
#define FALSE 0

#define UINT_ERROR 12345678

// Query Types

#define ResponseMask			0x10000000
#define QueryType_AskPredecessor	1
#define QueryType_Notify		2
#define QueryType_FindSuccessor	3
//#define QueryType_FindPredecessor	4
//#define QueryType_AskFingerEntry	5
//#define QueryType_UpdateFingerTable	6


inline void ASSERT(BOOL a)
{
	if(!a)
	{
		fprintf(stderr,"ASSERT FAILED at LINE %d, function %s\n", __LINE__, __FUNCTION__);
	}
}

static int pow(int a, int b)
{
	if(b == 0)
		return 1;
	else
		return a * pow(a, b-1);
}

const int LOGN = 16;
const int MAX_N = pow(2,LOGN);

// Default Target Port for nextHop queries from the SOS administrative module
// This can be arbitrary....
const int DEFAULT_TARGET_PORT = 80;

// Minimum Stabilize timer value in microseconds..
const int MIN_TIMER = 1000000;

// Maximum Stabilize timer value in microseconds..
const int MAX_TIMER = 4000000;

// Current Frequency of Stabilizing Thread
int CURR_STABILIZE_TIMER = MIN_TIMER;

// Minimum Poll time for Successor update
const int MIN_POLL_TIME = 10000;
// Maximum Poll time for Successor update
const int MAX_POLL_TIME = 10 * MIN_POLL_TIME;

// Buffer size for socket communications
const int MAXBUF = 512;

// Main UDP socket to communicate with other chord nodes..
int chord_socket;

// Unix Domain Socket to communicate with SOS administration module
int sos_socket;

// Socket name for Unix Domain Communication with SOS communication module
#define UDS_PATH "/tmp/kamra/sos-path"

// Threads and Semaphore Declarations...
pthread_t recv_thread;
pthread_t sos_thread;

sem_t table_sem;
sem_t hash_sem;
sem_t mutex_sem; // Generally used for all kinds of mutual exclusion...



#include "hash.h"
#include "table.h"
#include "comm.h"

// Utility Functions...

void ResetStabilizeTimer()
{
	CURR_STABILIZE_TIMER = MIN_TIMER;
}

UINT ChordHash(struct in_addr ip, in_port_t port)
{
	UINT hash = (ip.s_addr + port)%MAX_N;
	printf("ip=%d, port=%d, id=%d\n",ip.s_addr, port, hash);
	return hash;
}

void LockFingerTable()
{
	sem_wait(&table_sem);
}

void UnLockFingerTable()
{
	sem_post(&table_sem);
}

void LockHashTable()
{
	sem_wait(&hash_sem);
}

void UnLockHashTable()
{
	sem_post(&hash_sem);
}

void PrintFingerTable(int curr_timer)
{
	LockFingerTable();
	mytable->print(curr_timer);
	UnLockFingerTable();
}

void SetFinger(int index, UINT id)
{
	LockFingerTable();
	mytable->SetFinger(index, id);
	UnLockFingerTable();
}

void SetPredecessor(UINT id)
{
	LockFingerTable();
	mytable->SetPredecessor(id);
	UnLockFingerTable();
}

UINT GetMyID()
{
	LockFingerTable();
	UINT myid = mytable->GetMyID();
	UnLockFingerTable();

	return myid;
}

UINT GetIthStart(UINT myID, int k)
{
	UINT start = (myID + pow(2,k) + MAX_N) % MAX_N;
	return start;
}

UINT GetIthStart(int k)
{
	UINT myID = GetMyID();
	return GetIthStart(myID, k);
}

/*
UINT GetIthEnd(int k)
{
	UINT id = GetMyID();
	UINT end = (id - pow(2,k) + MAX_N) % MAX_N;
	return end;
}
*/

UINT GetIDFromIP(char inputIP[])
{
	LockHashTable();
	UINT id = hashtable->GetIDFromIP(inputIP);
	UnLockHashTable();
	return id;
}

void GetIPFromID(UINT id, char result[])
{
	LockHashTable();
	hashtable->GetIPFromID(id, result);
	UnLockHashTable();
}

UINT GetFinger(int k)
{
	LockFingerTable();
	UINT finger = mytable->GetFinger(k);
	UnLockFingerTable();
	return finger;
}

UINT GetMySuccessor()
{
	return GetFinger(0);
}

void DeleteFromCache(UINT id)
{
	LockHashTable();
	hashtable->Delete(id);
	UnLockHashTable();
}

UINT FindLowestFromHash()
{
	LockHashTable();
	UINT next = hashtable->FindLowestNode();
	UnLockHashTable();
	return next;
}

void ReplaceFingerEntries(UINT prev, UINT replacement)
{
	LockFingerTable();
	mytable->ReplaceFingerEntries(prev, replacement);
	UnLockFingerTable();
}

BOOL SetNextSuccessor(UINT start_id)
{
	// Step 1: Delete start_id from the Cache... (Hash Table)
	// Step 2: Find the lowest node greater than MyID in the Cache...
	// Step 3: Check all entries in the Finger Table and the Predecessor and replace all
	// occurances of start_id with this new node...
	// Step 4.1: If this lowest node is == myID return FALSE..
	// Step 4.2: return TRUE
	
	// Step 1:
	DeleteFromCache(start_id);

	// Step 2:
	UINT new_succ = FindLowestFromHash();

	// Step 3:
	ReplaceFingerEntries(start_id, new_succ);

	// Step 4:
	if(new_succ == GetMyID())
		return FALSE;
	else
		return TRUE;
}

UINT GetMyPredecessor()
{
	LockFingerTable();
	UINT pred = mytable->GetPredecessor();
	UnLockFingerTable();
	return pred;
}

void UpdatePredecessor(UINT newpred)
{
	LockFingerTable();
	mytable->UpdatePredecessor(newpred);
	UnLockFingerTable();
}

BOOL Belongs(UINT mid, UINT start, UINT end, int LeftInclusive, int RightInclusive, BOOL ShouldContain)
{

	if(!LeftInclusive && (mid == start))
		return FALSE;
	if(!RightInclusive && (mid == end))
		return FALSE;
	
	// Returns true if mid lies between start and end inclusive in circular order
	BOOL flag;
	if((end == start)&&(!ShouldContain))
	{
		flag = (end >= start);
	}
	else
		flag = (end > start);

	if(flag)
	{
		if((mid >= start)&&(mid <= end))
			return TRUE;
	}
	else
	{
		if((mid >= start)||(mid <= end))
			return TRUE;
	}
	return FALSE;
}

UINT ParseAndAddLocation(char *ipStr)
{
	LockHashTable();
	UINT id = hashtable->ParseAndAddLocation(ipStr);
	UnLockHashTable();
	return id;
}

struct location GetLocation(UINT id)
{
	LockHashTable();
	struct location loc = hashtable->GetLocation(id);
	UnLockHashTable();
	return loc;
}

struct location GetMyLocation()
{
	return GetLocation(GetMyID());
}

void UpdateLocation(struct location loc)
{
	LockHashTable();
	hashtable->Update(loc);
	UnLockHashTable();
}

UINT GetNextHop(UINT id)
{
	LockFingerTable();
	UINT nextHop = mytable->GetNextHop(id);
	UnLockFingerTable();
	return nextHop;
}

int GetFingerIndex(UINT start)
{
	UINT myID = GetMyID();
	int power2 = (start + MAX_N - myID)%MAX_N;
	int index = 0;
	while(power2 > 1)
	{
		index++;
		power2 /= 2;
	}
	return index;
}

UINT GetClosestPrecedingFinger(UINT id)
{
	LockFingerTable();
	UINT nextHop = mytable->GetClosestPrecedingFinger(id);
	UnLockFingerTable();
	return nextHop;
}

int GetNewQueryID()
{
	static int curr_id=12;
	return ++curr_id;
}

void FindNextHop(char input[], char output[])
{
	struct in_addr ip;

	if(strcmp(input, "0.0.0.0") == 0)
	{
		fprintf(stderr,"Got request from the administrative module to exit\nComplying ...\n");
		exit(-6);
	}
	
	inet_aton(input, &ip);
	
//	UINT dst = GetIDFromIP(input);
	UINT dst = ChordHash(ip, DEFAULT_TARGET_PORT);
	printf("Dst id is %d\n",dst);
	
	UINT nextHop = GetNextHop(dst);
	printf("nextHop id is %d\n",nextHop);
	GetIPFromID(nextHop, output);
	printf("nextHop IP is <%s>\n",output);
}

/*
void WaitAMoment()
{
	usleep(100000);
}
*/

#define FREE 0
#define WAITING 1
#define ZOMBIE 2

int Stabilize_Qid;
int Stabilize_Status;
struct Query Stabilize_Response;

BOOL Stabilize_WakeUp(struct Query *q_response)
{
	BOOL flag = FALSE;
	sem_wait(&mutex_sem);

	if(Stabilize_Qid == q_response->QueryID)
	{
		ASSERT(Stabilize_Status == WAITING);
		Stabilize_Status = ZOMBIE;
		Stabilize_Response = *q_response;
		flag = TRUE;
	}

	sem_post(&mutex_sem);
	return flag;
}

void Stabilize_SetupSleep(int qid)
{
	sem_wait(&mutex_sem);

	ASSERT(Stabilize_Status == FREE);
	Stabilize_Qid = qid;
	Stabilize_Status = WAITING;

	sem_post(&mutex_sem);
}

BOOL Stabilize_CheckForResponse()
{
	BOOL flag = FALSE;
	sem_wait(&mutex_sem);

	ASSERT(Stabilize_Status != FREE);
	if(Stabilize_Status == ZOMBIE)
	{
		Stabilize_Qid = 0;
		Stabilize_Status = FREE;
		flag = TRUE;
	}

	sem_post(&mutex_sem);
	return flag;
}

struct Query Stabilize_GetResponse()
{
	struct Query response;
	sem_wait(&mutex_sem);
	response = Stabilize_Response;
	sem_post(&mutex_sem);
	return response;
}

#include "table.cc"
#include "hash.cc"
#include "comm.cc"
