// the lock server implementation

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

// assumption: no failures

lock_server::lock_server()
{
    printf("new lock server\n");
    assert(pthread_mutex_init(&lock_lst_mutex,0)==0);
    assert(pthread_mutex_init(&rpc_lst_mutex,0)==0);
}

lock_protocol::status
lock_server::stat(std::string name, int &r){
    if (lock_lst.count(name) == 0){
        r = 0;
    } else {
        r = lock_lst[name]->stat();
    }
    return lock_protocol::OK;
}

lock_protocol::status
lock_server::subscribe(int x,int &r)
{
    int cl_id = random();
    rpc_lst *req_lst = new rpc_lst();
    assert(pthread_mutex_lock(&rpc_lst_mutex) == 0);
    while(cl_rpc_lst.count(cl_id)!=0){
        cl_id = random();
    }
    cl_rpc_lst[cl_id] = req_lst;
    assert(pthread_mutex_unlock(&rpc_lst_mutex) == 0);
    r = cl_id;
    return lock_protocol::OK;
    //printf("****New client %d\n",cl_id);
}
  

lock_protocol::status
lock_server::acquire(std::string name, int cl_id, int rpc_id, int &r)
{
    printf("BEGIN: Client: %d; Request: %d; acquire %s\n",cl_id,rpc_id,name.c_str());
    rpc_lst *req_lst;
    int result ;
    assert(cl_rpc_lst.count(cl_id) != 0);
    req_lst = cl_rpc_lst[cl_id];
    if (req_lst->find(rpc_id,result)){
        if(result == 100000){
            throw 1;
        } else {
            return result;
        }
    }
  
    assert(pthread_mutex_lock(&lock_lst_mutex) == 0);
    if(lock_lst.count(name) == 0){
        // new lock
        named_lock *l = new named_lock();
        lock_lst[name] = l;
        assert(pthread_mutex_unlock(&lock_lst_mutex) == 0);
        l->acquire();
    } else {
        assert(pthread_mutex_unlock(&lock_lst_mutex) == 0);
        named_lock *l = lock_lst[name];
        l->acquire();
    }
    req_lst->put_result(rpc_id, lock_protocol::OK, "");
    //printf("END: Client: %d; Request: %d; acquire %s\n",cl_id,rpc_id,name.c_str());
    return lock_protocol::OK; 
}

lock_protocol::status
lock_server::release(std::string name, int cl_id, int rpc_id, int &r)
{  
    printf("BEGIN: Client: %d; Request: %d; release %s\n",cl_id,rpc_id,name.c_str());
    rpc_lst *req_lst;
    int result ;
    assert(cl_rpc_lst.count(cl_id) != 0);
    req_lst = cl_rpc_lst[cl_id];
    if (req_lst->find(rpc_id,result)){
        if(result == 100000){
            throw 1;
        } else {
            return result;
        }
    }

    assert(pthread_mutex_lock(&lock_lst_mutex) == 0);
    if (lock_lst.count(name) == 0){
        assert(pthread_mutex_unlock(&lock_lst_mutex) == 0);
        req_lst->put_result(rpc_id, lock_protocol::NOENT, "");
        //printf("BEGIN: Client: %d; Request: %d; release %s\n",cl_id,rpc_id,name.c_str());
        return lock_protocol::NOENT;
    } else{
        assert(pthread_mutex_unlock(&lock_lst_mutex) == 0);
        named_lock *l = lock_lst[name];
        //printf("    Release lock %s\n", name.c_str());
        l->release();
        req_lst->put_result(rpc_id, lock_protocol::OK, "");
        //printf("END: Client: %d; Request: %d; release %s\n",cl_id,rpc_id,name.c_str());
        return lock_protocol::OK;
    }
}

 
lock_server::named_lock::named_lock() {
    assert(pthread_mutex_init(&lock_mutex, 0) == 0);
    assert(pthread_cond_init(&lock_cond, 0) == 0);
    taken = false;
    times_acquired = 0;
}

void
lock_server::named_lock::acquire() {
    assert(pthread_mutex_lock(&lock_mutex) == 0);
    while (taken == true) {
        assert(pthread_cond_wait(&lock_cond,&lock_mutex) == 0);
    }
    taken = true;
    times_acquired++;
    assert(pthread_mutex_unlock(&lock_mutex) == 0);
    return;
}

void
lock_server::named_lock::release() {
    assert(pthread_mutex_lock(&lock_mutex) == 0);
    taken = false;
    assert(pthread_cond_broadcast(&lock_cond) == 0);
    assert(pthread_mutex_unlock(&lock_mutex) == 0);
    return;
}

int 
lock_server::named_lock::stat() {
    return times_acquired;
}

rpc_lst::rpc_lst() {
    assert(pthread_mutex_init(&lst_mutex,0) == 0);
}

bool rpc_lst::find(int id, int &result){
    assert(pthread_mutex_lock(&lst_mutex) == 0);
    if (lst.count(id) == 0){
        lst[id] = 100000;
        assert(pthread_mutex_unlock(&lst_mutex) == 0);
        return false;
    }else{
        result = lst[id];
        assert(pthread_mutex_unlock(&lst_mutex) == 0);
        return true;
    }
}
bool rpc_lst::find(int id, std::string & last_owner){
    assert(pthread_mutex_lock(&lst_mutex) == 0);
    if (last_owners.count(id) == 0){
        assert(pthread_mutex_unlock(&lst_mutex) == 0);
        return false;
    }else{
        last_owner = last_owners[id];
        assert(pthread_mutex_unlock(&lst_mutex) == 0);
        return true;
    }
}

void  rpc_lst::print_list(){
    assert(pthread_mutex_lock(&lst_mutex) == 0);
    std::map<int,int>::iterator it = lst.begin();
    while (it != lst.end()) {    	
	printf("(%d, %d, %s) ", it->first, lst[it->first], last_owners[it->first].c_str());
	it++; 
    }
    printf("\n");
    assert(pthread_mutex_unlock(&lst_mutex) == 0);
      
}

int rpc_lst::put_result(int id, int result, std::string last_owner){
    assert(pthread_mutex_lock(&lst_mutex) == 0);
    lst[id] = result;
    last_owners[id] = last_owner;
    assert(pthread_mutex_unlock(&lst_mutex) == 0);
    //printf("  Finish request id %d with code %d\n",id,lst[id]);
    return 0;
}

