# include "kapa_internal.h"

# define MY_PORT 2500
# define MY_PORT_MAX 2520
# define MY_WAIT 100000
# define DEBUG 0

static int Nvalid = 0;
static int *VALID = NULL;

int KapaServerInit (KapaSockAddress *Address) {

  int status, InitSocket, length;

  Address[0].sin_family = AF_INET;
  Address[0].sin_port   = MY_PORT;
  Address[0].sin_addr.s_addr = INADDR_ANY; // use this line to bind any address / port?

retry_server:

  length = sizeof(Address[0]);

  InitSocket = socket (PF_INET, SOCK_STREAM, 0);
  if (InitSocket == -1) {
    perror ("socket: ");
    exit (2);
  }

  if (DEBUG) fprintf (stderr, "init sock: %d, len: %d, port %d\n", InitSocket, length, Address[0].sin_port);
  status = bind (InitSocket, (struct sockaddr *) Address, length);
  if (status == -1) {
    if (errno == EADDRINUSE) {
	close (InitSocket);
	Address[0].sin_port ++;
	if (Address[0].sin_port > MY_PORT_MAX) exit (2);
	goto retry_server;
    }
    perror ("bind: ");
    exit (2);
  }

  /* repeated starts of the server are limited by xinetd or something:
     requires 60sec timeout of the selected socket */

  if (DEBUG) fprintf (stderr, "bound to port: %d\n", Address[0].sin_port);
  status = listen (InitSocket, 10);
  if (status == -1) {
    perror ("listen: ");
    exit (2);
  }
  return (InitSocket);
}

int KapaServerWait (int InitSocket, KapaSockAddress *Address) {

  int i, status, BindSocket;
  KapaSockAddress Address_in;
  socklen_t length;
  u_int32_t addr;
  fd_set rfds;
  struct timeval wait;

  Address_in = Address[0];

  length = sizeof(Address_in);

  wait.tv_sec = 0;
  wait.tv_usec = MY_WAIT;

  /* do I need to clear rfds on each pass? */
  FD_SET (InitSocket, &rfds);
  status = select (InitSocket + 1, &rfds, NULL, NULL, &wait);
  if (status == -1) {
    perror ("select");
    abort ();
  }
  if (!status) return (-1);

  if (DEBUG) fprintf (stderr, "init sock: %d, len: %d\n", InitSocket, length);
  BindSocket = accept (InitSocket, (struct sockaddr *) &Address_in, &length);
  if (DEBUG) fprintf (stderr, "bind sock: %d\n", BindSocket);
  if (BindSocket == -1) {
    perror ("accept: ");
    exit (2);
  }

  addr = Address_in.sin_addr.s_addr;
  if (DEBUG) {
    fprintf (stderr, "incoming connection from: ");
    fprintf (stderr, " %u", (0xff & (addr >>  0)));
    fprintf (stderr, ".%u", (0xff & (addr >>  8)));
    fprintf (stderr, ".%u", (0xff & (addr >> 16)));
    fprintf (stderr, ".%u", (0xff & (addr >> 24)));
    fprintf (stderr, "\n");
  }

  if (Nvalid == 0) goto accepted;

  for (i = 0; i < Nvalid; i++) {
    /* valid IP addresses may be machines (120.90.121.142) or 
       class C networks (120.90.121.0) */
       
    /* for machine, address must match */
    if ((0xff & (VALID[i] >> 24)) != 0) {
      if (addr == VALID[i]) goto accepted;
    }

    /* for network, lower three bytes of address must match */
    if ((0xff & (VALID[i] >> 24)) == 0) {
      if ((0x00ffffff & addr) == VALID[i]) goto accepted;
    }
  }

  if (DEBUG) fprintf (stderr, "connection rejected\n");
  close (BindSocket);
  return (-1);

accepted:
  if (DEBUG) fprintf (stderr, "connection accepted\n");
  return (BindSocket);
}

/* load valid ip list */
int KapaDefineValidIP (char *ipstring) {

  int ip1, ip2, ip3, ip4, test, status;
  char string[80];

  if (Nvalid == 0) {
    Nvalid = 1;
    ALLOCATE (VALID, int, Nvalid);
  } else {
    Nvalid ++;
    REALLOCATE (VALID, int, Nvalid);
  }

  status = sscanf (ipstring, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4);
  test = TRUE;
  test &= (status == 4);
  test &= ((ip1 > 0) && (ip1 < 256)); 
  test &= ((ip2 > 0) && (ip2 < 256)); 
  test &= ((ip3 > 0) && (ip3 < 256)); 
  test &= ((ip4 >=0) && (ip4 < 256)); 
  if (!test) {
    fprintf (stderr, "invalid IP address %s\n", string);
    exit (2);
  }
  VALID[Nvalid-1] = ip1 | (ip2 << 8) | (ip3 << 16) | (ip4 << 24);
  return (TRUE);
}

