#include "kerbind.h"
#include "gs_descr.h"


static long gssock_open(FILEPTR *fp);
static long gssock_write(FILEPTR *fp, char *buf, long buflen);
#if 0	/* This is not static anymore, so that gs_read_delim() can use it */
static
#endif
       long gssock_read(FILEPTR *fp, char *buf, long buflen);
static long gssock_lseek(FILEPTR *fp, long where, short whence);
static long gssock_ioctl(FILEPTR *fp, short cmd, void *buf);
static long gssock_datime(FILEPTR *fp, short *timeptr, short rwflag);
static long gssock_close(FILEPTR *fp, short pid);
static long gssock_select(FILEPTR *fp, long proc, short mode);
static void gssock_unselect(FILEPTR *fp, long proc, short mode);
static long gs_command(FILEPTR *fp, union GS_cmd *args);

struct devdrv gsdev = {
  gssock_open, gssock_write, gssock_read, gssock_lseek,
  gssock_ioctl, gssock_datime, gssock_close,
  gssock_select, gssock_unselect,
  { 0L, 0L, 0L }
};

struct dev_descr gsdev_descr = {
  &gsdev, 0, 0, (struct tty *)0, { 0L, 0L, 0L, 0L }
};


static long gssock_open(FILEPTR *fp)
{
  struct GS *gs = gs_create();

  if (!gs) {
    DEBUG(("gsdev: gssock_open: out of memory"));
    LOG(DBG_ERROR, "gssock_open() returns %E", E_NOMEM);
    return E_NOMEM;
  }

  LOG(DBG_SYSCALL, "gssock_open() returns 0");
  fp->devinfo = (long)gs;
  return 0;
}

static long gssock_write(FILEPTR *fp, char *buf, long buflen)
{
  struct GS *gs = (struct GS *)fp->devinfo;
  long r, n;

  LOG(DBG_SYSCALL, "In gssock_write(%p, %p, %l)",
      (void *)fp, (void *)buf, buflen);

  if (gs->flags & GS_NOSOCKET) {
    LOG(DBG_ERROR, "In gssock_write(%p, %p, %l): bad handle",
	(void *)fp, (void *)buf, buflen);
    return E_BADHANDLE;
  }

  if ((gs->flags & GS_LISTENING) && gs_accept(gs) != 0) {
    LOG(DBG_SYSCALL, "In gssock_write(%p, %p, %l): no connections arrived",
	(void *)fp, (void *)buf, buflen);
    return E_LISTEN;
  }

#if 0
  /* This is kind of a lie, but at least it's an error code a STiK app will
   * be prepared to deal with. */
  if ((gs->flags & GS_PEND_OPEN) && gs_establish(gs) != 0) {
    LOG(DBG_SYSCALL, "In gssock_write(%p, %p, %l): open in progress",
	(void *)fp, (void *)buf, buflen);
    return E_OBUFFULL;
  }
#endif

  /* STiK has no provisions for writing part of a buffer and saving the
   * rest for later; so make sure we can write the whole thing. */
  r = f_cntl(gs->sock_fd, (long)&n, FIONWRITE);
  if (r < 0) {
    LOG(DBG_ERROR, "In gssock_write(%p, %p, %l): Fcntl(FIONWRITE) returned %d",
	(void *)fp, (void *)buf, buflen, r);
    return gs_xlate_error(r, "gssock_write");
  }
  if (n < buflen) {
    LOG(DBG_ERROR,
	"In gssock_write(%p, %p, %l): can only write %l bytes; "
	"returning E_OBUFFULL",
	(void *)fp, (void *)buf, buflen, n);
    return E_OBUFFULL;
  }

  /* Okay, we can safely write. */
  r = f_write(gs->sock_fd, buflen, buf);
  if (r < 0) {
    LOG(DBG_ERROR, "In gssock_write(%p, %p, %l): Fwrite() returned %e",
	(void *)fp, (void *)buf, buflen, r);
    return gs_xlate_error(r, "gssock_write");
  } else {
    /* Okay, according to the Fcntl(), we should have been able to write
     * everything; warn if we didn't. */
    if (r < buflen)
      LOG(DBG_ERROR, "In gssock_write(%p, %p, %l):  "
	  "only got %l of %l bytes",
	  (void *)fp, (void *)buf, buflen, r, buflen);
    LOG(DBG_SYSCALL, "gssock_write(%p, %p, %l) returns E_NORMAL",
	(void *)fp, (void *)buf, buflen);
    return E_NORMAL;
  }
}

