/* 
 * Copyright (c) 1993-1994 by Xerox Corporation.  All rights reserved.
 *
 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
 * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 * Permission is hereby granted to use or copy this program
 * for any purpose,  provided the above notices are retained on all copies.
 * Permission to modify the code and to distribute modified code is granted,
 * provided the above notices are retained, and a notice that the code was
 * modified is included with the above copyright notice.
 */
/* An sprintf implementation that understands cords.  This is probably	*/
/* not terribly portable.  It assumes an ANSI stdarg.h.  It further	*/
/* assumes that I can make copies of va_list variables, and read 	*/
/* arguments repeatedly by applyting va_arg to the copies.  This	*/
/* could be avoided at some performance cost.				*/
/* We also assume that unsigned and signed integers of various kinds	*/
/* have the same sizes, and can be cast back and forth.			*/
/* We assume that void * and char * have the same size.			*/
/* All this cruft is needed because we want to rely on the underlying	*/
/* sprintf implementation whenever possible.				*/
/* Boehm, September 21, 1995 6:00 pm PDT */

#include "cord.h"
#include "ec.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "gc.h"

#define CONV_SPEC_LEN 50	/* Maximum length of a single	*/
				/* conversion specification.	*/
#define CONV_RESULT_LEN 50	/* Maximum length of any 	*/
				/* conversion with default	*/
				/* width and prec.		*/


static int ec_len(CORD_ec x)
{
    return(CORD_len(x[0].ec_cord) + (x[0].ec_bufptr - x[0].ec_buf));
}

/* Possible nonumeric precision values.	*/
# define NONE -1
# define VARIABLE -2
/* Copy the conversion specification from CORD_pos into the buffer buf	*/
/* Return negative on error.						*/
/* Source initially points one past the leading %.			*/
/* It is left pointing at the conversion type.				*/
/* Assign field width and precision to *width and *prec.		*/
/* If width or prec is *, VARIABLE is assigned.				*/
/* Set *left to 1 if left adjustment flag is present.			*/
/* Set *long_arg to 1 if long flag ('l' or 'L') is present, or to	*/
/* -1 if 'h' is present.						*/
static int extract_conv_spec(CORD_pos source, char *buf,
			     int * width, int *prec, int *left, int * long_arg)
{
    register int result = 0;
    register int current_number = 0;
    register int saw_period = 0;
    register int saw_number;
    register int chars_so_far = 0;
    register char current;
    
    *width = NONE;
    buf[chars_so_far++] = '%';
    while(CORD_pos_valid(source)) {
        if (chars_so_far >= CONV_SPEC_LEN) return(-1);
        current = CORD_pos_fetch(source);
        buf[chars_so_far++] = current;
        switch(current) {
	  case '*':
	    saw_number = 1;
	    current_number = VARIABLE;
	    break;
          case '0':
            if (!saw_number) {
                /* Zero fill flag; ignore */
                break;
            } /* otherwise fall through: */
          case '1':
	  case '2':
	  case '3':
	  case '4':
	  case '5':
          case '6':
	  case '7':
	  case '8':
	  case '9':
	    saw_number = 1;
	    current_number *= 10;
	    current_number += current - '0';
	    break;
	  case '.':
	    saw_period = 1;
	    if(saw_number) {
	        *width = current_number;
	        saw_number = 0;
	    }
	    current_number = 0;
	    break;
	  case 'l':
	  case 'L':
	    *long_arg = 1;
	    current_number = 0;
	    break;
	  case 'h':
	    *long_arg = -1;
	    current_number = 0;
	    break;
	  case ' ':
	  case '+':
	  case '#':
	    current_number = 0;
	    break;
	  case '-':
	    *left = 1;
	    current_number = 0;
	    break;
	  case 'd':
	  case 'i':
	  case 'o':
	  case 'u':
	  case 'x':
	  case 'X':
	  case 'f':
	  case 'e':
	  case 'E':
	  case 'g':
	  case 'G':
	  case 'c':
	  case 'C':
	  case 's':
	  case 'S':
	  case 'p':
	  case 'n':
	  case 'r':
	    goto done;          
          default:
            return(-1);
        }
        CORD_next(source);
    }
    return(-1);
  done:
    if (saw_number) {
    	if (saw_period) {
    	    *prec = current_number;
    	} else {
    	    *prec = NONE;
    	    *width = current_number;
    	}
    } else {
    	*prec = NONE;
    }
    buf[chars_so_far] = '\0';
    return(result);
}

