#include <stdlib.h>
#include <unistd.h>

#include "request.h"
#include "mytimelib.h"
#include "myerrors.h"
#include "random.h"
#include "debug.h"


request *getNewRequest(int ID, int type, int hasDelay, long minDelay, long maxDelay, int nbOfParams, int **params) {
  request *req = (request *)(malloc(sizeof(struct request)));
  
  if (req == NULL) {
    criticalError("Allocation of request failed");
  }

  makeNewRequest(req,  ID, type, hasDelay, minDelay, maxDelay, nbOfParams, params);  
  return req;
}


// Delays are in microseconds
void makeNewRequest(request *req, int ID, int type, int hasDelay, long minDelay, long maxDelay, int nbOfParams, int **params) {
  long delay;
  int i;

  req->next = NULL;
  req->listOfRequests = NULL;
  req->nextRequestInList = NULL;

  req->type = type;
  req->ID = ID;
  req->hasDelay = hasDelay;

  if (req->hasDelay > 0) {
    delay = computeLongRandom(minDelay, maxDelay);
    delayToTimeSpec(&(req->delay), delay);
  }

  req->selected = 0;
  req->nbOfParams = nbOfParams;
  req->params = params;

  req->alreadyPending = 0;
  req->delayElapsed = 0;

  req->relatedRequest = NULL;

  if (type == SEND_ASYNC_REQUEST) {
    // Must create a new message
    req->msg = getNewMessageWithParams(nbOfParams);
    for(i=0; i<nbOfParams; i++) {
      req->msg->params[i] = *(params[i]);
    }
  }

}




void destroyRequest(request *req) {
  free((void *)req);
}

int isRequestSelected(request *req) {
  return req->selected;
}

int nbOfRequests(setOfRequests *list) {
  int cpt = 0;
  request *req;

  req = list->head;

  while(req != NULL) {
    cpt ++;
    req = req->nextRequestInList;
  }

  return cpt;
}

request *getRequestAtIndex(setOfRequests *list, int index) {
  int cpt = 0;
  request * req = list->head;

  while(cpt < index) {
    req = req->nextRequestInList;
    cpt ++;
  }

  return req;
  
}


request * addToRequestQueue(request *list, request *requestToAdd) {
  request *origin = list;

  if (list == NULL) {
    return requestToAdd;
  }

  while(list->next != NULL) {
    list = list->next;
  }
  
  list->next = requestToAdd;

  requestToAdd->next = NULL;

  return origin;
}

request * removeRequestFromList(request *list, request *requestToRemove) {
  request *origin = list;

  if (list == requestToRemove) {
    return list->next;
  }


  while(list->next != requestToRemove) {
    list = list->next;
  }

  list->next = requestToRemove->next;

  return origin;
} 


void copyParameters(request *src, request *dst) {
  int i;
  for(i=0; i<dst->nbOfParams; i++) {
    *(dst->params[i]) = *(src->params[i]);
  }
}


void clearListOfRequests(setOfRequests *list) {
  list->head = NULL;
}

setOfRequests *newListOfRequests(pthread_cond_t *wakeupCondition, pthread_mutex_t *mutex) {
  setOfRequests *list = (setOfRequests *)(malloc(sizeof(setOfRequests)));
  list->head = NULL;
  list->wakeupCondition = wakeupCondition;
  list->mutex = mutex;

  return list;
}

void fillListOfRequests(setOfRequests *list, char *name, pthread_cond_t *wakeupCondition, pthread_mutex_t *mutex) {
  list->head = NULL;
  list->owner = name;
  list->wakeupCondition = wakeupCondition;
  list->mutex = mutex;
}


void addRequestToList(setOfRequests *list, request* req) {
  request *tmpreq;

  if (list == NULL) {
    criticalError("NULL List in addRequestToList");
  }

  if (req == NULL) {
    criticalError("NULL req in addRequestToList");
  }

  req->listOfRequests = list;

  if (list->head == NULL) {
    list->head = req;
    req->nextRequestInList = NULL;
    return;
  }

  tmpreq = list->head;
  while(tmpreq->nextRequestInList != NULL) {
    tmpreq = tmpreq->nextRequestInList;
  }

  tmpreq->nextRequestInList = req;
  req->nextRequestInList = NULL;
}

void removeAllPendingRequestsFromPendingLists(request *req, int apartThisOne) {
  setOfRequests *list = req->listOfRequests;
  request *reqtmp;

  if (list == NULL) {
    return;
  }

  reqtmp = list->head;

  while(reqtmp != NULL) {
    debugInt("Considering request of type", reqtmp->type);
      if (reqtmp->alreadyPending) {
	if (reqtmp->type ==  RECEIVE_SYNC_REQUEST) {
	  debugMsg("Removing send sync request from inWaitQueue");
	  reqtmp->syncChannel->inWaitQueue = removeRequestFromList(reqtmp->syncChannel->inWaitQueue, reqtmp);
	  debugMsg("done");
	}

	if (reqtmp->type ==  SEND_SYNC_REQUEST) {
	  debugMsg("Removing receive sync request from outWaitQueue");
	  reqtmp->syncChannel->outWaitQueue = removeRequestFromList(reqtmp->syncChannel->outWaitQueue, reqtmp);
	  debugMsg("done");
	}

	if (reqtmp->type ==  RECEIVE_BROADCAST_REQUEST) {
	  debugMsg("Removing broadcast receive request from inWaitQueue");
	  reqtmp->syncChannel->inWaitQueue = removeRequestFromList(reqtmp->syncChannel->inWaitQueue, reqtmp);
	  debugMsg("done");
	}
      }
    reqtmp = reqtmp->nextRequestInList;
  }
}


// Identical means belonging to the same ListOfRequest
// Returns the identical request if found, otherwise, null
request *hasIdenticalRequestInListOfSelectedRequests(request *req, request *list) {
 
  while(list != NULL) {
    if (list->listOfRequests == req->listOfRequests) {
      return list;
    }
    list = list->relatedRequest;
  }

  return NULL;
}

request* replaceInListOfSelectedRequests(request *oldRequest, request *newRequest, request *list) {
  request *head = list;

  if (list == oldRequest) {
    newRequest->relatedRequest = oldRequest->relatedRequest;
    return newRequest;
  }

  //list=list->relatedRequest;
  while(list->relatedRequest != oldRequest) {
    list = list->relatedRequest;
  }

  list->relatedRequest = newRequest;
  newRequest->relatedRequest = oldRequest->relatedRequest;

  return head;
}


int nbOfRelatedRequests(request *list) {
  int cpt = 0;
  while(list->relatedRequest != NULL) {
    cpt ++;
    list = list->relatedRequest;
  }

  return cpt;
}