/* ----------------------------------------------------------------------------- 
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License.  Some additional
 * terms also apply to certain portions of SWIG.  The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at http://www.swig.org/legal.html. 
 *
 * fio.c
 *
 *     This file implements a number of standard I/O operations included
 *     formatted output, readline, and splitting. 
 * ----------------------------------------------------------------------------- */

char cvsroot_fio_c[] = "$Id: fio.c 11876 2010-02-27 23:53:33Z wsfulton $";

#include "dohint.h"

#define OBUFLEN  512

static DOH *encodings = 0;	/* Encoding hash */

/* -----------------------------------------------------------------------------
 * Writen()
 *
 * Write's N characters of output and retries until all characters are
 * written.   This is useful should a write operation encounter a spurious signal. 
 * ----------------------------------------------------------------------------- */

static int Writen(DOH *out, void *buffer, int len) {
  int nw = len, ret;
  char *cb = (char *) buffer;
  while (nw) {
    ret = Write(out, cb, nw);
    if (ret < 0)
      return -1;
    nw = nw - ret;
    cb += ret;
  }
  return len;
}

/* -----------------------------------------------------------------------------
 * DohEncoding()
 *
 * Registers a new printf encoding method.   An encoder function should accept
 * two file-like objects and operate as a filter. 
 * ----------------------------------------------------------------------------- */

void DohEncoding(char *name, DOH *(*fn) (DOH *s)) {
  if (! encodings)
    encodings = NewHash();
  Setattr(encodings, (void *) name, NewVoid((void *) fn, 0));
}

/* internal function for processing an encoding */
static DOH *encode(char *name, DOH *s) {
  DOH *handle, *ns;
  DOH *(*fn) (DOH *);
  long pos;
  char *cfmt = strchr(name, ':');
  DOH *tmp = 0;
  if (cfmt) {
    tmp = NewString(cfmt + 1);
    Append(tmp, s);
    Setfile(tmp, Getfile((DOH *) s));
    Setline(tmp, Getline((DOH *) s));
    *cfmt = '\0';
  }
  if (! encodings || !( handle = Getattr(encodings, name))) {
    return Copy(s);
  }
  if (tmp)
    s = tmp;
  pos = Tell(s);
  Seek(s, 0, SEEK_SET);
  fn = (DOH *(*)(DOH *)) Data(handle);
  ns = (*fn) (s);
  Seek(s, pos, SEEK_SET);
  if (tmp)
    Delete(tmp);
  return ns;
}

/* -----------------------------------------------------------------------------
 * DohvPrintf()
 *
 * DOH implementation of printf.   Output can be directed to any file-like object
 * including bare FILE * objects.   The same formatting codes as printf are
 * recognized with two extensions:
 *
 *       %s          - Prints a "char *" or the string representation of any
 *                     DOH object.   This will implicitly result in a call to
 *                     Str(obj). 
 *
 *       %(encoder)* - Filters the output through an encoding function registered
 *                     with DohEncoder(). 
 *
 * Note: This function is not particularly memory efficient with large strings. 
 * It's better to use Dump() or some other method instead. 
 * ----------------------------------------------------------------------------- */

