#include <stdio.h>
#include <string.h>
#ifndef __GNUC__
#include <starlet.h>
#include <libdef.h>
#include <libdtdef.h>
#else
#define LIB$K_DELTA_SECONDS 25
#endif
#include <descrip.h>
#include <iodef.h>
#include <stdlib.h>
#include <lib$routines.h>
#include "global.h"
#include "types.h"
#include "qio.h"
#ifdef DEBUG_MALLOC
#include "debug.c"
#endif
#ifdef UCX
#  include "ucx.h"
#endif
#include "debug.h"
#ifdef USE_DEFINES_H
#include "defines.h"
#endif

long myaddress = 0;

/* Internal qio errors */
#define QIO_UNKNET  1
#define QIO_MALLOC  2
#define QIO_UNKHOST 3
#define QIO_TIMEOUT 4

static int last_chan_active = 0;

/* ------------------------------------------------------------------------ */
static int qio_status, qio_iosb, qio_internal;
/* ------------------------------------------------------------------------ */
void set_sin_tcp(SIN *mysin, char *host, unsigned short port)
{                                
  memset(mysin, 0, sizeof(SIN));
  mysin->sin_family = AF_INET;
  mysin->sin_port = port;
  memcpy(&mysin->sin_address, host, sizeof(mysin->sin_address));
}

/* ------------------------------------------------------------------------ */
unsigned short i_qio_htons(unsigned short num)
{
  int y;
  y = (int)num & 0x00FF;
  return (((int)num>>8) + (y * 256));
}

unsigned i_qio_htonl(num)
  unsigned num;
{
  unsigned factor = 16777216, loop;
  unsigned y = 0;

  for (loop=0;loop<sizeof(num);loop++)
  {
    y += (num & 0xFF) * factor;
    num = num >> 8;
    factor = factor / 256;
  }
  return y;
}

/* ------------------------------------------------------------------------ */
static int readef (unsigned eflag)        /* Returns TRUE if eflag is set */
{
  unsigned flag, mask, status, loop;

  status = sys$readef(eflag, &mask);
  if (!ODD(status)) return 0;
  eflag = (unsigned)(eflag%32);
  flag = 1;
  for (loop=0; loop < eflag; loop++) flag = flag*2;
  return (!((flag & mask) == 0));
}

/* ------------------------------------------------------------------------ */
int i_qio_gethostname(char *hostname)
{
#ifdef MULTINET     
  qio_status = qio_iosb = qio_internal = 0;
  hostname[0] = 0;
  if (!get_logical("UCX$INET_HOST", hostname, TRUE))
    if (!get_logical("ARPANET_HOST_NAME", hostname, TRUE))
      if (!get_logical("TCPIP_HOSTNAME", hostname, TRUE))
      {
        qio_internal = QIO_UNKHOST;
        return -1;
      }
  return 1;
#else
#ifdef UCX
  char host1[80], domain[80];
  qio_status = qio_iosb = qio_internal = 0;
  hostname[0] = 0;
  if (get_logical("UCX$INET_HOST", host1, TRUE) &&
      get_logical("UCX$INET_DOMAIN", domain, TRUE))
  {
    if (strstr(host1, domain) || strstr(domain, host1))
      (void)strcpy(hostname, host1);
    else
      (void)sprintf(hostname, "%s.%s\0", host1, domain);
    return 1;
  }
  qio_internal = QIO_UNKHOST;
  return -1;
#endif
  qio_status = qio_iosb = 0;
  qio_internal = QIO_UNKNET;
  return -1;
#endif
}

/* ------------------------------------------------------------------------ */
static void qio_deassign(chan)
  short chan;
{
  (void)sys$dassgn(chan);
}

short i_qio_shutdown(short chan)
{
  unsigned status = 1;
  struct iosb_type iosb;
  qio_status = qio_internal = qio_iosb = 0;
#ifdef MULTINET
  status = sys$qiow(0, chan, IO$_SHUTDOWN, &iosb, 0, 0, 2, 0, 0, 0, 0, 0);
#else
#ifdef UCX
  status = sys$qiow(0, chan, IO$_DEACCESS|IO$M_SHUTDOWN, &iosb, 0, 0,
                    0, 0, 0, UCX$C_DSC_ALL, 0, 0);
#else
  qio_internal = QIO_UNKNET;
#endif
#endif
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
  if (qio_status || qio_iosb || qio_internal) return -1;
  status = sys$dassgn(chan);
  qio_status = (ODD(status))?0:status;
  if (qio_status) return -1;
  return 0;
}

