/*  -*- Mode: C -*-  */

/* snprintfv-test.c ---  */

/* Author:	       Gary V. Vaughan <gvv@techie.com>
 * Maintainer:	       Gary V. Vaughan <gvv@techie.com>
 * Created:	       Fri Nov 13 16:51:38 1998
 * Last Modified:      Thu Sep 23 15:08:09 1999
 *            by:      Gary V. Vaughan <garyv@samhain.aethos.co.uk>
 * ---------------------------------------------------------------------
 * @(#) $Id$
 * ---------------------------------------------------------------------
 */

/* Copyright (C) 1998, 1999 Gary V. Vaughan */

/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * As a special exception to the GNU General Public License, if you
 * distribute this file as part of a program that also links with and
 * uses the libopts library from AutoGen, you may include it under
 * the same distribution terms used by the libopts library.
 */

/* Code: */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <unistd.h>		/* for STDOUT_FILENO */
#ifndef STDOUT_FILENO
#  define STDOUT_FILENO 1
#endif

#define COMPILING_SNPRINTFV_C
#include "snprintfv.h"
#undef  COMPILING_SNPRINTFV_C
#include "register.h"

#include "mem.h"
#include "stream.h"
#include "filament.h"

#ifndef AUTHOR
#define AUTHOR		"Gary V. Vaughan <gvv@techie.com>"
#endif

#ifndef EOS
# define EOS		'\0'
#endif

#undef ISSET
#define ISSET(field, flag)	((field)&(flag))


/* --- Type Definitions --- */

/* We can't be more specific, because the signatures vary... */
typedef int API_func ();

typedef enum {
    STREAM_NONE,
    STREAM_FD,
    STREAM_STDOUT,
    STREAM_FILE,
    STREAM_UNLIMITED_BUFFER,
    STREAM_BUFFER,
    STREAM_ALLOC,
    STREAM_FILAMENT,
    STREAM_NATIVE
} stream_type;

typedef struct {
    char *name;
    API_func *func;
    stream_type stream;
} API_map_element;


/* --- Forward Declarations -- */

/* We must be carefull to provide a declaration for *all* static
 * functions, otherwise we get no type checking on the arguments
 * because of the PARAMS() K&R support we are using.
 */
static snv_constpointer *argv_dup PARAMS((unsigned argc, char *const argv[]));
static snv_constpointer *vector_canonicalize PARAMS((unsigned num_elements,
				snv_constpointer vector[]));
static int filprintfv PARAMS((filament *fil, const char *format,
				snv_constpointer const args[]));


/* --- Static Data --- */

/* A mapping from function names (-f flag) to API function pointers */

API_map_element map_name_to_func[] = {
    /*  name              function      stream	*/
    { "filprintfv",	filprintfv,	STREAM_FILAMENT },
    { "stream_printfv",	stream_printfv,	STREAM_NATIVE },
    { "dprintfv",	dprintfv,	STREAM_FD },
    { "printfv",	printfv,	STREAM_STDOUT },
    { "fprintfv",	fprintfv,	STREAM_FILE },
    
    /* call this on direct in the dispatch function;
       asprintfv returns a char* */
    { "asprintfv",	0/*asprintfv*/,	STREAM_ALLOC },

    { "sprintfv",	sprintfv,	STREAM_UNLIMITED_BUFFER },
    { "snprintfv",	snprintfv,	STREAM_BUFFER  },
    { NULL,		NULL,		STREAM_NONE }
};

static char *program_name = NULL;



/* --- The Code --- */

static snv_constpointer*
argv_dup (argc, argv)
    unsigned argc;
    char *const argv[];
{
    unsigned		index;
    snv_constpointer	*result;

    result	= snv_new(snv_constpointer, argc);

    for (index = 0; index < argc; index++)
    {
	result[index] = strdup(argv[index]);
    }
    
    return result;
}


