#include <string.h>
#include <support.h>
#include <osbind.h>
#include <mintbind.h>
#include <netinet/in.h>
#include "kerbind.h"
#include "gs_descr.h"


/* STIK global configuration structure.  STinG's <transprt.h> doesn't
 * define this, so we define it here. */

typedef struct config {
    uint32  client_ip;          /* IP address of client (local) machine     */
    uint32  provider;           /* IP address of provider, or 0L            */
    uint16  ttl;                /* Default TTL for normal packets           */
    uint16  ping_ttl;           /* Default TTL for 'ping'ing                */
    uint16  mtu;                /* Default MTU (Maximum Transmission Unit)  */
    uint16  mss;                /* Default MSS (Maximum Segment Size)       */
    uint16  df_bufsize;         /* Size of defragmentation buffer to use    */
    uint16  rcv_window;         /* TCP receive window                       */
    uint16  def_rtt;            /* Initial RTT time in ms                   */
    int16   time_wait_time;     /* How long to wait in 'TIME_WAIT' state    */
    int16   unreach_resp;       /* Response to unreachable local ports      */
    int32   cn_time;            /* Time connection was made                 */
    int16   cd_valid;           /* Is Modem CD a valid signal ??            */
    int16   line_protocol;      /* What type of connection is this          */
    void    (*old_vec)(void);   /* Old vector address                       */
    struct  slip *slp;          /* Slip structure for happiness             */
    char    *cv[101];           /* Space for extra config variables         */
    int16   reports;            /* Problem reports printed to screen ??     */
    int16   max_num_ports;      /* Maximum number of ports supported        */
    uint32  received_data;      /* Counter for data being received          */
    uint32  sent_data;          /* Counter for data being sent              */
} CONFIG;


static char *err_list[E_LASTERROR+1] = {
  "No error occured",				/* E_NORMAL */
  "Output buffer is full",			/* E_OBUFFULL */
  "No data available",				/* E_NODATA */
  "EOF from remote",				/* E_EOF */
  "Reset received from remote",			/* E_RRESET */
  "Unacceptable packet received, reset",	/* E_UA */
  "Something failed due to lack of memory",	/* E_NOMEM */
  "Connection refused by remote",		/* E_REFUSE */
  "A SYN was received in the window",		/* E_BADSYN */
  "Bad connection handle used.",		/* E_BADHANDLE */
  "The connection is in LISTEN state",		/* E_LISTEN */
  "No free CCB's available",			/* E_NOCCB */
  "No connection matches this packet (TCP)",	/* E_NOCONNECTION */
  "Failure to connect to remote port (TCP)",	/* E_CONNECTFAIL */
  "Invalid TCP_close() requested",		/* E_BADCLOSE */
  "A user function timed out",			/* E_USERTIMEOUT */
  "A connection timed out",			/* E_CNTIMEOUT */
  "Can't resolve the hostname",			/* E_CANTRESOLVE */
  "Domain name or dotted dec. bad format",	/* E_BADDNAME */
  "The modem disconnected",			/* E_LOSTCARRIER */
  "Hostname does not exist",			/* E_NOHOSTNAME */
  "Resolver Work limit reached",		/* E_DNSWORKLIMIT */
  "No nameservers could be found for query",	/* E_NONAMESERVER */
  "Bad format of DS query",			/* E_DNSBADFORMAT */
  "Destination unreachable",			/* E_UNREACHABLE */
  "No address records exist for host",		/* E_DNSNOADDR */
  "Routine unavailable",			/* E_NOROUTINE */
  "Locked by another application",		/* E_LOCKED */
  "Error during fragmentation",			/* E_FRAGMENT */
  "Time To Live of an IP packet exceeded",	/* E_TTLEXCEED */
  "Problem with a parameter",			/* E_PARAMETER */
  "Input buffer is too small for data"		/* E_BIGBUF */
};
char err_unknown[] = "Unrecognized error";

static int flags[64] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};


static long do_gs_ioctl(union GS_cmd *cmd)
{
  long fd;
  long ret;

  fd = Fopen(gsdev_name, 0);
  if (fd < 0)
    return 0;
  ret = Fcntl((int)fd, (long)cmd, GLUESTIK_CMD);
  Fclose(fd);
  return ret;
}

static void *do_KRmalloc(int32 size)
{
  union GS_cmd cmd;

  cmd.KRmalloc_cmd.cmd = KRMALLOC_CMD;
  cmd.KRmalloc_cmd.size = size;
  return (void *)do_gs_ioctl(&cmd);
}

static void do_KRfree(void *mem)
{
  union GS_cmd cmd;

  cmd.KRfree_cmd.cmd = KRFREE_CMD;
  cmd.KRfree_cmd.mem = mem;
  do_gs_ioctl(&cmd);
}

static int32 do_KRgetfree(int16 flag)
{
  union GS_cmd cmd;

  cmd.KRgetfree_cmd.cmd = KRGETFREE_CMD;
  cmd.KRgetfree_cmd.flag = flag;
  return do_gs_ioctl(&cmd);
}

