#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 "global.h"
#ifdef UCX
#include "ucx.h"
#endif /* UCX */
#include "qio.h"

extern int lib$cvt_to_internal_time(), lib$free_ef();
extern int lib$get_ef();

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

unsigned long _qio_htonl(num)
  unsigned long 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));
}

/* ------------------------------------------------------------------------ */

static char message_code[100];
static unsigned qio_status;

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

short _qio_shutdown(short chan)
{
  unsigned status;
  struct iosb_type iosb;
#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
  (void)strcpy(message_code, "unknown transport in shutdown");
  qio_status = -1;
  return -1;
#endif
#endif
  (void)strcpy(message_code, "error in shutdown");
  qio_status = status;
  if (ODD(qio_status)) qio_status = (int)iosb.status;
  if (ODD(qio_status)) 1;
  return -1;
}

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

#ifdef MULTINET
  (void)strcpy(dev, "_INET0:");
#else
#ifdef UCX
  get_logical("UCX$DEVICE", dev, TRUE);
#else
  (void)strcpy(message_code, "Unknown transport in assign");
  qio_status = -1;
  return -1;
#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);
  status = sys$assign(&device_d, &chan, 0, 0, 0);
  if (!ODD(status)) {
    (void)strcpy(message_code, "assign");
    qio_status = status;
    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 *sin, 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 = status;
    (void)strcpy(message_code, "status in accept");
    if (ODD(status)) return chan;
    return -1;
  } 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);
  }
#else
#ifdef UCX
  ctx[0] = len;
  ctx[1] = sin;
  ctx[2] = &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_status = status;
    (void)strcpy(message_code, "status in accept (UCX) routine");
    if (ODD(status)) return nchan;
    return -1;
  }
  else
    status = sys$qiow(0, chan, IO$_ACCESS|IO$M_ACCEPT, &iosb, 0, 0, 0 /*P1*/,
                      0, ctx, &nchan, 0, 0);
#else
  (void)strcpy(message_code, "Unknown transport in accept");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in accept");
    return -1;
  } else if (ODD(iosb.status)) return nchan;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in accept");
  return -1;
}

short _qio_accept(short chan, SIN *sin, int len)
{
  return my_accept(chan, sin, len, 0);
}

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

/* ------------------------------------------------------------------------ */

short _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);
#else
#ifdef UCX
  status = sys$qiow(0 ,chan, IO$_WRITEVBLK, &iosb, 0, 0, sbuf, size, 0, 0,
                    0, 0);
#else
  (void)strcpy(message_code, "Unknown transport in send");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in qio_send");
    return -1;
  } else if (ODD(iosb.status)) return iosb.length;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in qio_send");
  return -1;
}

/* ------------------------------------------------------------------------ */

struct Schar {
  short protocol;
  char protype, domain;
};

short _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 = -1;
  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);
#else
#ifdef UCX
  schar.protocol = UCX$C_TCP;
  schar.protype = UCX$C_STREAM;
  schar.domain = 0;
  vec[0] = sizeof(SIN);
  vec[1] = &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);
#else
  (void)strcpy(message_code, "Unknown transport in socket");
  qio_status = -1;
  return -1;
#endif /*  */
#endif /*  */
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in socket");
    return -1;
  } else if (ODD(iosb.status)) return chan;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in socket");
  return -1;
}

/* ------------------------------------------------------------------------ */

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

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

#ifdef MULTINET
  status = sys$qiow(0, chan, IO$_LISTEN, &iosb, 0, 0, MAXQUEUE,
                    0, 0, 0, 0, 0);
#else
#ifdef UCX
  status = 1;        /* Listening is handled in the bind for UCX */
  iosb.status = 1;
#else
  (void)strcpy(message_code, "Unknown transport in listen");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in listen");
    return -1;
  } else if (ODD(iosb.status)) return iosb.status;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in listen");
  return -1;
}

/* ------------------------------------------------------------------------ */

unsigned cefn;                       /* The connect Event flag. It gets set */
                                     /* When we succeed in making a connect */

short gchan = 0;                     /* The chan assigned to the network dev. */
#define CONNECT_ID 567890

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

  if (!readef(cefn))
  {
    status = sys$cantim(CONNECT_ID, 0);
    if (gchan) status = sys$cancel(gchan);
    if (!ODD(status)) {
      qio_status = status;
      strcpy(message_code, "Error deallocating channel");
    }
  }
  else gchan = 0;
}

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

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

  gchan = chan;

  status = lib$get_ef(&cefn);
  if (!ODD(status)) {
    (void)strcpy(message_code, "lib$get_ef in connect");
    qio_status = status;
    return -1;
  }

  status = lib$cvt_to_internal_time(&flags, &seconds,
                                    tquad);
  if (!ODD(status)) {
    (void)strcpy(message_code, "lib$cvt_to_internal_time in connect");
    qio_status = status;
    return -1;
  }

  status = sys$setimr(0, tquad, &connect_ast, CONNECT_ID, 0);
  if (!ODD(status)) {
    (void)strcpy(message_code, "set$setimr in connect");
    qio_status = status;
    return -1;
  }

  status = -1;
