#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include "waitq.h"

/*
  Resource locking

  Here is an example of how this is used:
  For DIOs, we want each DIO to be lockable by a specific client process.
  This prevents other clients from changing the DIO value during the lock
  period.

  A set of write locks can only be aquired when nobody else has any of
  the locks, and no higher priority clients want any of the locks.

 */
static int *wlock; // resource usage bitmap (id who holds each lock, if anyone)
static int *wlockct; // count of locks held on each resource
static int *block; // resource wanted bitmap
static int num_write_locks;
static waitq *head;

//-----------------------------------------------------------------------------
void dump_lock(int n) {
  int i;

  printf("ru[%d] = List[",n);
  printf("List[");
  for (i=0;i<num_write_locks;i++) {
    printf("%d",wlock[i]);
    if (i+1 < num_write_locks) printf(",");
  }
  printf("],");
  printf("List[");
  for (i=0;i<num_write_locks;i++) {
    printf("%d",block[i]);
    if (i+1 < num_write_locks) printf(",");
  }
  printf("],");
  dump_waitq(n,head);
  printf("];\n");
}

// initialize the locking system with the specified number of write locks
void lock_init(int write_locks) {
  int i;
  if (wlock) free(wlock);
  if (wlockct) free(wlockct);
  if (block) free(block);
  wlock = malloc(write_locks * sizeof(int));
  wlockct = malloc(write_locks * sizeof(int));
  block = malloc(write_locks * sizeof(int));
  for (i=0;i<write_locks;i++) {
    wlock[i] = 0;
    wlockct[i] = 0;
    block[i] = 0;
  }
  num_write_locks = write_locks;
}

// return false if specified resource is locked by anyone other than
// the specified client id
int lockedQ(int clientid,int num) {
  return (wlock[num] && wlock[num] != clientid);
}

// request write lock "num".
// if lock is available, assign lock to "clientid" and return true
// otherwise add "clientid" to the queue and return false
// false is also returned if an invalid lock is specified
int lock_request(int clientid,int num) {
  waitq *q;

  //fprintf(stderr,"lock_request(clientid=%d,num=%d)",clientid,num);
  if (num < 0 || num >= num_write_locks) return 0;
  if (q = waitq_peek(head,clientid)) {
    if (addto_waitq(q,num)) block[num]++;
  } else if (!wlock[num] && !block[num] || (wlock[num] == clientid)) {
    wlock[num] = clientid;
    wlockct[num]++;
    //fprintf(stderr,"succeeds\n");
    return 1;
  } else {
    new_waitq(&head,clientid,num);
    block[num]++;
  }
  //fprintf(stderr,"blocks\n");
  return 0;
}

// request write lock for all nums
// if all locks are available, they are assigned to clickid and return true
// otherwise add "clientid" to the queue and return false
// false is also returned if an invalid lock is specified
int lock_request_multiple(int clientid,int n,int *nums) {
  waitq *q;
  int i;

  for (i=0;i<n;i++) {
    if (nums[i] < 0 || nums[i] >= num_write_locks) return 0;
    if (lockedQ(clientid,nums[i])) {
      break;
    }
  }
  if (i == n) { // ok to lock!
    for (i=0;i<n;i++) {
      assert(lock_request(clientid,nums[i]));
    }
    return 1;
  } else { // must block
    for (i=0;i<n;i++) {
      if (q = waitq_peek(head,clientid)) {
	addto_waitq(q,nums[i]);
      } else {
	new_waitq(&head,clientid,nums[i]);
      }
      block[nums[i]]++;
    }
    return 0;
  }
}

// release the specified lock
// returns true if succeed, otherwise false.
// will fail if client does not own the lock or resource not locked
int lock_release(int clientid,int num) {
  //fprintf(stderr,"lock_release(clientid=%d,num=%d)\n",clientid,num);
  if (wlock[num] != clientid) return 0;
  if (--wlockct[num] == 0) {
    wlock[num] = 0;
  }
  return 1;
}

// releases all waits for the specified client
// this must be called by the server back-end after calling
// lock_get_next_client but before locking anything for that client
// it must also be called if a client goes away
int lock_release_wait(int clientid) {
  waitq *q = waitq_peek(head,clientid);
  int i;

  if (!q) return 0;
  for (i=0;i<q->num_wanted;i++) {
    block[q->wlock[i]]--;
    assert(block[q->wlock[i]] >= 0);
  }
  return remove_waitq(&head,clientid);
}

// this is the back end to lock_release.  whenever
// lock_release is called, it is possible that a new
// client may be able to lock the resources it wants. however
// lock_release is intended to be called from the front
// end of the server, and may be called multiple times to
// release multiple resources. when it completes, the back end of the
// server can call this function to determine if another client
// can be unblocked.
// returns 0 if no client can be unblocked, otherwise returns
// the clientid
// see if any clients are now unblocked (can lock all the resources they want)
// check resources in order. if all resources a client wants, and none of
// them are wanted for an earlier client, then we set it to the next
// client that can run.
int lock_get_next_client() {
  waitq *next = head;
  int *waited = malloc(sizeof(int) * num_write_locks);
  int i;

  for (i=0;i<num_write_locks;i++) {
    waited[i] = 0;
  }
  while (next) {
    // first, check if all the resources we want are unlocked
    for (i=0;i<next->num_wanted;i++) {
      if (wlock[next->wlock[i]]) {
	break;
      }
    }
    if (i == next->num_wanted) {
      // all available, so now check if any client before us 
      // is waiting on any resource we want
      for (i=0;i<next->num_wanted;i++) {
	if (waited[next->wlock[i]]) {
	  break;
	}
      }
      if (i == next->num_wanted) {
	free(waited);
	// remove client from waitq
	// otherwise if it doesn't lock the resources server will get stuck
	remove_waitq(&head,next->clientid); 
	return next->clientid;
      }
    } else {
      // not all available, so mark the ones we want so clients
      // below us won't be able to get them ahead of us
      for (i=0;i<next->num_wanted;i++) {
	waited[next->wlock[i]]++;
      }
    }
  }
  free(waited);
  return -1;
}
