// the caching lock server implementation

#include "lock_server_cache.h"
#include <sstream>
#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdexcept>

using std::exception;

// The server runs two threads that periodically sends grant and
// revoke requests to handle lost grants and revokes.

static void *
revokethread(void *x)
{
  lock_server_cache *sc = (lock_server_cache *) x;
  sc->revoker();
  return 0;
}

static void *
grantthread(void *x)
{
  lock_server_cache *sc = (lock_server_cache *) x;
  sc->granter();
  return 0;
}



lock_server_cache::lock_server_cache() 
  : lock_server() 
{
  
  printf("I am the lock_server_cache!\n");
  acq_index = 0;
  rel_index = 0;
 // acq_end = 0;

	queries = 0;
	assert(pthread_mutex_init(&q_lock, NULL) == 0);
  
  locks_map = new std::map<std::string, lock_data>;
  acq_list = new std::list<std::pair<std::string, request_info> >;
  acq_vector = new std::vector<std::pair<std::string, request_info> >;
  
  rel_list = new std::list<std::pair<std::string, request_info> >;
  rel_vector = new std::vector<std::pair<std::string, request_info> >;
  
  pthread_t th;
  int r = pthread_create(&th, NULL, &revokethread, (void *) this);
  assert (r == 0);
  r = pthread_create(&th, NULL, &grantthread, (void *) this);
  assert (r == 0);
  
  
}

void put_in_done_map(std::map<int,int> * done_map, int cid, int nonce) 
{
	std::map<int,int>::iterator aux = done_map->find(cid);
	if (aux != done_map->end()) {
		aux->second = nonce; 
	} 
	else {
		done_map->insert(std::make_pair<int,int>(cid, nonce));
	}
}

void c(std::string s) {
	printf("server: %s \n", s.c_str());
}
void c(int n) {
	printf("server: %d \n", n);
}

void lock_server_cache::ce(std::pair<std::string, lock_data> p) {
	printf("server: lock %s, nonce %d, cid %d, type " , p.first.c_str(), p.second.nonce, p.second.cid);
	switch (p.second.type) {
		case (NONE): {printf("NONE\n");break;}
		case (FREE): {printf("FREE\n");break;}
		case (REVOKING): {printf("REVOKING\n");break;}
		case (LOCKED): {printf("LOCKED\n");break;}
		default: {printf("OTHER state \n");}
	}
}

void
lock_server_cache::revoker()
{
	int r;
  // This method should be a continuous loop, that sends revoke
  // messages to lock holders whenever another client wants the
  // same lock
	if (DEBUG) c("in revoker in lock_server_cache");
	while (1) {
		//copy from the acq_vector
		int size = acq_vector->size();
		while (acq_index < size) { 
			std::pair<std::string,request_info> rq;
			try {
				rq = acq_vector->at(acq_index);
			} catch (exception& e) {
				if (DEBUG) printf("EXCEPTION CAUGHT!\n");
				break;
			}
			
			acq_list->push_back(rq);
			acq_index++;
		}
		
		
		//pass through the ACQ list we have so far and satisfy REVOKE+GRANT requests
		std::list<std::pair<std::string, request_info> >::iterator it = acq_list->begin();
		std::list<std::pair<std::string, request_info> >::iterator nextit;
		
		while (it != acq_list->end()) {
			
			nextit = ++it; it--;
			
			std::map<std::string, lock_data>::iterator elem= locks_map->find(it->first);
			//first check if request was already satisfied
			if (elem != locks_map->end()) {
				
				//erase from lost if it was performed
				std::map<int,int>::iterator itaux = elem->second.acq_done_map->find(it->second.cid);
				if (itaux != elem->second.acq_done_map->end()) {
					if (itaux->second >= it->second.nonce) {
						nextit = acq_list->erase(it);
						it = nextit;
						continue;
					}
				}
				
				switch (elem->second.type) {
				  case REVOKING: {
					  //it is all good, this acquire elements want the lock to be revoking
					break;
				  }
				  case LOCKED: {
					
					elem->second.type = REVOKING;
					struct sockaddr_in dst;
					make_sockaddr((char *)elem->second.hostname.c_str(), &dst);
					
					if (DEBUG) printf("server: revoking from client id %d \n", elem->second.cid);
					cl.call(dst, lock_protocol::revoke, elem->first, elem->second.nonce, r );
					
					break;
				  }
				  case FREE: {
					
					put_in_done_map(elem->second.acq_done_map, it->second.cid, it->second.nonce);
					
					elem->second.type = LOCKED;
					elem->second.hostname = it->second.hostname;
					elem->second.nonce = it->second.nonce;
					elem->second.cid = it->second.cid;
					
					struct sockaddr_in dst;
					make_sockaddr((char *)elem->second.hostname.c_str(), &dst);
					
					if (DEBUG) printf("server: granting to client id %d \n", it->second.cid);
					cl.call(dst, lock_protocol::grant, it->first, it->second.nonce, r );
					
					nextit = acq_list->erase(it);
					break;
				  }
				  case NONE: {
					assert(0); //the only time when it could be none is upon creating when we give it immediately
				  }
				  default: {}
				}
				
				
			} else { 
				//c("lock does not exist");
				//element does not exist
				lock_data * info = new lock_data;
				info->acq_done_map = new std::map<int,int>;
				info->rel_done_map = new std::map<int,int>;
				put_in_done_map(info->acq_done_map, it->second.cid, it->second.nonce);
				info->type = LOCKED; 
				info->cid = it->second.cid;
				info->hostname = it->second.hostname;
				info->nonce = it->second.nonce;
								
				std::pair<std::string, lock_data> p = std::make_pair<std::string, lock_data>(it->first, *info);
				locks_map->insert( p);
				struct sockaddr_in dst;
				make_sockaddr((char *)it->second.hostname.c_str(), &dst);
				if (DEBUG) printf("server: granting to client id %d \n", it->second.cid);
				cl.call(dst, lock_protocol::grant, it->first, it->second.nonce, r );
				
				nextit = acq_list->erase(it);
				
			}
			
			assert(it != nextit);
			it = nextit;
		}
			
		size  = rel_vector->size();
		while (rel_index < size) { 
			std::pair<std::string,request_info> ri;
			try {
				ri = rel_vector->at(rel_index);
			
			} catch (exception& e) {
				if (DEBUG) printf("EXCEPTION CAUGHT!\n");
				break;
			}
			rel_list->push_back(ri);
			rel_index++;
		}
		
		//go through release list and see what can be granted
		it = rel_list->begin();
		
		while (it != rel_list->end()) {
			
			nextit = ++it; --it;
			
			std::map<std::string, lock_data>::iterator elem= locks_map->find(it->first);
			//first check if request was already satisfied
			if (elem != locks_map->end()) {
				
				std::map<int,int>::iterator aux = elem->second.rel_done_map->find(it->second.cid);
				if ((aux != elem->second.rel_done_map->end()) && (aux->second >= it->second.nonce)) {
					it = rel_list->erase(it);
					continue;
				} 
				//element exists
				if ((elem->second.type == REVOKING) || (elem->second.type = LOCKED)) {
					if ((it->second.nonce == elem->second.nonce) && (it->second.cid = elem->second.cid)) {
						if (DEBUG) printf("lock %s is freed by client %d \n", it->first.c_str(), it->second.cid);
						elem->second.type = FREE;
						
						//place in release list
						put_in_done_map(elem->second.rel_done_map, it->second.cid, it->second.nonce);						
						
						nextit = rel_list->erase(it);
						
					}
				} else {
					assert(0);
				}
				
			}  else {
				assert(0); //upon release the lock needs to be known by the server
			}
			 
			it = nextit;
		}
		
		
		
	}
	
}


