/*
 * client-side RPC API for lock_server.
 */

#include "amisc.h"
#include "async.h"
#include "arpc.h"
#include "lock_proto.h"
#include "lock_client.h"

#define DBG 0

//
// public interface
//

// create a connection to the lock server.
// application should use a single lock_client
// object for all locks.
lock_client::lock_client(struct sockaddr_in sin)
{
  fd = inetsocket(SOCK_DGRAM);
  if(fd < 0){
    fprintf(stderr, "error: lock_client: inetsocket failed: %s\n",
            strerror(errno));
    exit(1);
  }
  x = axprt_dgram::alloc(fd);
  c = aclnt::alloc(x, lock_prog_1);
  sxx = asrv::alloc(x, lock_prog_1, wrap(lock_client::dispatch, this));

  server = sin;
}

// the application wants a lock.
// grant() will call the application's cb callback when
// the server gives us the lock (with a GRANT RPC).
// thus acquire() returns before the lock has
// been acquired.
void
lock_client::acquire(str name, callback<void>::ref cb)
{
  qitem *qi = new qitem(name, cb);
  queue.insert_head(qi);
  send_acquire(name);
}

// the application is done with a lock, so give it back to the server.
void
lock_client::release(str name)
{
  lock *l = locks[name];
  if(l == 0){
    fprintf(stderr, "error: release(%s) but not held (1)\n",
            hex(name).cstr());
    exit(1);
  }
  delaycb(0, wrap(this, &lock_client::really_release, name));
}

//
// private functions.
//

// convert a bit-string to a hex string for debug printouts.
str
lock_client::hex(str s)
{
  char buf[64];
  unsigned int len = s.len();
  const char *p = s.cstr();
  unsigned int i;

  buf[0] = '\0';
  for(i = 0; i < len && i*2+1 < sizeof(buf); i++){
    sprintf(buf + (i * 2), "%02x", p[i] & 0xff);
  }
  
  return str(buf);
}

// we've sent the acquire request to the lock server, now we
// have to wait for the server to send a GRANT RPC.
void
lock_client::acquire_cb1(bool *r, clnt_stat err)
{
  if(err != 0){
    fprintf(stderr, "error: lock_client::acquire_cb1: rpc err %d, quitting\n",
            err);
    exit(1);
  }
  delete r;
}

void
lock_client::send_acquire(str name)
{
  bool *r = new bool;
  lname xn;
  xn = name;

#if DBG
  printf("sending acquire %s\n", hex(name).cstr());
#endif

  c->call(LOCK_ACQUIRE, &xn, r,
          wrap(this, &lock_client::acquire_cb1, r),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) &server);
}

void
lock_client::release_cb(bool *r, clnt_stat err)
{
  if(err != 0){
    fprintf(stderr, "error: lock_client::release_cb: failed RPC, quitting\n");
    exit(1);
  }
  delete r;
}

// send the lock back to the server.
void
lock_client::really_release(str name)
{
  lock *l = locks[name];
  if(l == 0){
    fprintf(stderr, "error: release(%s) but not held (2)\n",
            hex(name).cstr());
    exit(1);
  }

#if DBG
  printf("sending release %s\n", hex(name).cstr());
#endif

  locks.remove(l);
  delete l;

  bool *r = new bool;
  lname xn;
  xn = name;
  c->call(LOCK_RELEASE, &xn, r,
          wrap(this, &lock_client::release_cb, r),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) &server);
}

// lock server is giving us a lock.
void
lock_client::grant(svccb *sbp)
{
  lname *arg = sbp->Xtmpl getarg<lname>();
  str name(arg->base(), arg->size());
  bool *res = sbp->Xtmpl getres<bool>();

#if DBG
  printf("got grant %s\n", hex(name).cstr());
#endif

  lock *l = locks[name];
  if(l != 0){
    fprintf(stderr, "error: server granted lock %s which we already hold!\n",
            hex(name).cstr());
    exit(1);
  }

  // find an app callback waiting for this lock.
  bool found = false;
  qitem *i, *ni;
  for(i = queue.first; i; i = ni){
    ni = queue.next(i);
    if(i->name == name){
      l = new lock(name);
      locks.insert(l);
      queue.remove(i);
      delaycb(0, i->cb);
      delete i;
      found = true;
      break;
    }
  }
  if(found == false){
    fprintf(stderr, "error: server granted lock %s but we were not waiting for it.\n",
            hex(name).cstr());
    exit(1);
  }

  *res = true;
  sbp->reply(res);
}

// handle GRANT RPCs from the server.
void
lock_client::dispatch(lock_client *lc, svccb *sbp)
{
  switch(sbp->proc()){
  case LOCK_GRANT:
    lc->grant(sbp);
    break;
  default:
    fprintf(stderr, "error: lock_client: unknown RPC %d\n", sbp->proc());
    sbp->reject(PROC_UNAVAIL);
    break;
  }
}