int CORD_vsprintf(CORD * out, CORD format, va_list args)
{
    CORD_ec result;
    register int count;
    register char current;
    CORD_pos pos;
    char conv_spec[CONV_SPEC_LEN + 1];
    
    CORD_ec_init(result);
    for (CORD_set_pos(pos, format, 0); CORD_pos_valid(pos); CORD_next(pos)) {
       	current = CORD_pos_fetch(pos);
       	if (current == '%') {
            CORD_next(pos);
            if (!CORD_pos_valid(pos)) return(-1);
            current = CORD_pos_fetch(pos);
            if (current == '%') {
               	CORD_ec_append(result, current);
            } else {
             	int width, prec;
             	int left_adj = 0;
             	int long_arg = 0;
		CORD arg;
		size_t len;
               
              	if (extract_conv_spec(pos, conv_spec,
              			      &width, &prec,
              			      &left_adj, &long_arg) < 0) {
              	    return(-1);
              	}
              	current = CORD_pos_fetch(pos);
            	switch(current) {
            	    case 'n':
            	    	/* Assign length to next arg */
            	    	if (long_arg == 0) {
            	    	    int * pos_ptr;
            	    	    pos_ptr = va_arg(args, int *);
            	    	    *pos_ptr = ec_len(result);
            	    	} else if (long_arg > 0) {
            	    	    long * pos_ptr;
            	    	    pos_ptr = va_arg(args, long *);
            	    	    *pos_ptr = ec_len(result);
            	    	} else {
            	    	    short * pos_ptr;
            	    	    pos_ptr = va_arg(args, short *);
            	    	    *pos_ptr = ec_len(result);
            	    	}
            	    	goto done;
            	    case 'r':
            	    	/* Append cord and any padding	*/
            	    	if (width == VARIABLE) width = va_arg(args, int);
            	    	if (prec == VARIABLE) prec = va_arg(args, int);
			arg = va_arg(args, CORD);
			len = CORD_len(arg);
			if (prec != NONE && len > prec) {
			  if (prec < 0) return(-1);
			  arg = CORD_substr(arg, 0, prec);
			  len = prec;
			}
			if (width != NONE && len < width) {
			  char * blanks = GC_MALLOC_ATOMIC(width-len+1);

			  memset(blanks, ' ', width-len);
			  blanks[width-len] = '\0';
			  if (left_adj) {
			    arg = CORD_cat(arg, blanks);
			  } else {
			    arg = CORD_cat(blanks, arg);
			  }
			}
			CORD_ec_append_cord(result, arg);
            	    	goto done;
		    case 'c':
			if (width == NONE && prec == NONE) {
			    register char c;

			    c = va_arg(args, int);
			    CORD_ec_append(result, c);
			    goto done;
			}
			break;
		    case 's':
		        if (width == NONE && prec == NONE) {
			    char * str = va_arg(args, char *);
			    register char c;

			    while (c = *str++) {
			        CORD_ec_append(result, c);
			    }
			    goto done;
			}
			break;
            	    default:
            	        break;
            	}
            	/* Use standard sprintf to perform conversion */
            	{
            	    register char * buf;
            	    va_list vsprintf_args = args;
            	    	/* The above does not appear to be sanctioned	*/
            	    	/* by the ANSI C standard.			*/
            	    int max_size = 0;
            	    int res;
            	    	
            	    if (width == VARIABLE) width = va_arg(args, int);
            	    if (prec == VARIABLE) prec = va_arg(args, int);
            	    if (width != NONE) max_size = width;
            	    if (prec != NONE && prec > max_size) max_size = prec;
            	    max_size += CONV_RESULT_LEN;
            	    if (max_size >= CORD_BUFSZ) {
            	        buf = GC_MALLOC_ATOMIC(max_size + 1);
            	    } else {
            	        if (CORD_BUFSZ - (result[0].ec_bufptr-result[0].ec_buf)
            	            < max_size) {
            	            CORD_ec_flush_buf(result);
            	        }
            	        buf = result[0].ec_bufptr;
            	    }
            	    switch(current) {
            	        case 'd':
            	        case 'i':
            	        case 'o':
            	        case 'u':
            	        case 'x':
            	        case 'X':
            	        case 'c':
            	            if (long_arg <= 0) {
            	              (void) va_arg(args, int);
            	            } else if (long_arg > 0) {
            	              (void) va_arg(args, long);
            	            }
            	            break;
            	        case 's':
            	        case 'p':
            	            (void) va_arg(args, char *);
            	            break;
            	        case 'f':
            	        case 'e':
            	        case 'E':
            	        case 'g':
            	        case 'G':
            	            (void) va_arg(args, double);
            	            break;
            	        default:
            	            return(-1);
            	    }
            	    res = vsprintf(buf, conv_spec, vsprintf_args);
            	    len = (size_t)res;
            	    if ((char *)(GC_word)res == buf) {
            	    	/* old style vsprintf */
            	    	len = strlen(buf);
            	    } else if (res < 0) {
            	        return(-1);
            	    }
            	    if (buf != result[0].ec_bufptr) {
            	        register char c;

			while (c = *buf++) {
			    CORD_ec_append(result, c);
		        }
		    } else {
		        result[0].ec_bufptr = buf + len;
		    }
            	}
              done:;
            }
        } else {
            CORD_ec_append(result, current);
        }
    }
    count = ec_len(result);
    *out = CORD_balance(CORD_ec_to_cord(result));
    return(count);
}

int CORD_sprintf(CORD * out, CORD format, ...)
{
    va_list args;
    int result;
    
    va_start(args, format);
    result = CORD_vsprintf(out, format, args);
    va_end(args);
    return(result);
}

int CORD_fprintf(FILE * f, CORD format, ...)
{
    va_list args;
    int result;
    CORD out;
    
    va_start(args, format);
    result = CORD_vsprintf(&out, format, args);
    va_end(args);
    if (result > 0) CORD_put(out, f);
    return(result);
}

int CORD_vfprintf(FILE * f, CORD format, va_list args)
{
    int result;
    CORD out;
    
    result = CORD_vsprintf(&out, format, args);
    if (result > 0) CORD_put(out, f);
    return(result);
}

int CORD_printf(CORD format, ...)
{
    va_list args;
    int result;
    CORD out;
    
    va_start(args, format);
    result = CORD_vsprintf(&out, format, args);
    va_end(args);
    if (result > 0) CORD_put(out, stdout);
    return(result);
}

int CORD_vprintf(CORD format, va_list args)
{
    int result;
    CORD out;
    
    result = CORD_vsprintf(&out, format, args);
    if (result > 0) CORD_put(out, stdout);
    return(result);
}
