#include "n_libc.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "misc.h"
#include "error.h"
#include "defs.h"
#include "archdefs.h"
#include "preprocess.h"

static struct include_dir	*includes_tail = NULL;

int	archflag;
int	abiflag;
int	nostdincflag;

static struct include_dir *
make_inc_dir(const char *path) {
	struct include_dir		*ret;
	static struct include_dir	nullinc;

	ret = n_xmalloc(sizeof *ret);
	*ret = nullinc;
	ret->path = (char *)path;
	return ret;
}	

static struct input_file *
tmp_in_file(FILE *fd, char *name) {
	static struct input_file	in;

	in.fd = fd;
	in.path = name;
	in.is_cmdline = 1;
	return &in;
}

static struct predef_macro *
tmp_pre_macro(const char *name, const char *text) {
	static struct predef_macro	pm;
	struct predef_macro		*ret = n_xmalloc(sizeof *ret);

	*ret = pm;
	ret->name = n_xstrdup(name);
	ret->text = n_xstrdup(text);
	return ret;
}

static void
usage(void) {
	puts("You invoked nwcpp incorrectly. Please refer to the README file");
	puts("for supported command line arguments");
	exit(EXIT_FAILURE);
}


/*
 * XXX The macros passed to the program are totally incomplete. In particular
 * there's usually only one operating system/architecture macro, such that
 * e.g. __amd64__ is defined but __amd64 isn't. This SUCKS!!
 */
