/*
 * client-side glue for blockdbd.
 */

#include "amisc.h"
#include "async.h"
#include "arpc.h"
#include "block_proto.h"
#include "dns.h"
#include "blockdbc.h"

blockdbc::blockdbc(struct sockaddr_in sin)
{
  fd = inetsocket(SOCK_DGRAM);
  if(fd < 0){
    fprintf(stderr, "blockdbc: inetsocket failed: %s\n",
            strerror(errno));
    exit(1);
  }
  x = axprt_dgram::alloc(fd);
  c = aclnt::alloc(x, block_prog_1);

  server = sin;
}

str
blockdbc::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);
}

void
blockdbc::put_done(callback<void, bool>::ref cb,
             bool *res, clnt_stat err)
{
  if(err || *res != true){
    fprintf(stderr, "blockdbc: put RPC failed: err=%d ok=%d errno=%d\n",
            err, *res, errno);
    cb(false);
  } else {
    cb(true);
  }
  delete res;
}

void
blockdbc::remove_done(callback<void, bool>::ref cb,
                      bool *res, clnt_stat err)
{
  if(err || *res != true){
    cb(false);
  } else {
    cb(true);
  }
  delete res;
}

void
blockdbc::real_put(str key, str value, callback<void, bool>::ref cb)
{
  put_args a;
  a.key = key;
  a.value = value;

  bool *r = new bool;

  c->call(BLOCK_PUT, &a, r,
          wrap(this, &blockdbc::put_done, cb, r),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) &server);
}


void
blockdbc::put(str key, str value, callback<void, bool>::ref cb)
{
  update_cache(key, value, true);
  (*cb)(true);
}

void
blockdbc::get_done(callback<void, bool, str>::ref cb, str key,
                   get_result *r, clnt_stat err)
{
  if(err || r->ok != true){
    cb(false, "");
  } else {
    update_cache(key, str(r->value.base(), r->value.size()), false);
    cb(true, str(r->value.base(), r->value.size()));
  }

  delete r;
}

void
blockdbc::real_get(str key, callback<void, bool, str>::ref cb)
{
  get_args a;
  a.key = key;
  get_result *r = new get_result;

  c->call(BLOCK_GET, &a, r,
          wrap(this, &blockdbc::get_done, cb, key, r),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) &server);
}

void
blockdbc::get_from_cache(str key, str value,
                         callback<void, bool, str>::ref cb)
{
  (*cb)(true, value);
}



void
blockdbc::get(str key, callback<void, bool, str>::ref cb)
{
  if (cache[key] == NULL) 
  {
    real_get(key, cb);
  } else {
    delaycb(0, wrap(this, &blockdbc::get_from_cache,
                    key, cache[key]->value, cb));
  }
}

void
blockdbc::remove(str key, callback<void, bool>::ref cb)
{

  remove_args a;
  a.key = key;
  bool *r = new bool;

  c->call(BLOCK_REMOVE, &a, r,
          wrap(this, &blockdbc::remove_done, cb, r),
          (AUTH *) 0,
          (xdrproc_t) 0, (xdrproc_t) 0,
          (u_int32_t) 0, (u_int32_t) 0,
          (struct sockaddr *) &server);
}

void
blockdbc::test_cb2(str wanted, bool ok, str value)
{
  if(ok == false || wanted != value){
    fprintf(stderr, "blockdbc: get test failed\n");
    exit(1);
  }
}

void
blockdbc::test_cb1(bool ok)
{
  if(ok == false){
    fprintf(stderr, "blockdbc: put test failed\n");
    exit(1);
  }
  get("kkk", wrap(this, &blockdbc::test_cb2, "vvv"));
}

void
blockdbc::test()
{
  put("kkk", "vvv", wrap(this, &blockdbc::test_cb1));
}

void
blockdbc::update_cache(str key, str val, bool dirty)
{
  cacheent *e;
  e = cache[key];

  if (e) {
    cache.remove(e);
    delete e;
  }

  e = New cacheent;
  e->key = key;
  e->value = val;
  e->dirty = dirty;
  cache.insert(e);
}


void
blockdbc::flush(str key, callback<void>::ref cb)
{
  cacheent *e;
  e = cache[key];

  if (e == NULL) {
    (*cb)();
    return;
  }

  if (e->dirty) {
    cache.remove(e);
    real_put(e->key, e->value, wrap(this, &blockdbc::flush_on_real_put, cb));
  } else {
    cache.remove(e);
    (*cb)();
  }
}

void
blockdbc::flush_on_real_put(callback<void>::ref cb, bool ok)
{
  (*cb)();
}