static snv_constpointer*
vector_canonicalize (num_elements, vector)
    unsigned num_elements;
    snv_constpointer vector[];
{
    unsigned index;

    for (index = 0; index < num_elements; index++)
    {
	char	*string	 = (char*)vector[index];

	/* Is it an explicit string? */
	if ((string != NULL) && (string[0] == '\"'))
	{
	    /* TODO: allow escaped quotes (any chars)! */
	    char *ptr;

	    for (ptr = string +1; *ptr != EOS && *ptr != '\"'; ptr++)
	    {
		*string++ = *ptr;
	    }
	    *string = EOS;
	}

	/* Is it a NULL pointer (0x0)? */
	else if (strcmp(string, "0x0") == 0)
	{
	    snv_delete((snv_pointer)vector[index]);
	    vector[index] = NULL;
	}

	/* Does it look like a floating point number? */
	else if (strpbrk(string, ".eE") != NULL)
	{
	    /* TODO: let base 14 numbers (exnnn)
	             fall through to strtol below */
	    double	value;
	    char	*endptr;

	    value = strtod(string, &endptr);

	    if (*endptr == EOS && *string != EOS)
	    {
		snv_delete((snv_pointer)vector[index]);
					     /* FIXME: memory leak */
		vector[index] = (snv_pointer)snv_new(double, 1);
		*(double*)vector[index] = value;
	    }

	}

	/* Does it look like a whole number? */
	else 
	{
	    int		  flags = 0;
	    char	  *endptr;
	    boolean	  is_negative = FALSE;
	    unsigned long value;

#ifdef __STDC__
	    /* Can't snv_assert with strings without the ANSI `#' macro. */
	    snv_assert(strpbrk(string, ".eE") == NULL);
#endif

	    /* Find the sign. */
	    if (strchr("+-", *string))
	    {
		if (*string++ == '-')
		{
		    is_negative = TRUE;
		}
	    }
		
	    /* Extract the absolute value. */
	    value = strtoul(string, &endptr, 0);
	    
	    /* Accept trailing u and l as flags. */
	    while (*endptr != EOS && strchr("ulUL", *endptr) != NULL)
	    {
		switch (*endptr)
		{
		case 'u':
		case 'U':
		    snv_assert(is_negative == FALSE);
		    flags |= SNV_FLAG_UNSIGNED;
		    break;

		case 'l':
		case 'L':
		    flags |= SNV_FLAG_LONG;
		    break;
		}
		++endptr;
	    }

	    if (*endptr == EOS && *string != EOS)
	    {
					     /* FIXME: memory leak */
		snv_delete((snv_pointer)vector[index]);
		if (ISSET(flags, SNV_FLAG_LONG)
		    && ISSET(flags, SNV_FLAG_UNSIGNED))
		{
		    vector[index] = snv_new(unsigned long, 1);
		    *(unsigned long*)vector[index] = value;
		}
		else if (ISSET(flags, SNV_FLAG_LONG))
		{
		    vector[index] = snv_new(long, 1);
		    *(long*)vector[index] =
			(is_negative ? -1 : 1)*(long)value;
		}
		else if (ISSET(flags, SNV_FLAG_UNSIGNED))
		{
		    vector[index] = SNV_UINT_TO_POINTER((unsigned int)value);
		}
		else
		{
		    vector[index] =
			SNV_INT_TO_POINTER((is_negative ? -1 : 1)*(int)value);
		}
	    }
	}

	/* Still here?  Must be an implicit string then!
	 * No conversion required =)O|
	 */
    }

    return vector;
}

/* Just to show how easy it is to write your own:
   here is format function which writes to a filament based STREAM. */
static int
filprintfv (fil, format, args)
    filament *fil;
    const char *format;
    snv_constpointer const args[];
{
    STREAM out;
    stream_init(&out, (stream_gpointer)fil, SNV_UNLIMITED, NULL, filputc);
    return stream_printfv(&out, format, args);
}