int
main(int argc, char **argv) {
	struct ga_option	options[] = {
		{ 'D', NULL, 1 },
		{ 'I', NULL, 1 },
#ifdef _AIX
		{ 0, "maix64", 0 },
#endif
		{ 0, "arch", 1 },
		{ 0, "abi", 1 },
		{ 0, "mabi", 1 },
		{ 0, "nostdinc", 0 },
	};
	int			n_opts = sizeof options / sizeof options[0];
	int			n_predef = 0;
	int			ch;
	int			idx;
	int			rc = 0;
	int			have_gnu = 0;
	char			buf[1024];
	char			*arch_str = NULL;
	char			*abi_str = NULL;
	struct stat		sbuf;
	struct include_dir	*tmp_inc;
	struct predef_macro	*predef = NULL;
	struct predef_macro	*predef_tail = NULL;
	struct predef_macro	*tmp_pre;
	struct input_file	*files = NULL;
	struct input_file	*files_tail = NULL;
	struct input_file	*tmp_fil;
static struct predef_macro	nullpre;	
static struct input_file	nullinput;
	char			*p;
	FILE			*predef_file = NULL;

	while ((ch = nw_get_arg(argc, argv, options, n_opts, &idx)) != -1) {
		switch (ch) {
		case 'D':
			tmp_pre = n_xmalloc(sizeof *tmp_pre);
			*tmp_pre = nullpre;
			tmp_pre->name = n_xstrdup(n_optarg);
			if (strcmp(n_optarg, "__GNUC__") == 0) {
				have_gnu = 1;
			}	
			if ((p = strchr(tmp_pre->name, '=')) != NULL) {
				*p = 0;
				tmp_pre->text = ++p;
			}
			APPEND_LIST(predef, predef_tail, tmp_pre);
			break;
		case 'I':
			tmp_inc = make_inc_dir(n_xstrdup(n_optarg)); 
			APPEND_LIST(include_dirs, includes_tail, tmp_inc);
			break;
		case '!':	
			tmp_fil = n_xmalloc(sizeof *tmp_fil);
			*tmp_fil = nullinput;
			tmp_fil->path = n_xstrdup(n_optarg);
			if ((tmp_fil->fd = fopen(n_optarg, "r")) == NULL) {
				perror(n_optarg);
				return EXIT_FAILURE;
			}
			APPEND_LIST(files, files_tail, tmp_fil);
			break;
		case '?':
			if (idx) { 
				if (strcmp(options[idx].name, "arch") == 0) {
					arch_str = n_xstrdup(n_optarg);
				} else if (strcmp(options[idx].name, "abi")
					== 0
					|| strcmp(options[idx].name, "mabi")
					== 0) {
					abi_str = n_xstrdup(n_optarg);
				} else if (strcmp(options[idx].name, "nostdinc")
					== 0) {	
					nostdincflag = 1;
				} else if (strcmp(options[idx].name, "maix64")
					== 0) {
					abi_str = "aix64";
				} else {
					usage();
				}	
			} else {
				usage();
			}	
			break;
		default:
			usage();
		}
	}

	tmp_pre = tmp_pre_macro("__STDC__", "1");
	APPEND_LIST(predef, predef_tail, tmp_pre);
	if (!have_gnu) {
		/*
		 * __GNUC__=2 or higher requires us to define things like
		 * limits.h constants ourselves on Linux, which is
		 * undesirable at this point. Unfortunately the defective
		 * FreeBSD garbage headers cannot deal with older versions,
		 * so we set the version to 2.95 if we're not on Linux
		 */
#ifdef linux
		/* XXX hmm set __GNUC_MINOR__ to what!? */
		tmp_pre = tmp_pre_macro("__GNUC__", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
#else
		tmp_pre = tmp_pre_macro("__GNUC__", "2");
		APPEND_LIST(predef, predef_tail, tmp_pre);
		tmp_pre = tmp_pre_macro("__GNUC_MINOR__", "95");
		APPEND_LIST(predef, predef_tail, tmp_pre);
#endif
	}

	set_target_arch_and_abi(&archflag, &abiflag, arch_str, abi_str);

	if (arch_str != NULL) {
		/* Are we generating for the host? */
		int	hostarch;
		int	hostabi;

		get_host_arch(&hostarch, &hostabi);
		if (archflag == hostarch) {
			arch_str = NULL;
		}
	}

	/*
	 * If no target architecture has been specified,  we are generating
	 * for the host, and also assume the same target operating system:
	 * We then propagate some system-specific macros. Note that if you
	 * use -arch, but specify as target the same architecture as the
	 * host, these macros will not be propagated. This is obscure and
	 * probably bad. XXX figure out a better way to handle this!
	 */
	if (arch_str == NULL) {
		/* Propagate some system macros */
#ifdef __FreeBSD__
		sprintf(buf, "%d", __FreeBSD__);
		tmp_pre = tmp_pre_macro("__FreeBSD__", buf);
		APPEND_LIST(predef, predef_tail, tmp_pre);
#elif defined __OpenBSD__
		sprintf(buf, "%d", __OpenBSD__);
		tmp_pre = tmp_pre_macro("__OpenBSD__", buf);
		APPEND_LIST(predef, predef_tail, tmp_pre);
#elif defined linux 
		sprintf(buf, "%d", linux);
		tmp_pre = tmp_pre_macro("linux", buf);
		APPEND_LIST(predef, predef_tail, tmp_pre);
#elif defined __sgi
		tmp_pre = tmp_pre_macro("__sgi", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
		/*
		 * IRIX seems to need extremely freaking much information
		 * by the preprocessor. I started out by doing
		 *
		 * echo 'main() {}' | gcc -E -dM - >foo.c
		 * cat icode.c >>foo.c
		 *
		 * ... and then removed lots and lots of macros defined by
		 * GNU cpp. The result is the list of macros below. Some of
		 * the stuff I removed seems to be rather important - e.g.
	 	 * SVR4 and MIPS version definitions and such - but there's
		 * no way I'm going to define dozens and dozens of macros
		 * to make stuff work in a voodoo black magic kind of way.
		 *
		 * In other words, this is a preliminary list that I will 
		 * change as I see fit
		 *
		 * (Stuff like _MIPS_SZPTR/INT/LONG, _ABI*... should
		 * probably be passed by the compiler once we have support
		 * for more ABIs.)
		 */
		{
			int	i;
			static const struct {
				char	*name;
				char	*text;
			} sgi_voodoo[] = {
				{ "_LANGUAGE_C", "1" },
				{ "__EXTENSIONS__", "1" },
				{ "_COMPILER_VERSION", "601" },
				{ "__LANGUAGE_C", "1" },
				{ "__mips64", "1" },
				{ "__LANGUAGE_C__", "1" },
				{ "_MIPS_SZPTR", "32" },
				{ "__STDC_HOSTED__", "1" },
				{ "_MIPS_SZINT", "32" },
				{ "_LONGLONG", "1" },
				{ "_MIPS_SIM", "_ABIN32" },
				{ "_SGI_SOURCE", "1" },
				{ "_ABIN32", "2" },
				{ "LANGUAGE_C", "1" },
				{ "_LANGUAGE_C", "1" },
				{ "__MIPSEB__", "1" },
				{ "_MIPSEB", "1" },
				{ "__mips", "3" },
#if 0
				{ "_MIPS_SZLONG", "32" },
#endif
				{ "__R4000__", "1" },
				{ NULL, NULL }
			};
			for (i = 0; sgi_voodoo[i].name != NULL; ++i) {
				tmp_pre = tmp_pre_macro(sgi_voodoo[i].name,
					sgi_voodoo[i].text);
				APPEND_LIST(predef, predef_tail, tmp_pre);
			}
			tmp_pre = tmp_pre_macro("_MIPS_SZLONG",
				abiflag == ABI_MIPS_N32? "32": "64");
			APPEND_LIST(predef, predef_tail, tmp_pre);
		}
#elif defined _AIX
		tmp_pre = tmp_pre_macro("_AIX", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);

		/* Announce ``long long'' support to headers */
		tmp_pre = tmp_pre_macro("_LONG_LONG", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);

		/*
		 * Apparently leaving __STDC_VERSION__ unset, or
		 * defining _LONG_LONG, or somesuch - causes the
		 * AIX headers to assume C99 support, which does
		 * not quite work with nwcc yet
		 */ 
		tmp_pre = tmp_pre_macro("__STDC_VERSION__", "199409L");
		APPEND_LIST(predef, predef_tail, tmp_pre);
#endif

#ifdef __i386__
		tmp_pre = tmp_pre_macro("__i386__", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
#elif defined __amd64__
		tmp_pre = tmp_pre_macro("__amd64__", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
#endif
	} else {
		/*
		 * Host and target differ - only define architecture-
		 * specific macros (no OS stuff!)
		 */
		char	*arch_name = NULL;

		switch (archflag) {
		case ARCH_X86:
			arch_name = "__i386__";
			break;
		case ARCH_AMD64:
			arch_name = "__amd64__";
			break;
		case ARCH_POWER:
			arch_name = "_AIX";  /* XXX :-( */
			break;
		case ARCH_MIPS:
			arch_name = "__sgi"; /* XXX :-/ */
			break;
		default:
			unimpl();
		}
		tmp_pre = tmp_pre_macro(arch_name, "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
	}	
	if (abiflag == ABI_POWER64) {
		tmp_pre = tmp_pre_macro("__64BIT__", "1");
		APPEND_LIST(predef, predef_tail, tmp_pre);
	}

	if (stat("/usr/local/nwcc/include", &sbuf) == 0) {
		p = "/usr/local/nwcc/include";
	} else {
		static char	cwdbuf[2048];

		if (getcwd(cwdbuf, sizeof cwdbuf - sizeof "/include") == NULL) {
			perror("getcwd");
			return EXIT_FAILURE;
		}
		strcat(cwdbuf, "/include");
		if (stat(cwdbuf, &sbuf) == 0) {
			p = cwdbuf;
		} else if ((p = getenv("NWCC_CPP")) != NULL) {
			char	*p2;

			if ((p2 = strrchr(p, '/')) != NULL) {
				*cwdbuf = 0;
#define MIN__VALUE(x, y) \
	((x) > (y)? (y): (x))
				strncat(cwdbuf, p,
					MIN__VALUE(p2 - p,
						sizeof cwdbuf
							- sizeof "/include"));
				strcat(cwdbuf, "/include");
				p = cwdbuf;
			} else {
				p = NULL;
			}
		}	
	}	
	
	if (p == NULL) {
		(void) fprintf(stderr, "Warning: Cannot determine private "
			"include file directory");
	} else {
		tmp_inc = make_inc_dir(n_xstrdup(p));
		APPEND_LIST(include_dirs, includes_tail, tmp_inc);
	}	

	if (!nostdincflag) {
		/* Set default include directory (comes last, as it should!) */
		tmp_inc = make_inc_dir(n_xstrdup("/usr/include")); 
		APPEND_LIST(include_dirs, includes_tail, tmp_inc);
	}	


	if (predef != NULL) {
		/*
		 * It is possible - though probably rare - to have complex
		 * definitions on the command line, such as
		 * -Dfoo='do { puts("lol"); } while (0)'
		 * ... so the predefined macros are supplied in a file that
		 * can be processed just like normal macro defintions
		 */
		if ((predef_file = get_tmp_file("", buf, "def")) == NULL) {
			return EXIT_FAILURE;
		}

		while (predef != NULL) {
			struct predef_macro	*pm;

			if (fprintf(predef_file,
				predef->text? "#define %s %s\n"
					: "#define %s\n",
					predef->name, predef->text) < 0) {
				perror("fprintf");
				return EXIT_FAILURE;
			}
			pm = predef;
			predef = predef->next;
			free(pm->name);
			free(pm);
		}
		(void) fclose(predef_file);
		if ((predef_file = fopen(buf, "r")) == NULL) {
			perror(buf);
			return EXIT_FAILURE;
		}
		(void) unlink(buf);
	}

	init_oplookup();
	cross_initialize_type_map(archflag, abiflag);

	if (files == NULL) {
		static struct input_file	in;
		int	ch;

		in.path = "<stdin>";
		in.fd = get_tmp_file("stdin", buf, "c");
		if (in.fd == NULL) {
			return EXIT_FAILURE;
		}
		while ((ch = getchar()) != EOF) {
			if (fputc(ch, in.fd) < 0) {
				perror("fputc");
				(void) unlink(buf);
				return EXIT_FAILURE;
			}
		}	
		if (fclose(in.fd) == EOF) {
			perror("fclose");
			(void) unlink(buf);
			return EXIT_FAILURE;
		}	
		if ((in.fd = fopen(buf, "r")) == NULL) {
			perror(buf);
			return EXIT_FAILURE;
		}

		(void) unlink(buf);
		if (predef_file) {
			rc |= preprocess(
				tmp_in_file(predef_file, "predef"), stdout);
		}	
		rc |= preprocess(&in, stdout);
		return errors != 0; /* XXX - rc ? */
	} else {
		for (; files != NULL; files = files->next) {
			if (predef_file) {
				(void) rewind(predef_file);
				rc |= preprocess(
					tmp_in_file(predef_file, "predef"),
					stdout);
			}	
			rc |= preprocess(files, stdout);
		}
	}	
	return errors != 0? EXIT_FAILURE: 0; 
}



syntax highlighted by Code2HTML, v. 0.9.1