/*
 * The main module for SIP.
 *
 * Copyright (c) 2005
 * 	Riverbank Computing Limited <info@riverbankcomputing.co.uk>
 * 
 * This file is part of SIP.
 * 
 * This copy of SIP is licensed for use under the terms of the SIP License
 * Agreement.  See the file LICENSE for more details.
 * 
 * SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include "sip.h"


#ifndef PACKAGE
#define	PACKAGE	"sip"
#endif

#define	VERSION	"4.2.1 (4.2.1-297)"


/* Global variables - see sip.h for their meaning. */
char *sipVersion;
stringList *includeDirList;

static char *sipPackage = PACKAGE;
static int warnings = FALSE;


static void appendString(stringList **,char *);
static void help(void);
static void version(void);
static void usage(void);
static char parseopt(int,char **,char *,char **,int *,char **);
static int parseInt(char *,char);


int main(int argc,char **argv)
{
	char *filename, *docFile, *codeDir, *srcSuffix, *flagFile;
	char arg, *optarg, *buildFile, *apiFile;
	int optnr, exceptions, tracing, releaseGIL, parts;
	FILE *file;
	sipSpec spec;
	stringList *versions, *xfeatures;

	/* Initialise. */
	sipVersion = VERSION;
	includeDirList = NULL;
	versions = NULL;
	xfeatures = NULL;
	buildFile = NULL;
	codeDir = NULL;
	docFile = NULL;
	srcSuffix = NULL;
	flagFile = NULL;
	apiFile = NULL;
	exceptions = FALSE;
	tracing = FALSE;
	releaseGIL = FALSE;
	parts = 0;

	/* Parse the command line. */
	optnr = 1;

	while ((arg = parseopt(argc,argv,"hVa:b:ec:d:gI:j:rs:t:wx:z:",&flagFile,&optnr,&optarg)) != '\0')
		switch (arg)
		{
		case 'a':
			/* Where to generate the API file. */
			apiFile = optarg;
			break;

		case 'b':
			/* Generate a build file. */
			buildFile = optarg;
			break;

		case 'e':
			/* Enable exceptions. */
			exceptions = TRUE;
			break;

		case 'g':
			/* Always release the GIL. */
			releaseGIL = TRUE;
			break;

		case 'j':
			/* Generate the code in this number of parts. */
			parts = parseInt(optarg,'j');
			break;

		case 'z':
			/* Read a file for the next flags. */
			if (flagFile != NULL)
				fatal("The -z flag cannot be specified in an argument file\n");

			flagFile = optarg;
			break;

		case 'c':
			/* Where to generate the code. */
			codeDir = optarg;
			break;

		case 'd':
			/* Where to generate the documentation. */
			docFile = optarg;
			break;

		case 't':
			/* Which platform or version to generate code for. */
			appendString(&versions,optarg);
			break;

		case 'x':
			/* Which features are disabled. */
			appendString(&xfeatures,optarg);
			break;

		case 'I':
			/* Where to get included files from. */
			appendString(&includeDirList,optarg);
			break;

		case 'r':
			/* Enable tracing. */
			tracing = TRUE;
			break;

		case 's':
			/* The suffix to use for source files. */
			srcSuffix = optarg;
			break;

		case 'w':
			/* Enable warning messages. */
			warnings = TRUE;
			break;

		case 'h':
			/* Help message. */
			help();
			break;

		case 'V':
			/* Display the version number. */
			version();
			break;

		default:
			usage();
		}

	if (optnr < argc)
	{
		file = NULL;
		filename = argv[optnr++];

		if (optnr < argc)
			usage();
	}
	else
	{
		file = stdin;
		filename = "stdin";
	}

	if (releaseGIL && warnings)
		warning("The -g command line flag is deprecated\n");

	/* Parse the input file. */
	parse(&spec,file,filename,versions,xfeatures);

	/* Verify and transform the parse tree. */
	transform(&spec);

	/* Generate code. */
	generateCode(&spec,codeDir,buildFile,docFile,apiFile,srcSuffix,
		     exceptions,tracing,releaseGIL,parts,xfeatures);

	/* All done. */
	return 0;
}


/*
 * Parse the next command line argument - similar to UNIX getopts().  Allow a
 * flag to specify that a file contains further arguments.
 */
