/*
 * mpdrspeed_coord.c
 *
 * Author: Dave Rochberg
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1998,1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_common.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_getopt.h>
#include <nasd/nasd_timer.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <ctype.h>
#include <time.h>

#define CHECKRC(_rc_,a) {if (_rc_ < 0) {perror(a);exit(1);}}
#define PORT (3840)

typedef struct client_arg_s {
  nasd_threadgroup_t   *group;
  char		       *target;
  nasd_thread_t         handle;
  int			number;
  int			drive;
  int			ready;
} client_arg_t;

int nclients;
client_arg_t *clients;

nasd_threadgroup_t clients_group;

NASD_DECLARE_MUTEX(print_mutex); /* Note weird interaction between print_mutex and go for startup_watcher */

NASD_DECLARE_MUTEX(go_mutex);
NASD_DECLARE_COND(go_cond);
int go=0;

NASD_DECLARE_MUTEX(first_client_mutex);
int first_client = 0;

NASD_DECLARE_MUTEX(drives_stop_mutex);
NASD_DECLARE_COND(drives_stop_cond);
int drives_stop=0;

char uniquifier[1024];
char *special = NULL;

int
make_connection(char *hostname)
{
  struct sockaddr_in	addr;
  struct hostent	*hp;
  int			s;
  int			i,rc;
  int			opt;
  struct hostent	hostent;
  nasd_status_t         nasd_status;

  bzero((char *)&addr,sizeof(addr));
  hp = &hostent;
  nasd_status = nasd_gethostbyname_r(hostname, hp);
  if (nasd_status) {
    fprintf(stderr, "ERROR: got 0x%x (%s) getting address of %s\n",
      nasd_status, nasd_error_string(nasd_status), hostname);
    exit(1);
  }

  i = 0;
  do {
    bcopy((char *)hp->h_addr,(char *)&addr.sin_addr,hp->h_length);
    addr.sin_family=AF_INET;
    addr.sin_port = htons (PORT);
    s = socket(AF_INET,SOCK_STREAM,PF_UNSPEC);
    CHECKRC(s,"socket");
    opt = 1;
    rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY,(char *)&opt,sizeof(int));
    CHECKRC(rc,"Turning off delay");
    rc = connect(s,(struct sockaddr *)&addr,sizeof(struct sockaddr_in));
    if (rc < 0) {
      close(s);
      sleep(1);
    } else {
      break;
    }
    i++;
  } while (i<1000);
  if (i>=1000) {
    printf ("Abort: Could not connect to host %s\n",hostname);/*DB*/
    exit(1);
  }
  return s;
}

void 
startwatch_thread (nasd_threadarg_t *args) 
{
  nasd_delaycounter_t delayer;
  int i;
  
  while (!go) {
    NASD_BEGIN_DELAYCNT(&delayer);
    NASD_DELAY_FROM(&delayer,NASD_USEC_PER_SEC);
    if (!go) {
      NASD_LOCK_MUTEX(print_mutex);
      printf("Waiting for ");
      for (i=0;i<nclients;i++) {
	if (!clients[i].ready) {
	  printf("%s ",clients[i].target);
	}
      }
      printf("\n");
      fflush(stdout);
      NASD_UNLOCK_MUTEX(print_mutex);
    } 
  }
  NASD_EXIT_THREAD(0);
}
  