int DohvPrintf(DOH *so, const char *format, va_list ap) {
  static char *fmt_codes = "dioxXucsSfeEgGpn";
  int state = 0;
  const char *p = format;
  char newformat[256];
  char obuffer[OBUFLEN];
  char *fmt = 0;
  char temp[64];
  int widthval = 0;
  int precval = 0;
  int maxwidth;
  char *w = 0;
  int ivalue;
  double dvalue;
  void *pvalue;
  char *stemp;
  int nbytes = 0;
  char encoder[128], *ec = 0;
  int plevel = 0;

  memset(newformat, 0, sizeof(newformat));

  while (*p) {
    switch (state) {
    case 0:			/* Ordinary text */
      if (*p ! = '%') {
	Putc(*p, so);
	nbytes++;
      } else {
	fmt = newformat;
	widthval = 0;
	precval = 0;
	*(fmt++) = *p;
	encoder[0] = 0;
	state = 10;
      }
      break;
    case 10:			/* Look for a width and precision */
      if (isdigit((int) *p) && (*p ! = '0')) {
	w = temp;
	*(w++) = *p;
	*(fmt++) = *p;
	state = 20;
      } else if (strchr(fmt_codes, *p)) {
	/* Got one of the formatting codes */
	p--;
	state = 100;
      } else if (*p == '*') {
	/* Width field is specified in the format list */
	widthval = va_arg(ap, int);
	sprintf(temp, "%d", widthval);
	for (w = temp; *w; w++) {
	  *(fmt++) = *w;
	}
	state = 30;
      } else if (*p == '%') {
	Putc(*p, so);
	fmt = newformat;
	nbytes++;
	state = 0;
      } else if (*p == '(') {
	++plevel;
	ec = encoder;
	state = 60;
      } else {
	*(fmt++) = *p;
      }
      break;

    case 20:			/* Hmmm.  At the start of a width field */
      if (isdigit((int) *p)) {
	*(w++) = *p;
	*(fmt++) = *p;
      } else if (strchr(fmt_codes, *p)) {
	/* Got one of the formatting codes */
	/* Figure out width */
	*w = 0;
	widthval = atoi(temp);
	p--;
	state = 100;
      } else if (*p == '.') { 
	*w = 0;
	widthval = atoi(temp);
	w = temp;
	*(fmt++) = *p;
	state = 40;
      } else {
	/* ??? */ 
	*w = 0;
	widthval = atoi(temp);
	state = 50;
      }
      break;

    case 30:			/* Parsed a width from an argument.   Look for a . */ 
      if (*p == '.') { 
	w = temp;
	*(fmt++) = *p;
	state = 40;
      } else if (strchr(fmt_codes, *p)) {
	/* Got one of the formatting codes */
	/* Figure out width */
	p--;
	state = 100;
      } else {
	/* hmmm.  Something else. */ 
	state = 50;
      }
      break;

    case 40:
      /* Start of precision expected */
      if (isdigit((int) *p) && (*p ! = '0')) {
	*(fmt++) = *p;
	*(w++) = *p;
	state = 41;
      } else if (*p == '*') {
	/* Precision field is specified in the format list */
	precval = va_arg(ap, int);
	sprintf(temp, "%d", precval);
	for (w = temp; *w; w++) {
	  *(fmt++) = *w;
	}
	state = 50;
      } else if (strchr(fmt_codes, *p)) {
	p--;
	state = 100;
      } else {
	*(fmt++) = *p;
	state = 50;
      }
      break;
    case 41:
      if (isdigit((int) *p)) {
	*(fmt++) = *p;
	*(w++) = *p;
      } else if (strchr(fmt_codes, *p)) {
	/* Got one of the formatting codes */
	/* Figure out width */
	*w = 0;
	precval = atoi(temp);
	p--;
	state = 100;
      } else {
	*w = 0;
	precval = atoi(temp);
	*(fmt++) = *p;
	state = 50;
      }
      break;
      /* Hang out, wait for format specifier */
    case 50:
      if (strchr(fmt_codes, *p)) {
	p--;
	state = 100;
      } else {
	*(fmt++) = *p;
      }
      break;

      /* Got an encoding header */
    case 60:
      if (*p == '(') {
	++plevel;
	*ec = *p;
	ec++;
      } else if (*p == ')') {
	--plevel;
	if (plevel <= 0) {
	  *ec = 0;
	  state = 10;
	} else {
	  *ec = *p;
	  ec++;
	}
      } else {
	*ec = *p;
	ec++;
      }
      break;
    case 100:
      /* Got a formatting code */
      if (widthval < precval)
	maxwidth = precval;
      else
	maxwidth = widthval;
      if ((*p == 's') || (*p == 'S')) {	/* Null-Terminated string */
	DOH *doh;
	DOH *Sval;
	DOH *enc = 0;
	doh = va_arg(ap, DOH *);
	if (DohCheck(doh)) {
	  /* Is a DOH object. */ 
	  if (DohIsString(doh)) {
	    Sval = doh;
	  } else {
	    Sval = Str(doh);
	  }
	  if (strlen(encoder)) {
	    enc = encode(encoder, Sval);
	    maxwidth = maxwidth + strlen(newformat) + Len(enc);
	  } else {
	    maxwidth = maxwidth + strlen(newformat) + Len(Sval);
	  }
	  *(fmt++) = 's';
	  *fmt = 0;
	  if ((maxwidth + 1) < OBUFLEN) {
	    stemp = obuffer;
	  } else {
	    stemp = (char *) DohMalloc(maxwidth + 1);
	  }
	  if (enc) {
	    nbytes += sprintf(stemp, newformat, Data(enc));
	  } else {
	    nbytes += sprintf(stemp, newformat, Data(Sval));
	  }
	  if (Writen(so, stemp, strlen(stemp)) < 0)
	    return -1;
	  if ((DOH *) Sval ! = doh) {
	    Delete(Sval);
	  }
	  if (enc)
	    Delete(enc);
	  if (*p == 'S') {
	    Delete(doh);
	  }
	  if (stemp ! = obuffer) {
	    DohFree(stemp);
	  }
	} else {
	  if (! doh)
	    doh = (char *) "";

	  if (strlen(encoder)) {
	    DOH *s = NewString(doh);
	    Seek(s, 0, SEEK_SET);
	    enc = encode(encoder, s);
	    Delete(s);
	    doh = Char(enc);
	  } else {
	    enc = 0;
	  }
	  maxwidth = maxwidth + strlen(newformat) + strlen((char *) doh);
	  *(fmt++) = 's';
	  *fmt = 0;
	  if ((maxwidth + 1) < OBUFLEN) {
	    stemp = obuffer;
	  } else {
	    stemp = (char *) DohMalloc(maxwidth + 1);
	  }
	  nbytes += sprintf(stemp, newformat, doh);
	  if (Writen(so, stemp, strlen(stemp)) < 0)
	    return -1;
	  if (stemp ! = obuffer) {
	    DohFree(stemp);
	  }
	  if (enc)
	    Delete(enc);
	}
      } else {
	*(fmt++) = *p;
	*fmt = 0;
	maxwidth = maxwidth + strlen(newformat) + 64;

	/* Only allocate a buffer if it is too big to fit.   Shouldn't have to do
	   this very often */

	if (maxwidth < OBUFLEN)
	  stemp = obuffer;
	else
	  stemp = (char *) DohMalloc(maxwidth + 1);
	switch (*p) {
	case 'd':
	case 'i':
	case 'o':
	case 'u':
	case 'x':
	case 'X':
	case 'c':
	  ivalue = va_arg(ap, int);
	  nbytes += sprintf(stemp, newformat, ivalue);
	  break;
	case 'f':
	case 'g':
	case 'e':
	case 'E':
	case 'G':
	  dvalue = va_arg(ap, double);
	  nbytes += sprintf(stemp, newformat, dvalue);
	  break;
	case 'p':
	  pvalue = va_arg(ap, void *);
	  nbytes += sprintf(stemp, newformat, pvalue);
	  break;
	default:
	  break;
	}
	if (Writen(so, stemp, strlen(stemp)) < 0)
	  return -1;
	if (stemp ! = obuffer)
	  DohFree(stemp);
      }
      state = 0;
      break;
    }
    p++;
  }
  if (state) {
    int r;
    *fmt = 0;
    r = Writen(so, fmt, strlen(fmt));
    if (r < 0)
      return -1;
    nbytes += r;
  }
  return nbytes;
}