static char parseopt(int argc,char **argv,char *opts,char **flags,int *optnrp,
		     char **optargp)
{
	char arg, *op, *fname;
	int optnr;
	static FILE *fp = NULL;

	/* Deal with any file first. */

	fname = *flags;

	if (fname != NULL && fp == NULL && (fp = fopen(fname,"r")) == NULL)
		fatal("Unable to open %s\n",fname);

	if (fp != NULL)
	{
		char buf[200], *cp, *fname;
		int ch;

		fname = *flags;
		cp = buf;

		while ((ch = fgetc(fp)) != EOF)
		{
			/* Skip leading whitespace. */

			if (cp == buf && isspace(ch))
				continue;

			if (ch == '\n')
				break;

			if (cp == &buf[sizeof (buf) - 1])
				fatal("An flag in %s is too long\n",fname);

			*cp++ = (char)ch;
		}

		*cp = '\0';

		if (ch == EOF)
		{
			fclose(fp);
			fp = NULL;
			*flags = NULL;
		}

		/*
		 * Get the option character and any optional argument from the
		 * line.
		 */

		if (buf[0] != '\0')
		{
			if (buf[0] != '-' || buf[1] == '\0')
				fatal("An non-flag was given in %s\n",fname);

			arg = buf[1];

			/* Find any optional argument. */

			for (cp = &buf[2]; *cp != '\0'; ++cp)
				if (!isspace(*cp))
					break;

			if (*cp == '\0')
				cp = NULL;
			else
				cp = sipStrdup(cp);

			*optargp = cp;

			if ((op = strchr(opts,arg)) == NULL)
				fatal("An invalid flag was given in %s\n",fname);

			if (op[1] == ':' && cp == NULL)
				fatal("Missing flag argument in %s\n",fname);

			if (op[1] != ':' && cp != NULL)
				fatal("Unexpected flag argument in %s\n",fname);

			return arg;
		}
	}

	/* Check there is an argument and it is a switch. */

	optnr = *optnrp;

	if (optnr >= argc || argv[optnr] == NULL || argv[optnr][0] != '-')
		return '\0';

	/* Check it is a valid switch. */

	arg = argv[optnr][1];

	if (arg == '\0' || (op = strchr(opts,arg)) == NULL)
		usage();

	/* Check for the switch parameter, if any. */

	if (op[1] == ':')
	{
		if (argv[optnr][2] != '\0')
		{
			*optargp = &argv[optnr][2];
			++optnr;
		}
		else if (optnr + 1 >= argc || argv[optnr + 1] == NULL)
			usage();
		else
		{
			*optargp = argv[optnr + 1];
			optnr += 2;
		}
	}
	else if (argv[optnr][2] != '\0')
		usage();
	else
	{
		*optargp = NULL;
		++optnr;
	}

	*optnrp = optnr;

	return arg;
}


/*
 * Parse an integer option.
 */
static int parseInt(char *arg, char opt)
{
	char *endptr;
	int val;

	val = strtol(arg, &endptr, 10);

	if (*arg == '\0' || *endptr != '\0')
		fatal("Invalid integer argument for -%c flag\n", opt);

	return val;
}


/*
 * Append a string to a list of them.
 */
static void appendString(stringList **headp,char *s)
{
	stringList *sl;

	/* Create the new entry. */

	sl = sipMalloc(sizeof (stringList));

	sl -> s = s;
	sl -> next = NULL;

	/* Append it to the list. */

	while (*headp != NULL)
		headp = &(*headp) -> next;

	*headp = sl;
}


/*
 * Display a warning message.
 */
void warning(char *fmt,...)
{
	static int start = TRUE;

	va_list ap;

	if (!warnings)
		return;

	if (start)
	{
		fprintf(stderr,"%s: Warning: ",sipPackage);
		start = FALSE;
	}

	va_start(ap,fmt);
	vfprintf(stderr,fmt,ap);
	va_end(ap);

	if (strchr(fmt,'\n') != NULL)
		start = TRUE;
}


/*
 * Display all or part of a one line error message describing a fatal error.
 * If the message is complete (it has a newline) then the program exits.
 */
void fatal(char *fmt,...)
{
	static int start = TRUE;

	va_list ap;

	if (start)
	{
		fprintf(stderr,"%s: ",sipPackage);
		start = FALSE;
	}

	va_start(ap,fmt);
	vfprintf(stderr,fmt,ap);
	va_end(ap);

	if (strchr(fmt,'\n') != NULL)
		exit(1);
}


/*
 * Display the SIP version number on stdout and exit with zero exit status.
 */
static void version(void)
{
	printf("%s\n",sipVersion);
	exit(0);
}


/*
 * Display the help message on stdout and exit with zero exit status.
 */
static void help(void)
{
	printf(
"Usage:\n"
"    %s [-h] [-V] [-a file] [-c dir] [-d file] [-e] [-I dir] [-j #] [-m file] [-r] [-s suffix] [-t version] [-w] [-x feature] [-z file] [file]\n"
"where:\n"
"    -h          display this help message\n"
"    -V          display the %s version number\n"
"    -a file     the name of the Scintilla API file [default not generated]\n"
"    -b file     the name of the build file [default none generated]\n"
"    -c dir      the name of the code directory [default not generated]\n"
"    -d file     the name of the documentation file [default not generated]\n"
"    -e          enable support for exceptions [default disabled]\n"
"    -I dir      look in this directory when including files\n"
"    -j #        split the generated code into # files [default 1 per class]\n"
"    -r          generate code with tracing enabled [default disabled]\n"
"    -s suffix   the suffix to use for C or C++ source files [default \".c\" or \".cpp\"]\n"
"    -t tag      the version/platform to generate code for\n"
"    -w          enable warning messages\n"
"    -x feature  this feature is disabled\n"
"    -z file     the name of a file containing more command line flags\n"
"    file        the name of the specification file [default stdin]\n"
		,sipPackage,sipPackage);

	exit(0);
}


/*
 * Display the usage message.
 */
static void usage(void)
{
	fatal("Usage: %s [-h] [-V] [-a file] [-b file] [-c dir] [-d file] [-e] [-g] [-I dir] [-j #] [-r] [-s suffix] [-t tag] [-w] [-x feature] [-z file] [file]\n",sipPackage);
}