static void *do_KRrealloc(void *mem, int32 newsize)
{
  union GS_cmd cmd;

  cmd.KRrealloc_cmd.cmd = KRREALLOC_CMD;
  cmd.KRrealloc_cmd.mem = mem;
  cmd.KRrealloc_cmd.newsize = newsize;
  return (char *)do_gs_ioctl(&cmd);
}

char *do_get_err_text(int16 code)
{
  if (code < 0)
    code = -code;
  if (code > 2000)
    return err_unknown;
  if (code > 1000) {
    /* Encoded GEMDOS errors */
    return strerror(code - 1000);
  }
  if (code > E_LASTERROR)
    return err_unknown;
  return err_list[code];
}

static char *do_getvstr(char *var)
{
  union GS_cmd cmd;

  cmd.getvstr_cmd.cmd = GETVSTR_CMD;
  cmd.getvstr_cmd.var = var;
  return (char *)do_gs_ioctl(&cmd);
}

/* Incompatibility:  Does nothing.  We can't really support this well,
   since MiNTnet transparently supports multiple modems, as well as
   non-modem methods of connections, such as local networks */
static int16 do_carrier_detect()
{
  return 0;
}

static int16 do_TCP_open(uint32 rhost, int16 rport, int16 tos, uint16 obsize)
{
  union GS_cmd cmd;
  long fd;
  long ret;

  cmd.TCP_open_cmd.cmd = TCP_OPEN_CMD;
  if (rhost == 0) {
    cmd.TCP_open_cmd.rhost = 0;
    cmd.TCP_open_cmd.rport = 0;
    cmd.TCP_open_cmd.lhost = INADDR_ANY;
    cmd.TCP_open_cmd.lport = rport;
  } else if (rport == 0) {
    CIB *cib = (CIB *)rhost;
    cmd.TCP_open_cmd.rhost = cib->rhost;
    cmd.TCP_open_cmd.rport = cib->rport;
    cmd.TCP_open_cmd.lhost = cib->lhost;
    cmd.TCP_open_cmd.lport = cib->lport;
  } else {
    cmd.TCP_open_cmd.rhost = rhost;
    cmd.TCP_open_cmd.rport = rport;
    cmd.TCP_open_cmd.lhost = INADDR_ANY;
    cmd.TCP_open_cmd.lport = 0;
  }

  fd = Fopen(gsdev_name, 2);
  if (fd < 0)
    return fd;
  /* The TCP_OPEN_CMD command transmogrifies this descriptor into an actual
   * connection. */
  ret = Fcntl(fd, (long)&cmd, GLUESTIK_CMD);
  if (ret < 0)
    return ret;
  return fd;
}

static int16 do_TCP_close(int16 fd, int16 timeout)
{
  Fclose(fd);
  return 0;
}

static int16 do_TCP_send(int16 fd, void *buf, int16 len)
{
  return Fwrite(fd, (long)len, (void *)buf);
}

static int16 do_TCP_wait_state(int16 fd, int16 state, int16 timeout)
{
  union GS_cmd cmd;

  cmd.TCP_wait_state_cmd.cmd = TCP_WAIT_CMD;
  cmd.TCP_wait_state_cmd.timeout = timeout;
  return Fcntl(fd, &cmd, GLUESTIK_CMD);
}

/* Incompatibility:  Does nothing.  MiNTnet handles this internally. */
static int16 do_TCP_ack_wait(int16 fd, int16 timeout)
{
  return E_NORMAL;
}

static int16 do_UDP_open(uint32 rhost, int16 rport)
{
  union GS_cmd cmd;
  int fd, ret;

  cmd.UDP_open_cmd.cmd = UDP_OPEN_CMD;
  cmd.UDP_open_cmd.rhost = rhost;
  cmd.UDP_open_cmd.rport = rport;

  fd = Fopen(gsdev_name, 2);
  if (fd < 0)
    return fd;
  /* The UDP_OPEN_CMD command transmogrifies this descriptor into an actual
   * connection. */
  ret = Fcntl(fd, (long)&cmd, GLUESTIK_CMD);
  if (ret < 0)
    return ret;
  return fd;
}

static int16 do_UDP_close(int16 fd)
{
  Fclose(fd);
  return 0;
}

static int16 do_UDP_send(int16 fd, void *buf, int16 len)
{
  return Fwrite(fd, (long)len, (void *)buf);
}

/* Incompatibility:  Does nothing.  MiNTnet handles its own "kicking" */
static int16 do_CNkick(int16 fd)
{
  return E_NORMAL;
}

static int16 do_CNbyte_count(int16 fd)
{
  return Fcntl(fd, 0, CNBYTECOUNT_CMD);
}

static int16 do_CNget_char(int16 fd)
{
  char c;
  long ret;

  ret = Fread(fd, 1L, &c);
  if (ret < 0)
    return ret;
  if (ret == 0)
    return E_NODATA;
  return c;
}