/* -----------------------------------------------------------------------------
 * DohPrintf()
 *
 * Variable length argument entry point to Printf
 * ----------------------------------------------------------------------------- */

int DohPrintf(DOH *obj, const char *format, ...) {
  va_list ap;
  int ret;
  va_start(ap, format);
  ret = DohvPrintf(obj, format, ap);
  va_end(ap);
  return ret;
}

/* -----------------------------------------------------------------------------
 * DohPrintv()
 * 
 * Print a null-terminated variable length list of DOH objects
 * ----------------------------------------------------------------------------- */

int DohPrintv(DOHFile * f, ...) {
  va_list ap;
  int ret = 0;
  DOH *obj;
  va_start(ap, f);
  while (1) {
    obj = va_arg(ap, void *);
    if ((! obj) || (obj == DohNone))
      break;
    if (DohCheck(obj)) {
      ret += DohDump(obj, f);
    } else {
      ret += DohWrite(f, obj, strlen((char *) obj));
    }
  }
  va_end(ap);
  return ret;
}

/* ----------------------------------------------------------------------------- 
 * DohCopyto()
 *
 * Copies all of the input from an input stream to an output stream.  Returns the
 * number of bytes copied. 
 * ----------------------------------------------------------------------------- */

int DohCopyto(DOH *in, DOH *out) {
  int nbytes = 0, ret;
  int nwrite = 0, wret;
  char *cw;
  char buffer[16384];

  if ((! in) || (! out))
    return 0;
  while (1) {
    ret = Read(in, buffer, 16384);
    if (ret > 0) {
      nwrite = ret;
      cw = buffer;
      while (nwrite) {
	wret = Write(out, cw, nwrite);
	if (wret < 0)
	  return -1;
	nwrite = nwrite - wret;
	cw += wret;
      }
      nbytes += ret;
    } else {
      return nbytes;
    }
  }
}


