/*  xfile.c -- implementation for fast line buffered files
**
**  Currently (Sat  06-15-1991) XFILEs are for reading CR-LF terminated lines
**  from MS-DOS text files.  Period.  It's not that the method can't be used
**  as well for output buffering, or (in some form) for binary files, it's
**  that such are handled fast enough to suit me already, whereas text mode
**  input performance leaves me wishing for more speed.  This attempts to
**  solve that problem.
**
**  Sun  06-16-1991 -- CR-LF accepted, but so is bare LF now; the extracted
**  line does NOT have a NEWLINE at the end anymore (which will likely be
**  a mixed blessing...)
**
**  The code should be fairly portable: if/when I get around to polishing it
**  (and that won't be until I've used it some and am sure it's stable) I'll
**  be aiming for near-ANSI portability; for now I'm not pushing so very hard
**  for that.
**
**  The semantics are a bit odd: the lines are returned in a buffer that the
**  XFILE owns, and may be altered by a call to xgetline or xclose.  For
**  applications that resent this, XFILEs probably aren't a big win anyway,
**  but there might be some cases where using XFILE and copying (some) lines
**  is still a good idea.  The performance with long lines is good: it can
**  handle lines the size of the buffer, though it may truncate up to one
**  QUANTUM less one bytes "early": this depends on the location of the start
**  of the line in the buffer when we begin scanning.  In practice, XBUFSIZE
**  is probably larger than you'd set for a line buffer size anyway...
**
**  INTERNALS:
**
**  Reading the first buffer's worth at open time makes the EOF case easier to
**  detect.
**
**  TO DO:
**
**  clean up xgetline!
*/

#include <stdlib.h>
#include <string.h>
#include "xfile.h"

#if !defined(__ZTC__) && !defined(__TURBOC__)
 static int DOS_OPEN(const char *name, int mode, ...)
 {
       int hdl;

       if (0 == _dos_open(name, mode, &hdl))
             return hdl;
       else  return -1;
 }

 static int READ(int fd, void *buf, size_t len)
 {
       unsigned count;

       if (0 == _dos_read(fd, buf, len, &count))
             return count;
       else  return -1;
 }
#endif

#ifndef XBUFN               /* set default # of quanta in buffer, allow -D */
 #define XBUFN 8
#endif

#define QUANTUM 512
#define XBUFSIZE (XBUFN * QUANTUM)


/*  xopen -- allocate and open an XFILE
**
**  NB: currently I'm designing these for READ-ONLY TEXT FILES only: the xopen
**      interface may have to be changed...
**
**  returns pointer to XFILE of opened file or null pointer on error
**
**  ? should it leave a better error description somewhere ?
*/

XFILE *xopen(char const *name)
{
      XFILE *f = malloc(sizeof(XFILE) + XBUFSIZE + 1);
      int n;

      if (f == 0)
            goto error0;
      f->buf = (char *)f + sizeof(XFILE);

      if ((f->fd = DOS_OPEN(name, O_RDONLY)) < 0)
            goto error1;

      if ((n = READ(f->fd, f->buf, XBUFSIZE)) < 0)
            goto error2;

      f->buf[n] = 0;
      f->nextChar = f->buf;
      return f;

error2:
      CLOSE(f->fd);
error1:
      free(f);
error0:
      return 0;
}


/*
**  xclose -- close and deallocate an XFILE
*/

void xclose(XFILE *f)
{
      CLOSE(f->fd);
      free(f);
}


/*
**  xgetline -- get the next text line into memory
**
**  returns a pointer to the line (a NUL-terminated string) or a null pointer
*/

char *xgetline(XFILE *f)
{
      char *s = f->nextChar, *p;
      int n;

      for (p = s; *p != 0; ++p)
      {
            if (*p == '\n')
            {
                  if (s < p && p[-1] == '\r')
                        p[-1] = 0;
                  else  *p = 0;
                  f->nextChar = p + 1;
                  return s;
            }
      }

      /*
      **  end of line not found in buffer -- p points to the sentinel NUL
      */

      if (p == f->buf)                    /* iff empty, EOF */
            return 0;

      /*
      **  move prefix of line to bottom of buffer
      */

      if (s != f->buf)
      {
            for (p = f->buf; (*p = *s) != 0; ++p, ++s)
                  ;
            s = f->buf;
      }

      n = XBUFSIZE - (p - f->buf);

      if (n < QUANTUM)                    /* insufficent room, break line */
      {
            f->nextChar = p;
            return s;
      }

      n = (n / QUANTUM) * QUANTUM;        /* quantize: count to read */
      n = READ(f->fd, p, n);

      /*
      **  read error is sort of ignored here... same return as EOF.
      **  we'll see if this proves to be sufficent...
      */

      if (n < 0)
      {
            f->nextChar = f->buf;
            f->buf[0] = 0;
            return 0;
      }

      p[n] = 0;

      for ( ; *p != 0; ++p)
      {
            if (*p == '\n')
            {
                  if (s < p && p[-1] == '\r')
                        p[-1] = 0;
                  else  *p = 0;
                  ++p;
                  break;
            }
      }

      f->nextChar = p;
      return p == s ? 0 : s;
}