#ifdef MULTINET
  status = sys$qiow(cefn, chan, IO$_CONNECT, &iosb, connect_ast, 0, sin,
                    len,0,0,0,0);
#else
#ifdef UCX
  ucx_sin.size = len;
  ucx_sin.sin = sin;
  status = sys$qiow(cefn, chan, IO$_ACCESS, &iosb, connect_ast, 0, 0,
                    1, &ucx_sin, 0, 0, 0);
#else
  (void)strcpy(message_code, "Unknown transport in connect");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  (void)lib$free_ef(&cefn);
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in connect");
    return -1;
  } else if (ODD(iosb.status)) return iosb.status;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in connect");
  return -1;
}

/* ------------------------------------------------------------------------ */

short _qio_bind(SIN *sin, 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, sin,
                    sizeof(*sin), 0, 0, 0, 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] = 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, 3, 0, 0); /* 3 stands for listen */
#else
  (void)strcpy(message_code, "Unknown transport in bind");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "status in bind");
    return -1;
  } else if (ODD(iosb.status)) return iosb.status;
  qio_status = iosb.status;
  (void)strcpy(message_code, "iosb.status in bind");
  return -1;
}

/* ------------------------------------------------------------------------ */
short _qio_receive(short chan, char *buffer, unsigned maxlen,
                   struct iosb_type *iosb, unsigned efn, unsigned routine)
{
  int status;
#ifdef MULTINET
  short func = IO$_RECEIVE;
#else
#ifdef UCX
  short func = IO$_RECEIVE;
#else
  (void)strcpy(message_code, "Unknown transport in receive");
  qio_status = -1;
  return -1;
#endif /* UCX */
#endif /* MULTINET */

  if (!routine)
    status = sys$qiow(0, chan, func, iosb, 0 ,0, buffer, maxlen,
                      0, 0, 0, 0);
  else {
    status = sys$qio(efn, chan, func, iosb, routine, 0, buffer, maxlen,
                     0,0,0,0);
    if (!ODD(status)) {
      qio_status = status;
      (void)strcpy(message_code, "ast, status");
      return -1;
    }
    return 1;
  }
  if (!ODD(status)) {
    qio_status = status;
    (void)strcpy(message_code, "no routine,status in receive");
    return -1;
  } else if (ODD(iosb->status)) return iosb->length;
  qio_status = iosb->status;
  (void)strcpy(message_code, "no routine,iosb.status in receive");
  return -1;
}

/* ------------------------------------------------------------------------ */
static void _qio_get_port(short chan, SIN *sin)
{
  int sinlen = sizeof(SIN), status;
  status = sys$qiow(0, chan, IO$_GETSOCKNAME, 0, 0, 0, sin, &sinlen, 0,0,0,0);
}

/* ----------------------------------------------------------------------- */
short _qio_accept_after_wait(short oldchan, short newchan, SIN *sin, int size)
{
#ifdef UCX
  qio_deassign(oldchan);
  return newchan;
#else
  newchan = _qio_accept(oldchan, sin, size);
  qio_deassign(oldchan);
  return newchan;
#endif
}

/* ----------------------------------------------------------------------- */
SIN *_qio_bind_and_listen(short *nchan, ROUTINE rout)
{
  short chan;
  int status;
  SIN *sin;
  if (!(sin = (SIN *)malloc(sizeof(SIN)))) 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 = _qio_bind(sin, &chan);
#else
  chan = status = _qio_socket(AF_INET, SOCK_STREAM, PF_INET);
  if (status != -1) status = _qio_bind(sin, &chan);
#endif
  _qio_get_port(chan, sin);
  if (status != -1) status = _qio_listen(chan);
  if (status != -1) status = my_accept(chan, sin, sizeof(SIN), rout);
  if (status == -1) {
    free(sin);
    return NULL;
  }
  *nchan = status;
  return sin;
}

short _qio_socket_and_connect(SIN *sin, int sz)
{
  short chan;
  chan = _qio_socket(AF_INET, SOCK_STREAM, PF_INET);
  if (chan == -1) return chan;
  if (_qio_connect(chan, sin, sz) == -1) return -1;
  return chan;
}

void _get_socket_error(char *str)
{
  sprintf(str, "%s %d", message_code, qio_status);
}

int setup_qio_hooks(char *str)
{
  get_socket_error = _get_socket_error;
  qio_accept_after_wait = _qio_accept_after_wait;
  qio_bind_and_listen = _qio_bind_and_listen;
  qio_connect = _qio_connect;
  qio_htons = _qio_htons;
  qio_htonl = _qio_htonl;
  qio_receive = _qio_receive;
  qio_shutdown = _qio_shutdown;
  qio_socket = _qio_socket;
  qio_socket_and_connect = _qio_socket_and_connect;
  qio_send = _qio_send;
  return 1;
}