void
lock_server_cache::granter()
{

  // This method should be a continuous loop, waiting for locks
  // to be released and then sending grant messages to those who
  // are waiting for it.
	
	
	//function performed by REVOKER

}


lock_protocol::status
lock_server_cache::stat(std::string name, int &r)
{
  lock_protocol::status ret = lock_server::stat(name, r);
  return ret;
}

void add_to_task_vector(std::vector<std::pair<std::string, request_info> > * vct, std::string hostname, std::string name, int nonce, int cid) {
	request_info * ri = new request_info;
	ri->hostname = hostname;
	ri->nonce = nonce;
	ri->cid = cid;
	vct->push_back(std::make_pair<std::string, request_info>(name, *ri));
	
}

lock_protocol::status lock_server_cache::acquire(std::string hostname, string name, int nonce, int cid, int & r)
{


	assert(pthread_mutex_lock(&q_lock)==0);
	queries++;
	//if (queries % 10 == 0) {
		printf("QUERIES: %d \n", queries);
	//}
	assert(pthread_mutex_unlock(&q_lock)==0);

	if (DEBUG) printf("server: entering acquire lock: %s from client %d !\n", name.c_str(),cid);
	
	//check if acquire is satisfied -- a read to shared structure which should be fine
	std::map<std::string, lock_data>::iterator it = locks_map->find(name);
	if (it == locks_map->end()) {
		//printf("adding task\n");
		add_to_task_vector(acq_vector, hostname, name, nonce, cid);
	} else { 
		std::map<int, int>::iterator it2 = it->second.acq_done_map->find(cid);
		if ((it2 == it->second.acq_done_map->end()) ||  (nonce > it2->second)) {
			//printf("adding task\n");
			add_to_task_vector(acq_vector,  hostname, name, nonce, cid);
		}	
	}
	//printf("server: returning from acquire\n");	
	return lock_protocol::RETRY;
}


//nonce should be the same as when acquiring
lock_protocol::status lock_server_cache::release(std::string hostname, std::string name, int nonce, int cid, int & r)
{

	assert(pthread_mutex_lock(&q_lock)==0);
	queries++;
	//if (queries % 10 == 0) {
		printf("QUERIES %d \n", queries);
	//}
	assert(pthread_mutex_unlock(&q_lock)==0);

	if (DEBUG) printf("server: entering release lock %s from client %d !\n", name.c_str(),cid);
	
	//check if release is satisfied already-- a read to shared structure which should be fine
	std::map<std::string, lock_data>::iterator it = locks_map->find(name);
	std::map<int, int>::iterator it2 = it->second.rel_done_map->find(cid);
	if ((it2 == it->second.rel_done_map->end()) || (nonce > it2->second)) {
		add_to_task_vector(rel_vector, hostname, name, nonce, cid);
	}
		
	return lock_protocol::OK;
}

