/* osfstream.c */
/* Reads lines serially from a file (using OS handles) */

#include <stdlib.h>

#include "swis.h"

#include "log.h"
#include "osfstream.h"

#undef EOF
#define EOF (-1)

struct osfstream {
  int fh;
  long int pos;
};

osfstream *
osfstream_open(const char *filename)
{
  _kernel_oserror *e;
  osfstream *fp = malloc(sizeof(osfstream));
  if (!fp)
  {
    LOG(("Out of memory opening file '%s'", filename));
    return 0;
  }
  e = _swix(OS_Find, _INR(0,1)|_OUT(0), 0x42, filename, &fp->fh);
  if (e)
  {
    LOG(("Error opening file '%s':\n  %x %s\n", filename,
         e->errnum, e->errmess));
    return 0;
  }
  fp->pos = 0;
  return fp;
}

int
osfstream_close(fmstream fm)
{
  _kernel_oserror *e = _swix(OS_Find, _INR(0,1), 0, fm.fp->fh);
  if (e)
  {
    LOG(("Error closing file:\n  %x %s\n", e->errnum, e->errmess));
  }
  free(fm.fp);
  return (e != 0);
}

int
osfstream_seek(fmstream fm, long int offset)
{
  /* Can't check for oversized offset at this point, don't know size of file
     or anything, so bad offset won't fail until next read */
  if (offset < 0)
  {
    LOG(("Negative osfstream_seek offset\n"));
    return EOF;
  }
  fm.fp->pos = offset;
  return 0;
}

long int
osfstream_tell(fmstream fm)
{
  return fm.fp->pos;
}


/* If first, uses OS_GBPB 3 to set position too, otherwise uses OS_BGet.
   This may be marginally more efficient than OS_GBPB 3 every time.
   Returns -2 for error other than EOF. */
static int
osfstream_read_byte(osfstream *fp, int first)
{
  _kernel_oserror *e;
  int c = 0;
  int nnr;
  if (first)
  {
    e = _swix(OS_GBPB, _INR(0,4)|_OUT(3), 3, fp->fh, &c, 1, fp->pos, &nnr);
    if (e)
    {
      LOG(("osfstream_read_byte OS_GBPB 3 failed:\n  %x %s\n",
           e->errnum, e->errmess));
      return -2;
    }
    if (nnr)
    {
      LOG(("osfstream_read_byte OS_GBPB 3 failed, no bytes read (EOF?)\n"));
      return EOF;
    }
  }
  else
  {
    unsigned int flags;
    e = _swix(OS_BGet, _IN(1)|_OUT(0)|_OUT(_FLAGS), fp->fh, &c, &flags);
    if (e)
    {
      LOG(("osfstream_read_byte OS_BGet failed:\n  %x %s\n",
           e->errnum, e->errmess));
      return -2;
    }
    if (flags & _C)
    {
      LOG(("osfstream_read_byte OS_BGet failed, EOF\n"));
      return EOF;
    }
  }
  ++fp->pos;
  return c;
}

/* Rather like fgets(), but returns bytes read or -1 for error, 0 for EOF */
static int
osfstream_gets(char *buffer, size_t max, osfstream *fp)
{
  int n;
  int first = 1;
  int c = 32;
  for (n = 0; n < max && c >= 32; ++n)
  {
    c = osfstream_read_byte(fp, first);
    first = 0;
    if (c == -2)
      return -1;
    else if (c == EOF)
      return 0;
    buffer[n] = c;
  }
  return n;
}

#define LINE_UNIT 80

char *
osfstream_read_line(fmstream fm)
{
  int size = LINE_UNIT;
  char *buf = malloc(LINE_UNIT);
  for (;;)
  {
    if (!buf)
    {
      LOG(("osfstream_read_line: Out of memory\n"));
      return 0;
    }
    /* Place marks so we can tell what happened after osfstream_gets */
    buf[size - LINE_UNIT] = 0;
    buf[size - 1] = 0;
    /* Resume/start reading into start of newly allocated portion of buf */
    if (osfstream_gets(buf + size - LINE_UNIT, LINE_UNIT, fm.fp) == -1)
    {
      free(buf);
      return 0;
    }
    /* Have we got a whole line? */
    if (buf[size - 1] < 32)
    {
      int n;
      /* If it's a blank line, skip it */
      /*
      if (buf[0] >= 32)
      {
      */
        /* Replace ctrl char with 0 and trim before returning
           NB no need for range check in loop, we checked above */
        for (n = size - LINE_UNIT; buf[n] > 31; ++n);
        buf[n] = 0;
        buf = realloc(buf, n + 1);
        return buf;
      /*}*/
    }
    else
    {
      /* buf was too short, extend and loop to read more */
      size += LINE_UNIT;
      buf = realloc(buf, size);
    }
  }
  return 0;
}

char *
osfstream_read_bytes(fmstream fm, long int offset, size_t size, char *buffer)
{
  _kernel_oserror *e;
  size_t nnr;
  e = _swix(OS_GBPB, _INR(0,4)|_OUT(3), 3,
            fm.fp->fh, buffer, size, offset, &nnr);
  if (e)
  {
    LOG(("osfstream_read_bytes OS_GBPB 3 failed:\n  %x %s\n",
         e->errnum, e->errmess));
    return 0;
  }
  fm.fp->pos = offset + size - nnr;
  if (nnr)
  {
    LOG(("osfstream_read_bytes OS_GBPB 3 failed,"
         " only %d/%d bytes read (EOF?)\n",
         size - nnr, size));
    return 0;
  }
  return buffer;
}

char *
osfstream_read_pstring(fmstream fm)
{
  int size;
  char *result;
  size = osfstream_read_byte(fm.fp, 1);
  if (size < 0)
  {
    return 0;
  }
  result = malloc(size + 1);
  if (!result)
  {
    LOG(("osfstream_read_pstring out of memory\n"));
    return 0;
  }
  if (!osfstream_read_bytes(fm, fm.fp->pos, size, result))
  {
    return 0;
  }
  result[size - 1] = 0;
  return result;
}

int
osfstream_read_word16le(fmstream fm, fms_t16 *value)
{
  int msb;
  *value = osfstream_read_byte(fm.fp, 1);
  if (*value < 0)
    return EOF;
  msb = osfstream_read_byte(fm.fp, 0);
  if (msb < 0)
    return EOF;
  *value += msb << 8;
  return 0;
}

int
osfstream_read_word32le(fmstream fm, int *value)
{
  return osfstream_read_bytes(fm, fm.fp->pos, 4, (char *) value) ? 0 : EOF;
}