void
client_thread(
  nasd_threadarg_t  *args)
{
  client_arg_t	*me= (client_arg_t *)args;
  int			s,rc ;	
  int			buf;
  int			i;
  FILE			*syncfi;
  int c;

  i = 0; 
  
  s = make_connection(me->target);
  CHECKRC(s,"Making connection");
  rc = read(s,&buf,4);
  me->ready = 1;
  NASD_THREADGROUP_RUNNING(me->group);
  NASD_LOCK_MUTEX(go_mutex);
  while (!go) 
    NASD_WAIT_COND(go_cond,go_mutex);
  NASD_UNLOCK_MUTEX(go_mutex);
  
  switch (me->drive) {
  case 0:
    rc = write(s,&buf,4);
    CHECKRC(rc,"Write");  
    rc = read(s,&buf,4);
    if (rc<4) {
      printf ("Trouble in reading from client %s\n",me->target);/*DB*/
    }
    NASD_LOCK_MUTEX(first_client_mutex);
    if (0==first_client) {
      first_client=1;
      drives_stop=1;
      NASD_BROADCAST_COND(drives_stop_cond);
    }
    NASD_UNLOCK_MUTEX(first_client_mutex);
    break;
  case 1:
    /* Tell drive to go */
    rc = write(s,&buf,4);
    CHECKRC(rc,"Write");
    NASD_LOCK_MUTEX(drives_stop_mutex);
    while (!drives_stop) 
      NASD_WAIT_COND(drives_stop_cond,drives_stop_mutex);
    NASD_UNLOCK_MUTEX(drives_stop_mutex);
    rc = write(s,&buf,4);
    break;
  default:
    printf("ERROR:  Wrong case in client behavior switch\n");
  }
  NASD_LOCK_MUTEX(print_mutex);
  printf ("------------------\n");/*DB*/
  printf("tag: %s\n",uniquifier);
  if (special) printf("special: %s\n",special);
  printf("%s: %s\n",me->drive?"Drive":"Client",me->target);
  syncfi = fdopen (s,"r");
  while ((c=getc(syncfi))!=EOF) {
    putc(c,stdout);
    fflush(stdout);
  }
  printf("endtag: %s\n",uniquifier);
  fflush(stdout);
  NASD_UNLOCK_MUTEX(print_mutex);

  close(s);
  NASD_THREADGROUP_DONE(me->group);
}


int main (int argc, char **argv) {
  int		i;
  int		arg;
  int		drives=0;
  time_t	time_t;
  char		*p;
  char		c;
  nasd_thread_t startwatch;
  nasd_status_t rc;

  /*
   * XXX these functions have return values- someone should look at them
   */

  nasd_threads_init();
  nasd_init_threadgroup(&clients_group);
  nasd_mutex_init(&print_mutex);
  nasd_mutex_init(&go_mutex);
  nasd_cond_init(&go_cond);
  nasd_mutex_init(&first_client_mutex);
  nasd_mutex_init(&drives_stop_mutex);
  nasd_cond_init(&drives_stop_cond);

  time(&time_t);
  _nasd_timestr_r(time_t, uniquifier);

  p = uniquifier;
  while (*p) {
    if (isspace(*p)) *p='_';
    p++;
  }

  printf ("\n\n%s\n",uniquifier);/*DB*/
  nclients = argc;
  NASD_Malloc(clients, nclients * sizeof(client_arg_t), (client_arg_t *));
  if (clients == NULL) {
    fprintf(stderr, "ERROR: out of memory allocating clients array\n");
    exit(1);
  }
  bzero((char *)clients, nclients * sizeof(client_arg_t));
  nclients =0;

  while (nasd_getopt(argc, argv, "s:", &c)) {
    switch (c) {
    case 's' :
      special=strdup(nasd_optarg);
      break;
    }
  }
  i = 0;
  for (arg = nasd_optind ; arg < argc ; arg++) {
    if (!strcmp(argv[arg],"-d")) {
      drives=1;
      continue;
    }

    clients[i].number = i;
    clients[i].target=argv[arg];
    clients[i].group = &clients_group;
    clients[i].drive = drives;
    clients[i].ready = 0;
    rc = nasd_thread_create(&clients[i].handle,
		       (nasd_threadfunc_t)client_thread,
		       (nasd_threadarg_t)&(clients[i]));
    if (rc) {
      fprintf(stderr, "ERROR: could not create thread i=%d rc=0x%x (%s)\n",
        i, rc, nasd_error_string(rc));
      exit(1);
    }
    NASD_THREADGROUP_STARTED(&clients_group);
    nclients++;
    i++;
  }

  rc = nasd_thread_create(&startwatch,
		     (nasd_threadfunc_t)startwatch_thread,
		     NULL);
  if (rc) {
    fprintf(stderr, "ERROR: could not create startwatch thread rc=0x%x (%s)\n",
      rc, nasd_error_string(rc));
    exit(1);
  }
  
  NASD_THREADGROUP_WAIT_START(&clients_group);
  NASD_LOCK_MUTEX(print_mutex);
  printf ("Broadcasting go\n");fflush(stdout);/*DB*/
  go = 1;
  NASD_BROADCAST_COND(go_cond);
  NASD_UNLOCK_MUTEX(print_mutex);
  NASD_THREADGROUP_WAIT_STOP(&clients_group);
  exit(0);
}
  
  
