/* $Id: test_arpc.C,v 1.1 2003/12/09 23:06:58 dbg Exp $ */

#include "async.h"
#include "arpc.h"
#include "test_arpc_prot.h"


/*
 * tcpaccept_t
 */

class tcpaccept_t {
  int s;
  typedef callback<void, int, struct sockaddr *, socklen_t>::ref cb_t;
  cb_t::ref cb;
  int naccepts;
        
  void accept_client ();

public:
  tcpaccept_t (int s, cb_t::ref cb, int naccepts = 0);
  ~tcpaccept_t ();
};

tcpaccept_t::tcpaccept_t (int s, cb_t::ref cb, int naccepts = 0)
  : s (s), cb (cb), naccepts (naccepts)
{
  assert (s > -1);
  make_async (s);
  fdcb (s, selread, wrap (this, &tcpaccept_t::accept_client));
}

tcpaccept_t::~tcpaccept_t ()
{
  if (s > -1) {
    fdcb (s, selread, NULL);
    close (s);
  }
}

void
tcpaccept_t::accept_client ()
{
  struct sockaddr addr;
  socklen_t addrlen;

  int c = accept (s, &addr, &addrlen);
  (*cb) (c, &addr, addrlen);

  if (naccepts && --naccepts == 0) {
    fdcb (s, selread, NULL);
    close (s);
    s = -1;
    delete this;
  }
}

tcpaccept_t *
tcp_accept_clients (int s, tcpaccept_t::cb_t::ref cb, int naccepts = 0)
{
  return New tcpaccept_t (s, cb, naccepts);
}

void
tcp_accept_cancel (tcpaccept_t *t)
{
  delete t;
}


/*
 * rpc server
 */

ptr<asrv_resumable> srv = NULL;
ptr<axprt_stream> srvxprt;

int current = 0;

void
send_reply (svccb *sbp)
{
  sbp->reply (NULL);
}

void
server_dispatch (svccb *sbp)
{
  if (!sbp) {
    warn ("server: connection broken\n");
    return;
  }

  switch (sbp->proc ()) {
  case NUM_PUT:
    {
      current++;
      int *n = sbp->template getarg<int> ();
      if (*n != current) {
        warn ("messages arrived out of order: expected %d, got %d\n",
              current, *n);
        exit (1);
      }

      static bool mode = false;
      if (mode)
       delaycb (5, wrap (send_reply, sbp));
      else
        send_reply (sbp);
      mode = !mode;

      break;
    }
  default:
    sbp->reject (PROC_UNAVAIL);
    break;
  }
}

int connection;

void
handle_client (int c, struct sockaddr *addr, socklen_t addrlen)
{
  if (c < 0)
    fatal ("server: accept() failed: %s\n", strerror(errno));
  connection = c;

  struct sockaddr_in *sin = (sockaddr_in *) addr;
  warn ("server: accepted connection from %s:%d\n",
        inet_ntoa(sin->sin_addr), sin->sin_port);

  ref<axprt_stream> xprt = axprt_stream::alloc (c);
  srvxprt = xprt;

  if (!srv) {
    warn ("server: starting new asrv\n");
    srv = asrv_resumable::alloc (xprt, num_prog_1, wrap (server_dispatch));
  }
  else {
    warn ("server: resuming old asrv\n");
    if (!srv->resume (xprt))
      fatal ("couldn't resume server\n");
  }
}

u_int16_t server_port;
tcpaccept_t *tcpa;

void
create_server ()
{
  int s = inetsocket (SOCK_STREAM, 0, INADDR_ANY);
  if (!s)
    fatal ("socket() failed: %s", strerror (errno));

  struct sockaddr_in sin;
  socklen_t slen;
  if (getsockname (s, (sockaddr *) &sin, &slen))
    fatal ("getsockname() failed: %s", strerror (errno));
  server_port = ntohs (sin.sin_port);

  if (listen (s, 4))
    fatal ("listen() failed: %s", strerror (errno));

  warn ("server: listening on port %d ...\n", server_port);
  tcpa = tcp_accept_clients (s, wrap (handle_client));
}


/*
 * rpc client
 */

int NMAX = 32;
int n;
ptr<aclnt_resumable> clnt = NULL;

void create_client (callback<void>::ptr cb);

#if 0
void
exit_on_eof ()
{
  warn ("client: broken connection\n");
  exit (1);
}
#endif

void
warn_on_eof ()
{
  warn ("client: lost a connection\n");
}

void
resume_on_fail ()
{
  warn ("client: connection failed; attempting to resume\n");
  create_client (NULL);
}

int acked = 0;

void
finish ()
{
  tcp_accept_cancel (tcpa);
  clnt = NULL;
  srvxprt = NULL;
  srv = NULL;
  // XXX: srv doesn't get destroyed (refs remain)

  warn ("client: finished\n");
  exit (0);
}

void
num_ack (int *np, clnt_stat err)
{
  if (err) {
    warn ("client: received error %d\n", err);
    exit (1);
  }

  ++acked;
  // if (*np != acked)
  //   fatal ("got replies out of order\n");
  if (acked == NMAX)
    finish ();

  delete np;
}

void
num_put (int n)
{
  int *np = New int (n);
  clnt->call (NUM_PUT, &n, NULL, wrap (num_ack, np));
}

void
nums_put (int i)
{
  static int n = 1;
  for (; i > 0 && n <= NMAX; --i, ++n)
    num_put (n);
}

void
client_connected (callback<void>::ptr cb, int s)
{
  if (s < 0)
    fatal ("client failed to connect\n");

  warn ("client: connected\n");
  ref<axprt_stream> xprt  = axprt_stream::alloc (s);
  if (!clnt) {
    clnt = aclnt_resumable::alloc (xprt, num_prog_1);
    clnt->seteofcb (wrap (warn_on_eof));
    clnt->setfailcb (wrap (resume_on_fail));

    if (cb)
      cb ();
  }
  else {
    warn ("client: resuming ...\n");
    if (clnt->resume (xprt))
      warn ("client: resume completed\n");
    else {
      warn ("client: resume failed\n");
      exit (1);
    }
  }
}

void
create_client (callback<void>::ptr cb)
{
  in_addr_t loop = static_cast<in_addr_t> (ntohl(INADDR_LOOPBACK));
  in_addr *loopp = reinterpret_cast<in_addr *> (&loop); // XXX
  warn ("client: connecting to %s:%d ...\n", inet_ntoa(*loopp), server_port);
  tcpconnect (*loopp, server_port, wrap(client_connected, cb));
}


/*
 * main
 */

void
sabotage ()
{
  shutdown (connection, SHUT_WR);
}

void
send_numbers ()
{
  int sent = 0;

  nums_put (4);
  sent += 4;

  int delay = 0;
  while (sent < NMAX) {
    delaycb (++delay, wrap (nums_put, 4));
    sent += 4;
  }
}

int
main (int argc, char **argv)
{
  create_server();
  create_client(wrap (send_numbers));

  delaycb (2, wrap (sabotage));
  delaycb (7, wrap (sabotage));
  amain();
}