int
main (argc, argv)
  int argc;
  char *const argv[];
{
    char    *function_name = "printfv";
    API_func *function_handle;
    snv_constpointer *vArgs;
    char    *buf = NULL;
    char    *format;
    int     count_or_errorcode;
    unsigned    buflen	= 1024;
    stream_type stream	= STREAM_NONE;

    program_name = argv[0];
    while (*program_name != '\0') program_name++;
    while (program_name > argv[0] && *(program_name -1) != '/') program_name--;

    while (argc > 2)
    {
	char *p = argv[1];
	char *optarg = argv[2];
	
	if (*p == '-')
	{
	    switch (*(++p))
	    {
	    case 'f':
		function_name = optarg;
		break;

	    case 'n':
	    {
		char *endptr;
		buflen = (unsigned)strtoul(optarg, &endptr, 10);
		if (endptr <= optarg)
		{
		    fprintf(stderr, "%s: `%s', invalid argument to `-%c'.\n",
			    program_name, optarg, *p);
		    exit(EXIT_FAILURE);
		}
		break;
	    }

	    default:
		fprintf(stderr, "%s: `-%s', unknown flag.\n",
			program_name, p);
		exit(EXIT_FAILURE);
	    }
	}
	else
	{
	    /* start of arguments proper... */
	    break;
	}

	argc -=2; argv += 2;
    }
    
    /* We need at least a format string... */
    if (argc < 2)
    {
	fprintf(stderr, "%s: format string required.\n", program_name);
	exit(EXIT_FAILURE);
    }

    {
	/* Make sure the function name we have can be mapped to
	 * a pointer by the map table...
	 */
	API_map_element *element = map_name_to_func;

	while (element && element->name)
	{
	    if (strcmp(element->name, function_name) == 0)
	    {
		break;
	    }
	    element++;
	}

	/* ...and complain if it can't. */
	if (element->name == NULL)
	{
	    fprintf(stderr, "%s: `%s' is not a valid function name.\n",
		    program_name, function_name);
	    exit(EXIT_FAILURE);
	}

	/* ...save the matching function handle if we can. */
	function_handle = element->func;
	stream		= element->stream;
    }

    ++argv, --argc, format = argv[0];
    vArgs = (++argv, --argc < 1 ) ? NULL
	    : vector_canonicalize(argc, argv_dup(argc, argv));

    /* Call the appropriate function, with the correct args based on
       the cli parameter settings. */
    switch (stream)
    {
    case STREAM_FD:		/* file descriptor */
	count_or_errorcode = (*function_handle)(STDOUT_FILENO, format, vArgs);
	break;
    case STREAM_STDOUT:		/* FILE stream */
	count_or_errorcode = (*function_handle)(format, vArgs);
	break;
    case STREAM_FILE:		/* FILE stream */
	count_or_errorcode = (*function_handle)(stdout, format, vArgs);
	break;
    case STREAM_UNLIMITED_BUFFER: /* don't use this, use one of the next two */
	buf = snv_new(char, buflen);
	count_or_errorcode = (*function_handle)(buf, format, vArgs);
	break;
    case STREAM_BUFFER:		/* this one prevents buffer overruns */
	buf = snv_new(char, buflen);
	count_or_errorcode = (*function_handle)(buf, buflen, format, vArgs);
	break;
    case STREAM_ALLOC:		/* this one allocates memory for you */
	buf = asprintfv(format, vArgs);
	count_or_errorcode = buf ? strlen(buf) : -1;
	break;
    case STREAM_NATIVE:		/* STREAM stream */
    {
	STREAM out;
	char *pbuf;
	pbuf = buf = snv_new(char, buflen);
	stream_init(&out, (stream_gpointer)&pbuf, buflen, NULL, bufputc);
	count_or_errorcode = (*function_handle)(&out, format, vArgs);
	break;
    }
    case STREAM_FILAMENT:	/* homebrew */
    {
	filament *fil = filnew(NULL, 0);
	count_or_errorcode = (*function_handle)(fil, format, vArgs);
	buf = fildelete(fil);	/* recycle and convert to char* all-in-one */
	break;
    }
    default:
	fprintf(stderr, "Internal error!\n");
	abort();
	break;
    }
    
    /* Pretty print the results. */
    if (buf)
    {
	int count = 0;
	while (count < count_or_errorcode)
	{
	    putchar((int)buf[count++]);
	}
	/* snv_delete(buf); SEGVs HPsUX =(O| */
    }
    printf("\n*** %s returned ", function_name);
    if (count_or_errorcode < 0)
    {
	printf("ERROR (%s).\n", /* FIXME: printfv_strerror(count_or_errorcode) */ "bummer");
    }
    else
    {
	printf("%d chars.\n", count_or_errorcode);
    }

    return EXIT_SUCCESS;
}

/* snprintfv-test.c ends here */