/* -----------------------------------------------------------------------------
 * DohSplit()
 *
 * Split an input stream into a list of strings delimited by the specified
 * character.   Optionally accepts a maximum number of splits to perform. 
 * ----------------------------------------------------------------------------- */

DOH *DohSplit(DOH *in, char ch, int nsplits) {
  DOH *list;
  DOH *str;
  int c;

  list = NewList();

  if (DohIsString(in)) {
    Seek(in, 0, SEEK_SET);
  }

  while (1) {
    str = NewStringEmpty();
    do {
      c = Getc(in);
    } while ((c ! = EOF) && (c == ch));
    if (c ! = EOF) {
      Putc(c, str);
      while (1) {
	c = Getc(in);
	if ((c == EOF) || ((c == ch) && (nsplits ! = 0)))
	  break;
	Putc(c, str);
      }
      nsplits--;
    }
    Append(list, str);
    Delete(str);
    if (c == EOF)
      break;
  }
  return list;
}

/* -----------------------------------------------------------------------------
 * DohSplitLines()
 *
 * Split an input stream into a list of strings delimited by newline characters. 
 * ----------------------------------------------------------------------------- */

DOH *DohSplitLines(DOH *in) {
  DOH *list;
  DOH *str;
  int c = 0;

  list = NewList();

  if (DohIsString(in)) {
    Seek(in, 0, SEEK_SET);
  }

  while (c ! = EOF) {
    str = NewStringEmpty();
    while ((c = Getc(in)) ! = '\n' && c ! = EOF) {
      Putc(c, str);
    }
    Append(list, str);
    Delete(str);
  }
  return list;
}


/* -----------------------------------------------------------------------------
 * DohReadline()
 *
 * Read a single input line and return it as a string. 
 * ----------------------------------------------------------------------------- */

DOH *DohReadline(DOH *in) {
  char c;
  int n = 0;
  DOH *s = NewStringEmpty();
  while (1) {
    if (Read(in, &c, 1) < 0) {
      if (n == 0) {
	Delete(s);
	return 0;
      }
      return s;
    }
    if (c == '\n')
      return s;
    if (c == '\r')
      continue;
    Putc(c, s);
    n++;
  }
}