static short qio_assign(void)
{
  unsigned status;
  char dev[80];
  struct dsc$descriptor_s device_d;
  short chan = 0;

  qio_status = qio_iosb = qio_internal = 0;
#ifdef MULTINET
  (void)strcpy(dev, "_INET0:");
#else
#ifdef UCX
  get_logical("UCX$DEVICE", dev, TRUE);
#else
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  device_d.dsc$b_dtype = DSC$K_DTYPE_D;
  device_d.dsc$b_class = DSC$K_CLASS_S;

  device_d.dsc$w_length = strlen(dev);
  device_d.dsc$a_pointer = dev;
  status = sys$assign(&device_d, &chan, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  if (qio_status || qio_internal) return -1;
  return chan;
}

/* ------------------------------------------------------------------------ */
/* Link an incoming user on chan to the newchan that we have created */

struct address_struct {
  unsigned long Length;
  struct sockaddr Address;
};

static short my_accept(short chan, SIN *mysin, int len, ROUTINE routine)
{
  struct iosb_type iosb;
  unsigned status, ctx[3];
  short nchan;
  struct address_struct address;

  nchan = chan;
#ifdef MULTINET
  if (routine) {
    status = sys$qio(0, chan, IO$_ACCEPTWAIT, &iosb, routine, chan, 0,
                      0, 0, 0, 0, 0);
    qio_status = (ODD(status))?0:status;
    if (qio_status) return -1;
    return chan;
  } else {
    if ((nchan = qio_assign()) == -1) return -1;
    status = sys$qiow(0, nchan, IO$_ACCEPT, &iosb, 0, 0, &address,
                      sizeof(address), chan, 0, 0, 0);
    qio_status = (ODD(status))?0:status;
    qio_iosb = (ODD(iosb.status))?0:iosb.status;
    qio_internal = 0;
    if (qio_status || qio_iosb) return -1;
    return nchan;
  }
#else
#ifdef UCX
  ctx[0] = len;
  ctx[1] = (int)mysin;
  ctx[2] = (int)&len;
  if ((nchan = qio_assign()) == -1) return -1; /**/
  if (routine) {
    status = sys$qio(0, chan, IO$_ACCESS|IO$M_ACCEPT, &iosb, routine, nchan,
                     0 /*P1*/, 0, ctx, &nchan, 0, 0);
    qio_iosb = qio_internal = 0;
    qio_status = (ODD(status))?0:status;
    if (qio_status) return -1;
    return nchan;
  }
  else
  {
    status = sys$qiow(0, chan, IO$_ACCESS|IO$M_ACCEPT, &iosb, 0, 0, 0 /*P1*/,
                      0, ctx, &nchan, 0, 0);
    qio_status = (ODD(status))?0:status;
    qio_iosb = (ODD(iosb.status))?0:iosb.status;
    qio_internal = 0;
    if (qio_status || qio_iosb) return -1;
    return nchan;
  }
#else
  qio_status = qio_iosb = 0;
  qio_internal = QIO_UNKNET;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  return -1;
}

short i_qio_accept(short chan, SIN *mysin, int len)
{
  return my_accept(chan, mysin, len, 0);
}

short i_qio_accept_wait(short chan, SIN *mysin, int len, ROUTINE routine)
{
  return my_accept(chan, mysin, len, routine);
}

/* ------------------------------------------------------------------------ */
short i_qio_send(short chan, char *sbuf, unsigned size)
{
  unsigned status;
  struct iosb_type iosb;

#ifdef MULTINET
  status = sys$qiow(0 ,chan, IO$_SEND, &iosb, 0, 0, sbuf, size, 0, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
  qio_internal = 0;
#else
#ifdef UCX
  status = sys$qiow(0 ,chan, IO$_WRITEVBLK, &iosb, 0, 0, sbuf, size, 0, 0,
                    0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
  qio_internal = 0;
#else
  qio_status = qio_iosb = 0;
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  if (qio_status || qio_iosb || qio_internal) return -1;
  return iosb.length;
}

/* ------------------------------------------------------------------------ */
struct Schar {
  short protocol;
  char protype, domain;
};

short i_qio_socket(int domain, int type, int protocol)
{
  short chan;
  unsigned status = 0;
  struct iosb_type iosb;
  struct Schar schar;
  int vec[2];
  SIN sin;

  qio_status = qio_iosb = qio_internal = 0;
  if ((chan = qio_assign()) == -1) return -1;

#ifdef MULTINET
  status = sys$qiow(0, chan, IO$_SOCKET, &iosb, 0, 0, domain,
                    type, 0, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
#ifdef UCX
  schar.protocol = UCX$C_TCP;
  schar.protype = UCX$C_STREAM;
  schar.domain = 0;
  vec[0] = sizeof(SIN);
  vec[1] = (int)&sin;
  memset(&sin, 0, sizeof(SIN));
  sin.sin_family = UCX$C_AF_INET;
  sin.sin_address = INET$C_INADDR_ANY;
  status = sys$qiow(0, chan, IO$_SETMODE, &iosb, 0, 0, &schar, /*P1*/
                    0, vec, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  if (qio_status || qio_iosb || qio_internal) return -1;
  return chan;
}

/* ------------------------------------------------------------------------ */
#define MAXQUEUE 5    /* Maximum queue size for the system */

short i_qio_listen(short chan)
{
  unsigned status;
  struct iosb_type iosb;

  qio_status = qio_iosb = qio_internal = 0;
#ifdef MULTINET
  status = sys$qiow(0, chan, IO$_LISTEN, &iosb, 0, 0, MAXQUEUE,
                    0, 0, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
#else
#ifdef UCX
  /* Listening is handled in the bind for UCX */
#else
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  if (qio_status || qio_iosb || qio_internal) return -1;
  return 1;
}

/* ------------------------------------------------------------------------ */
static long unsigned cefn;        /* The connect Event flag. It gets set */
                                  /* When we succeed in making a connect */
static short gchan = 0;           /* The chan assigned to the network dev. */
#define CONNECT_ID 567890

static void connect_ast(unsigned argh)
{
  unsigned status=1;

  if (gchan) {
    status = sys$cancel(gchan);
    gchan = 0;
  } else 
    status = sys$cantim(CONNECT_ID, 0);
}
void i_qio_connect_cancel(void)
{
  if (last_chan_active) sys$cancel(last_chan_active);
/*  connect_ast(0); */
}

struct UCX_SIN {
  unsigned size;
  char *mysin;
};

short i_qio_connect(short chan, SIN *mysin, unsigned len)
{
  struct iosb_type iosb;
  unsigned tquad[2], status;
  struct UCX_SIN ucx_sin;
  long int seconds = 10;
  long unsigned flags = LIB$K_DELTA_SECONDS;

  gchan = chan;

  qio_status = qio_iosb = qio_internal = 0;
  status = lib$get_ef(&cefn);
  qio_status = (ODD(status))?0:status;
  if (qio_status) return -1;

  status = lib$cvt_to_internal_time(&flags, &seconds, tquad);
  qio_status = (ODD(status))?0:status;
  if (qio_status) return -1;

  status = sys$setimr(0, tquad, connect_ast, CONNECT_ID, 0);
  qio_status = (ODD(status))?0:status;
  if (qio_status) return -1;

  last_chan_active = chan;
#ifdef MULTINET
  status = sys$qiow(cefn, chan, IO$_CONNECT, &iosb, 0, 0, mysin,
                    len,0,0,0,0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
#ifdef UCX
  ucx_sin.size = len;
  ucx_sin.mysin = (char *)mysin;
  status = sys$qiow(cefn, chan, IO$_ACCESS, &iosb, 0, 0, 0,
                    1, &ucx_sin, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  last_chan_active = 0;
  lib$free_ef(&cefn);
  if (gchan == 0) qio_internal = QIO_TIMEOUT;
  gchan = 0; /* need to set this to 0 so that connect_ast doesn't kill it */
  connect_ast(CONNECT_ID);
  if (qio_status || qio_iosb || qio_internal) return -1;
  return 1; /* Was iosb.status */
}

/* ------------------------------------------------------------------------ */
short i_qio_bind(SIN *mysin, short *chan)
{
  unsigned status;
  struct iosb_type iosb;
  struct Schar schar;
  int vec[2];

  qio_status = -1;
#ifdef MULTINET
  status = sys$qiow(0, *chan, IO$_BIND, &iosb, 0, 0, mysin,
                    sizeof(*mysin), 0, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
  qio_internal = 0;
#else
#ifdef UCX
  if ((*chan = qio_assign()) == -1) return -1;
  schar.protocol = UCX$C_TCP;
  schar.protype = UCX$C_STREAM;
  schar.domain = 0;
  vec[0] = sizeof(SIN);
  vec[1] = (int)mysin;
  mysin->sin_family = UCX$C_AF_INET;
/*  sin.sin_address = INET$C_INADDR_ANY;   /**/
  status = sys$qiow(0, *chan, IO$_SETMODE, &iosb, 0, 0, &schar, /*P1*/
                    0, vec, 3, 0, 0); /* 3 stands for listen */
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
  qio_internal = 0;
#else
  qio_status = qio_iosb = 0;
  qio_internal = QIO_UNKNET;
#endif /* UCX */
#endif /* MULTINET */
  if (qio_status || qio_iosb || qio_internal) return -1;
  return 1;  /* was iosb.status */
}

/* ------------------------------------------------------------------------ */
short i_qio_receive(short chan, char *buffer, unsigned maxlen,
                   struct iosb_type *iosb, unsigned efn, ROUTINE routine)
{
  int status;
  short func = IO$_RECEIVE;
  qio_status = qio_iosb = qio_internal = 0;
#ifdef MULTINET
#else
#ifdef UCX
#else
  qio_internal = QIO_UNKNET;
  return -1;
#endif /* UCX */
#endif /* MULTINET */

  if (!routine)
  {
    last_chan_active = chan;
    status = sys$qiow(0, chan, func, iosb, 0 ,0, buffer, maxlen,
                      0, 0, 0, 0);
    qio_status = (ODD(status))?0:status;
    qio_iosb = (ODD(iosb->status))?0:iosb->status;
    last_chan_active = 0;
  }
  else {
    status = sys$qio(efn, chan, func, iosb, routine, chan, buffer, maxlen,
                     0,0,0,0);
    qio_status = (ODD(status))?0:status;
    if (qio_status) return -1;
    return 1;
  }
  if (qio_status || qio_iosb || qio_internal) return -1;
  return iosb->length;
}

/* ------------------------------------------------------------------------ */
static int i_qio_get_port(short chan, SIN *mysin)
{
  int sinlen = sizeof(SIN), status, vec[3], len;
  struct iosb_type iosb;
  qio_status = qio_iosb = qio_internal = 0;
#ifdef MULTINET
  status = sys$qiow(0, chan, IO$_GETSOCKNAME, &iosb, 0, 0, mysin, &sinlen,
                    0, 0, 0, 0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
#ifdef UCX
  len = vec[0] = sinlen;
  vec[1] = (int)mysin;
  vec[2] = (int)&len;
  status = sys$qiow(0, chan, IO$_SENSEMODE, &iosb, 0, 0, 0, 0, vec, 0,0,0);
  qio_status = (ODD(status))?0:status;
  qio_iosb = (ODD(iosb.status))?0:iosb.status;
#else
  qio_internal = QIO_UNKNET;
#endif
#endif
  if (qio_status || qio_iosb || qio_internal) return -1;
  return 1;
}

/* ----------------------------------------------------------------------- */
short i_qio_accept_after_wait(short oldchan, short newchan, SIN *mysin, int size)
{
#ifdef UCX
  if (oldchan != newchan) qio_deassign(oldchan);
  return newchan;
#else
  newchan = i_qio_accept(oldchan, mysin, size);
  return newchan;
#endif
  qio_status = qio_iosb = 0;
  qio_internal = QIO_UNKNET;
  return -1;
}

/* ----------------------------------------------------------------------- */
SIN *i_qio_bind_and_listen(short *nchan, ROUTINE rout)
{
  short chan;
  int status;
  SIN *sin;
  qio_status = qio_iosb = qio_internal = 0;
  if (!(sin = (SIN *)mymalloc(sizeof(SIN))))
  {
    qio_internal = QIO_MALLOC;
    return NULL;
  }
  memset(sin, 0, sizeof(SIN));
  sin->sin_family = AF_INET;
  sin->sin_address = myaddress;
  sin->sin_address = 0;
  sin->sin_port = 0;
#ifdef UCX
  status = i_qio_bind(sin, &chan);
#else
  chan = status = i_qio_socket(AF_INET, SOCK_STREAM, PF_INET);
  if (status != -1) status = i_qio_bind(sin, &chan);
#endif
  if (status != -1) status = i_qio_get_port(chan, sin);
  if (status != -1) status = i_qio_listen(chan);
  if (status != -1) status = my_accept(chan, sin, sizeof(SIN), rout);
  if (status == -1) {
    myfree(sin);
    return NULL;
  }
  *nchan = status;
  return sin;
}

/* ------------------------------------------------------------------------ */
short i_qio_socket_and_connect(SIN *mysin, int sz)
{
  short chan;
  qio_status = qio_iosb = qio_internal = 0;
  chan = i_qio_socket(AF_INET, SOCK_STREAM, PF_INET);
  if (chan == -1) return chan;
  if (i_qio_connect(chan, mysin, sz) == -1) return -1;
  return chan;
}

/* ------------------------------------------------------------------------ */
static int my_strerror(num, string)
  unsigned num;
  char *string;
{
  struct dsc$descriptor_s ms_d;
  unsigned status;
  unsigned short len = 0;

  ms_d.dsc$w_length = 255;
  ms_d.dsc$b_dtype = DSC$K_DTYPE_T;
  ms_d.dsc$b_class = DSC$K_CLASS_S;
  ms_d.dsc$a_pointer = string;
  status = sys$getmsg(num, &len, &ms_d, 0, 0);
  if (!ODD(status)) return status;
  string[(int)len] = 0;
  return 1;
}

/* ------------------------------------------------------------------------ */
void i_get_socket_error(char *str)
{
  int ret, code;
  char err[256];
  if (qio_internal)
  {
    if (qio_internal == QIO_MALLOC)
      strcpy(str, "could not malloc space");
    else if (qio_internal == QIO_UNKNET)
      strcpy(str, "unknown networking software");
    else if (qio_internal == QIO_UNKHOST)
      strcpy(str, "could not lookup your hostname");
    else if (qio_internal == QIO_TIMEOUT)
      strcpy(str, "operation cancelled due to timeout");
    else
      sprintf(str, "unknown internal error - %d", qio_internal);
    return;
  }
  *str = 0;
  if (qio_status) {
    ret = my_strerror(qio_status, str);
    if (ret!=1) sprintf(str, "status error: %d", qio_status);
  } else if (qio_iosb) {
    char *lame = NULL;
    ret = 1;
    code = (qio_iosb - 0x8000) / 8;
#ifndef CROSS
    lame = strerror(code);
#endif
    strcpy(str, lame?lame:"empty string");
  } else strcpy(str, "Error 0");
}

/* ------------------------------------------------------------------------ */
#ifdef USE_RELOC_QIO
int setup_qio_hooks(char *str)
{
  var_get_socket_error = _get_socket_error;
  var_qio_accept = _qio_accept;
  var_qio_accept_after_wait = _qio_accept_after_wait;
  var_qio_bind_and_listen = _qio_bind_and_listen;
  var_qio_connect = _qio_connect;
  var_qio_htons = _qio_htons;
  var_qio_htonl = _qio_htonl;
  var_qio_receive = _qio_receive;
  var_qio_shutdown = _qio_shutdown;
  var_qio_socket = _qio_socket;
  var_qio_socket_and_connect = _qio_socket_and_connect;
  var_qio_send = _qio_send;
  var_qio_gethostname = _qio_gethostname;
  return 1;
}
#endif