static NDB *do_CNget_NDB(int16 fd)
{
  return (NDB *)Fcntl(fd, 0, CNGETNDB_CMD);
}

static int16 do_CNget_block(int16 fd, void *buf, int16 len)
{
  return Fread(fd, (long)len, (void *)buf);
}

static void do_housekeep(void)
{
  /* does nothing */
}

static int16 do_resolve(char *dn, char **rdn, uint32 *alist, int16 lsize)
{
  struct GS_resolve_cmd cmd;
  PMSG pmsg;

  cmd.dn = dn;
  cmd.rdn = rdn;
  cmd.alist = alist;
  cmd.lsize = lsize;

  pmsg.userlong1 = (long)&cmd;
  pmsg.userlong2 = E_LOCKED;	/* Just in case Pmsg() fails... */
  Pmsg(2, RESOLV_MBX, &pmsg);
  return pmsg.userlong2;
}

static void do_ser_disable(void)
{
  /* does nothing */
}

static void do_ser_enable(void)
{
  /* does nothing */
}

static int16 do_set_flag(int16 flag)
{
  int flg_val;

  if (flag < 0 || flag >= 64)
    return E_PARAMETER;
  /* This is probably not necessary, since a MiNT process currently can't
   * be preempted in supervisor mode, but I'm not taking any chances... */
  p_semaphore(2, FLG_SEM, -1);
  flg_val = flags[flag];
  flags[flag] = 1;
  p_semaphore(3, FLG_SEM, 0);
  return flg_val;
}

static void do_clear_flag(int16 flag)
{
  if (flag < 0 || flag >= 64)
    return;
  /* This is probably not necessary, since a MiNT process currently can't
   * be preempted in supervisor mode, but I'm not taking any chances... */
  p_semaphore(2, FLG_SEM, -1);
  flags[flag] = 0;
  p_semaphore(3, FLG_SEM, 0);
}

static CIB * do_CNgetinfo(int16 fd)
{
  return (CIB *)Fcntl(fd, 0, CNGETINFO_CMD);
}

/* Incompatibility:  None of the *_port() commands do anything.  Don't
   use a STiK dialer with GlueSTiK, gods know what will happen! */
static int16 do_on_port(char *port)
{
  return E_NOROUTINE;
}

static void do_off_port(char *port)
{
  return;
}

static int16 do_setvstr(char *vs, char *value)
{
  union GS_cmd cmd;

  cmd.setvstr_cmd.cmd = SETVSTR_CMD;
  cmd.setvstr_cmd.vs = vs;
  cmd.setvstr_cmd.value = value;
  return do_gs_ioctl(&cmd);
}

static int16 do_query_port(char *port)
{
  return E_NOROUTINE;
}

static int16 do_CNgets(int16 fd, char *buffer, int16 len, char delim)
{
  return Fcntl(fd, 0, CNGETS_CMD);
}


static TPL trampoline = {
  TRANSPORT_DRIVER,
  "Scott Bigham (GlueSTiK\277 v" GS_VERSION ")",
  "01.13",
  do_KRmalloc,
  do_KRfree,
  do_KRgetfree,
  do_KRrealloc,
  do_get_err_text,
  do_getvstr,
  do_carrier_detect,
  do_TCP_open,
  do_TCP_close,
  do_TCP_send,
  do_TCP_wait_state,
  do_TCP_ack_wait,
  do_UDP_open,
  do_UDP_close,
  do_UDP_send,
  do_CNkick,
  do_CNbyte_count,
  do_CNget_char,
  do_CNget_NDB,
  do_CNget_block,
  do_housekeep,
  do_resolve,
  do_ser_disable,
  do_ser_enable,
  do_set_flag,
  do_clear_flag,
  do_CNgetinfo,
  do_on_port,
  do_off_port,
  do_setvstr,
  do_query_port,
  do_CNgets,
  (int16 (*)(uint32, uint8, uint8, void *, uint16))0,
  (int16 (*)(int16 (*)(IP_DGRAM *), int16))0,
  (void (*)(IP_DGRAM *))0,
};

static DRV_HDR *do_get_dftab(char *tpl_name)
{
  /* we only have the one, so this is pretty easy... ;) */
  if (strcmp(tpl_name, trampoline.module) != 0)
    return 0;
  return (DRV_HDR *)&trampoline;
}

static int16 do_ETM_exec(char *tpl_name)
{
  /* even easier... ;) */
  return 0;
}

static CONFIG stik_cfg;
DRV_LIST stik_driver = {MAGIC, do_get_dftab, do_ETM_exec, &stik_cfg, NULL};

int init_stik_if(void)
{
  if (p_semaphore(0, FLG_SEM, 0) < 0) {
    c_conws("Unable to obtain STiK flag semaphore\r\n");
    return 0;
  }
  p_semaphore(3, FLG_SEM, 0);
  stik_cfg.client_ip = INADDR_LOOPBACK;
  return 1;
}

void cleanup_stik_if(void)
{
  p_semaphore(1, FLG_SEM, 0);
}