long gssock_read(FILEPTR *fp, char *buf, long buflen)
{
  struct GS *gs = (struct GS *)fp->devinfo;
  long r, n;

  LOG(DBG_SYSCALL, "In gssock_read(%p, %p, %l)",
      (void *)fp, (void *)buf, buflen);

  if (gs->flags & GS_NOSOCKET) {
    LOG(DBG_SYSCALL, "In gssock_read(%p, %p, %l): bad handle",
	(void *)fp, (void *)buf, buflen);
    return E_BADHANDLE;
  }

  if ((gs->flags & GS_LISTENING) && gs_accept(gs) != 0) {
    LOG(DBG_SYSCALL, "In gssock_read(%p, %p, %l): no connections arrived",
	(void *)fp, (void *)buf, buflen);
    return E_LISTEN;
  }

#if 0
  if ((gs->flags & GS_PEND_OPEN) && gs_establish(gs) != 0) {
    LOG(DBG_SYSCALL, "In gssock_read(%p, %p, %l): open in progress",
	(void *)fp, (void *)buf, buflen);
    return E_NODATA;
  }
#endif

  /* STiK has no provisions for reading less than a full buffer's worth of
   * data; make sure we can fill the buffer. */
  n = gs_canread(fp);
  if (n < 0)
    return n;
  if (n < buflen) {
    LOG(DBG_SYSCALL, "In gssock_read(%p, %p, %l): only %l bytes available",
	(void *)fp, (void *)buf, buflen, n);
    return E_NODATA;
  }

  /* Okay, we can safely read. */
  r = f_read(gs->sock_fd, buflen, buf);
  if (r < 0) {
    LOG(DBG_ERROR, "In gssock_read(%p, %p, %l): Fread() returns %e",
	(void *)fp, (void *)buf, buflen, r);
    return gs_xlate_error(r, "gssock_read");
  } else {
    /* Okay, according to the Fcntl(), we should have been able to fill the
     * entire buffer; warn if we didn't. */
    if (r < buflen)
      LOG(DBG_ERROR, "In gssock_read(%p, %p, %l):  "
	  "only able to read %l of %l bytes",
	  (void *)fp, (void *)buf, buflen, r, buflen);
    LOG(DBG_SYSCALL, "gssock_read(%p, %p, %l) reads %l bytes",
	(void *)fp, (void *)buf, buflen, r);
    return buflen;
  }
}

static long gssock_lseek(FILEPTR *fp, long where, short whence)
{
  /* MiNTnet sockets don't allow this, and STiK has no provisions for it;
   * what're ya gonna do? ;) */
  return E_NOROUTINE;
}

static long gssock_ioctl(FILEPTR *fp, short cmd, void *buf)
{
  struct GS *gs = (struct GS *)fp->devinfo;

  switch (cmd) {
    /* Most commands have args, and so get called via GLUESTIK_CMD. */
    case GLUESTIK_CMD:
      return gs_command(fp, (union GS_cmd *)buf);

    /* These commands have no args (or a simple arg passed via |buf|), and
     * so can also be called directly. */
    case CNBYTECOUNT_CMD:
      return gs_canread(fp);
    case CNGETNDB_CMD:
      return gs_readndb(fp);
    case CNGETINFO_CMD:
      LOG(DBG_SYSCALL, "In gssock_ioctl(%p, CNGETINFO_CMD)", (void *)fp);
      if (!gs) {
	LOG(DBG_ERROR, "In gssock_ioctl(%p, CNGETINFO_CMD): bad handle",
	    (void *)fp);
	return E_BADHANDLE;
      }
      LOG(DBG_SYSCALL, "gssock_ioctl(%p, CNGETINFO_CMD) returns %p",
	  (void *)fp, (void *)&(gs->cib));
      return (long)&(gs->cib);
    case GETERRTXT_CMD:
      return (long)do_get_err_text((int16)(long)buf);
    case GETDBGLVL_CMD:
      return debug_level;
    case SETDBGLVL_CMD:
      debug_level = (long)buf;
      return 0;

    /* Everything else gets passed through to the underlying socket. */
    default:
      if (gs->flags & GS_NOSOCKET) {
	LOG(DBG_ERROR, "In gssock_ioctl(%p): bad handle", (void *)fp);
	return E_BADHANDLE;
      }
      return f_cntl(gs->sock_fd, buf, cmd);
  }
}