/* connect to a running server on the specified host */
int KapaClientSocket (char *hostname) {

  int i, status, InitSocket, length;
  KapaSockAddress Address;
  struct hostent  *host;
  char tmpline[80], hostip[80];

  host = gethostbyname (hostname);
  bzero (hostip, 80);
  for (i = 0; i < host[0].h_length; i++) {
    sprintf (tmpline, "%u", (0xff & host[0].h_addr[i]));
    strcat (hostip, tmpline);
    if (i < host[0].h_length - 1) strcat (hostip, ".");
  }

  if (DEBUG) {
    fprintf (stderr, "trying %s (%s:%d)...", host[0].h_name, hostip, MY_PORT);
  }

  Address.sin_family = AF_INET;
  Address.sin_port   = MY_PORT;

retry_client:
  status = inet_aton (hostip, &Address.sin_addr);
  if (!status) {
    fprintf (stderr, "invalid address\n");
    exit (2);
  }

  length = sizeof(Address);

  InitSocket = socket (PF_INET, SOCK_STREAM, 0);
  if (InitSocket == -1) {
    perror ("socket: ");
    exit (2);
  }

  status = connect (InitSocket, (struct sockaddr *) &Address, length);
  if (status == -1) {
    if (DEBUG) fprintf (stderr, "error connecting: %d\n", errno);
    if (errno == ECONNREFUSED) {
      close (InitSocket);
      Address.sin_port ++;
      if (Address.sin_port > MY_PORT_MAX) return (-1);
      goto retry_client;
    }
    perror ("connect: ");
    exit (2);
  }

  if (DEBUG) fprintf (stderr, "connected on port: %d\n", Address.sin_port);
  if (DEBUG) fprintf (stderr, "connected\n");

  // the client uses a BLOCKing socket by default
  fcntl (InitSocket, F_SETFL, !O_NONBLOCK); 
  return (InitSocket);
}

int KapaOpen (char *kapa_exec, char *kapa_name) {

  // kapa_exec may be kapa://host, in which case we attempt to connect to an 
  // already running kapa, or the program path, in which case we are supposed
  // to launch it locally, then connect to it.

  int sock, Ntry;
  char line[128];

  if (!strncmp (kapa_exec, "kapa://", 7)) {
    sock = KapaClientSocket (&kapa_exec[7]);
    return (sock);
  }

  if (kapa_name == NULL) {
    sprintf (line, "%s &", kapa_exec);
  } else {
    sprintf (line, "%s -name '%s' &", kapa_exec, kapa_name);
  }
  system (line);

# define NTRY 500
  Ntry = 0;
  while (Ntry < NTRY) {
    sock = KapaClientSocket ("localhost");
    if (sock != -1) break;
    // if (errno != EAGAIN) break;
    if (errno != ECONNREFUSED) break;
    usleep (10000);
    Ntry ++;
  }

  if (sock < 0) return (-1);
  return (sock);
}

/* start socketed connection (UNIX Socket) */
int KapaOpenNamedSocket (char *kapa_exec, char *name) {

  int InitSocket, status;
  struct sockaddr_un Address;
  socklen_t AddressLength;
  char temp[128], socket_name[64];
  int Ntry, fd;

  sprintf (socket_name, "/tmp/Kapa.XXXXXX");
  if ((fd = mkstemp (socket_name)) == -1) {
    fprintf (stderr, "error starting kapa\n");
    return (-1);
  }
  close (fd);
  unlink (socket_name);

  strcpy (Address.sun_path, socket_name); 
  Address.sun_family = AF_UNIX; 
  InitSocket = socket (AF_UNIX, SOCK_STREAM, 0); 
  status = bind (InitSocket, (struct sockaddr *) &Address, sizeof (Address));
  status = listen (InitSocket, 1);
  
  if (name == NULL) {
    sprintf (temp, "%s -socket %s &", kapa_exec, socket_name);
  } else {
    sprintf (temp, "%s -socket %s -name %s &", kapa_exec, socket_name, name);
  }
  system (temp);

  AddressLength =  sizeof (Address);
  fcntl (InitSocket, F_SETFL, O_NONBLOCK); 

# define NTRY 500
  Ntry = 0;
  while (Ntry < NTRY) {
    fd = accept (InitSocket, (struct sockaddr *)&Address, &AddressLength);
    if (fd != -1) break;
    if (errno != EAGAIN) break;
    usleep (10000);
    Ntry ++;
  }

  if (fd < 0) return (-1);

  // the client uses a BLOCKing socket by default
  fcntl (fd, F_SETFL, !O_NONBLOCK); 
  return (fd);
}

// wait for the initiating process to connect to the socket
int KapaWaitNamedSocket (char *sockpath) {

  int sock, status;
  struct sockaddr_un Address;

  strcpy (Address.sun_path, sockpath);
  Address.sun_family = AF_UNIX;
  sock = socket (AF_UNIX, SOCK_STREAM, 0);
  status = connect (sock, (struct sockaddr *) &Address, sizeof (Address));
  if (status < 0) {
    fprintf (stderr, "unsuccessful connection: %d\n", status);
    exit (0);
  }

  // the server uses an unblocked socket
  fcntl (sock, F_SETFL, O_NONBLOCK);  
  unlink (sockpath);
  return (sock);
}
