//
// Test a lock server.
// Creates two separate client connections to a lock server,
// checks that the server only grants a lock to one client
// at a time.
//

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

static void
usage ()
{
  fprintf(stderr, "Usage: lock_tester lock-server-host lock-server-port\n");
  exit (1);
}

// convert a bit-string to a hex string for debug printouts.
str
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);
}

// check_grant() and check_release() check that the lock server
// doesn't grant the same lock to both clients.
// it assumes that lock names are distinct in the first byte.
int count[256];

void
check_grant(str name)
{
  int x = name.cstr()[0] & 0xff;
  if(count[x] != 0){
    fprintf(stderr, "error: server granted %s twice\n", hex(name).cstr());
    exit(1);
  }
  count[x] += 1;
}

void
check_release(str name)
{
  int x = name.cstr()[0] & 0xff;
  if(count[x] != 1){
    fprintf(stderr, "error: client released un-held lock %s\n", hex(name).cstr());
    exit(1);
  }
  count[x] -= 1;
}

void
test8()
{
  exit(0);
}

int finished = 0;

void
test7(lock_client *lc, str name)
{
  check_release(name);
  lc->release(name);
  finished++;
  if(finished >= 16){
    printf("lock_tester: passed tests\n");
    delaycb(1, wrap(&test8));
  }
}

void
test6(lock_client *lc, str name)
{
  check_grant(name);
  delaycb(random() % 2, wrap(&test7, lc, name));
}

void
test5(lock_client *lcs[])
{
  printf("Acquire a and b from two clients:\n");
  // issue concurrent requests from both clients.
  for(int i = 0; i < 2; i++){
    lcs[i]->acquire("a", wrap(&test6, lcs[i], "a"));
    lcs[i]->acquire("a", wrap(&test6, lcs[i], "a"));
    lcs[i]->acquire("a", wrap(&test6, lcs[i], "a"));
    lcs[i]->acquire("a", wrap(&test6, lcs[i], "a"));
    lcs[i]->acquire("b", wrap(&test6, lcs[i], "b"));
    lcs[i]->acquire("b", wrap(&test6, lcs[i], "b"));
    lcs[i]->acquire("b", wrap(&test6, lcs[i], "b"));
    lcs[i]->acquire("b", wrap(&test6, lcs[i], "b"));
  }
}

int aaaa_count = 0;

void
aaaa2(lock_client *lcs[])
{
  check_grant("a");
  check_release("a");
  lcs[0]->release("a");
  assert(aaaa_count <= 1);
  aaaa_count += 1;
  if(aaaa_count == 2)
    test5(lcs);
}

void
aaaa1(lock_client *lcs[])
{
  printf("Acquire a, acquire a, release a, release a:\n");
  lcs[0]->acquire("a", wrap(&aaaa2, lcs));
  lcs[0]->acquire("a", wrap(&aaaa2, lcs));
}

void
abba3(lock_client *lcs[])
{
  check_grant("b");
  check_release("b");
  lcs[0]->release("b");
  check_release("a");
  lcs[0]->release("a");
  aaaa1(lcs);
}

void
abba2(lock_client *lcs[])
{
  check_grant("a");
  lcs[0]->acquire("b", wrap(&abba3, lcs));
}

void
abba1(lock_client *lcs[], str name)
{
  check_release(name);
  lcs[0]->release(name);

  printf("Acquire a, acquire b, release b, release a:\n");
  lcs[0]->acquire("a", wrap(&abba2, lcs));
}

void
test4(lock_client *lcs[], str name)
{
  check_grant(name);
  delaycb(0, wrap(&abba1, lcs, name));
}

void
test3(lock_client *lcs[], str name)
{
  lcs[0]->acquire(name, wrap(&test4, lcs, name));
}

void
test2(lock_client *lcs[], str name)
{
  check_release(name);
  lcs[0]->release(name);
  test3(lcs, name);
}

void
test1(lock_client *lcs[], str name)
{
  check_grant(name);
  delaycb(1, wrap(&test2, lcs, name));
}

void
test(lock_client *lcs[])
{
  printf("Acquire a, release a, acquire a, release a:\n");
  // first just acquire and then release a single lock,
  // then acquire and release it again.
  lcs[0]->acquire("a", wrap(&test1, lcs, "a"));
}

// dns_hostbyname() has found lock server's host name.
void
got_bs(char *port, ptr<hostent> h, int err)
{
  if(!h || err != 0){
    fprintf(stderr, "error: could not resolve lock server host name\n");
    exit(1);
  }

  struct sockaddr_in sin;
  bzero(&sin, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(atoi(port));
  sin.sin_addr = *(in_addr*)h->h_addr;

  // make two connections to the lock server,
  // as if we were two separate clients.
  lock_client **lcs = New lock_client * [2];
  for(int i = 0; i < 2; i++)
    lcs[i] = New lock_client(sin);

  test(lcs);
}

int
main (int argc, char **argv)
{
  setprogname (argv[0]);
  if (argc != 3 || !isdigit(argv[2][0]))
    usage ();

  srandom(getpid());

  // look up lock server's host name
  dns_hostbyname(argv[1],
                 wrap(&got_bs, argv[2]),
                 true, true);

  amain ();
}