static long gssock_datime(FILEPTR *fp, short *timeptr, short rwflag)
{
  struct GS *gs = (struct GS *)fp->devinfo;

  if (gs->flags & GS_NOSOCKET) {
    LOG(DBG_ERROR, "In gssock_datime(%p, %p, %d): bad handle",
	(void *)fp, (void *)timeptr, (int)rwflag);
    return E_BADHANDLE;
  }

  return f_datime(timeptr, gs->sock_fd, rwflag);
}

static long gssock_close(FILEPTR *fp, short pid)
{
  struct GS *gs = (struct GS *)fp->devinfo;

  LOG(DBG_SYSCALL, "In gssock_close(%p)", (void *)fp);
  gs_release(gs);
  return 0;
}

/* Fselect() should never be called on a GlueSTiK connection directly, but
 * on the contained socket via TCP_wait_state() or similar. */
static long gssock_select(FILEPTR *fp, long proc, short mode)
{
  return E_NOROUTINE;
}

static void gssock_unselect(FILEPTR *fp, long proc, short mode)
{
  return;
}

static long gs_command(FILEPTR *fp, union GS_cmd *args)
{
  struct GS *gs = (struct GS *)fp->devinfo;

  switch (args->cmd.cmd) {
    case KRMALLOC_CMD:
      return (long)gs_mem_alloc(args->KRmalloc_cmd.size);
    case KRFREE_CMD:
      gs_mem_free(args->KRfree_cmd.mem);
      return 0;
    case KRGETFREE_CMD:
      return gs_mem_getfree(args->KRgetfree_cmd.flag);
    case KRREALLOC_CMD:
      return (long)gs_mem_realloc(args->KRrealloc_cmd.mem,
				  args->KRrealloc_cmd.newsize);
    case GETVSTR_CMD:
      return (long)gs_getvstr(args->getvstr_cmd.var);
    case TCP_OPEN_CMD:
      return gs_connect(fp, args);
    case TCP_WAIT_CMD:
      return gs_wait(fp, args->TCP_wait_state_cmd.timeout);
    case UDP_OPEN_CMD:
      return gs_udp_open(fp, args);
    case CNBYTECOUNT_CMD:
      return gs_canread(fp);
    case CNGETNDB_CMD:
      return gs_readndb(fp);
    case CNGETINFO_CMD:
      if (!gs)
	return E_BADHANDLE;
      return (long)&(gs->cib);
    case SETVSTR_CMD:
      return gs_setvstr(args->setvstr_cmd.vs, args->setvstr_cmd.value);
    case CNGETS_CMD:
      return gs_read_delim(fp, args);
    case GETERRTXT_CMD:
      return (long)do_get_err_text(args->get_err_text_cmd.err);
    case LOGENTRY_CMD:
      if (args->log_entry_cmd.lev <= debug_level)
	log_entry_raw(args->log_entry_cmd.pid, args->log_entry_cmd.lev,
		      args->log_entry_cmd.line);
      return 0;
    case GETDBGLVL_CMD:
      return debug_level;
    case SETDBGLVL_CMD:
      debug_level = args->set_dbglvl_cmd.lev;
      return 0;
    default:
      return E_PARAMETER;
  }
}
