/* ----------------------------------------------------------------------------- 
 * fio.c
 *
 *     This file implements a number of standard I/O operations included
 *     formatted output, readline, and splitting.
 * 
 * Author(s) : David Beazley (beazley@cs.uchicago.edu)
 *
 * Copyright (C) 1999-2000.  The University of Chicago
 * See the file LICENSE for information on usage and redistribution.	
 * ----------------------------------------------------------------------------- */

char cvsroot_fio_c[] = "$Header: /cvs/projects/SWIG/Source/DOH/fio.c,v 1.1 2002/12/03 20:56:42 beazley Exp $";

#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;
  if (!encodings || !(handle = Getattr(encodings,name))) {
    return Copy(s);
  }
  pos = Tell(s);
  Seek(s,0,SEEK_SET);
  fn = (DOH *(*)(DOH *)) Data(handle);
  ns = (*fn)(s);
  Seek(s,pos,SEEK_SET);
  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;

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

  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(*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 == '(') {
	ec = encoder;
	state = 60;
      } else {
	*(fmt++) = *p;
      }
      break;
      
    case 20: /* Hmmm. At the start of a width field */
      if (isdigit(*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(*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(*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 == ')') {
	*ec = 0;
	state = 10;
      } 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 = "";
	  
	  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 = NewString("");
    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;
}

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

DOH *
DohReadline(DOH *in) {
  char c;
  int n = 0;
  DOH *s = NewString("");
  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++;
  }
}

  
