/*
 * Copyright (c) 1990-2004 by Wayne C. Gramlich.
 * All rights reserved.
 */

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef HEAP_EXPORTS_H
#include "heap_exports.h"
#endif

#ifndef LIBC_EXPORTS_H
#include "libc_exports.h"
#endif

#ifndef LINT_H
#include "lint.h"
#endif

#ifndef STR_EXPORTS_H
#include "str_exports.h"
#endif

#ifndef STRVEC_EXPORTS_H
#include "strvec_exports.h"
#endif

#ifndef UNIX_ASSERT_H
#include "unix_assert.h"
#endif

#ifndef UNIX_STDIO_H
#include "unix_stdio.h"
#endif

#ifndef UNIX_STDLIB_H
#include "unix_stdlib.h"
#endif

#ifndef UNIX_SYS_STAT_H
#include "unix_sys_stat.h"
#endif

#ifndef VECTOR_DEFS_H
#include "vector_defs.h"
#endif

/* Lint is a lot happier if we don't #include "run_time_exports.h"! */
extern char	*run__time__enums;
extern char	*run__time__type_defs;
extern char	*run__time__struct_defs;

typedef struct Flags_struct *Flags;
struct Flags_struct {
	int		c_plus_plus;
	int		cross_compile;
	Str		compiler_name;
	int		debug_c;
	Vec(Str)	interface_files;
	Vec(Str)	libraries;
	Vec(Str)	object_files;
	Str		out_dir;
	Str		out_file_name;
	int		profile;
	Str		sub_dir;
	Vec(Str)	use_files;
	int		verbose;
};

LOCAL Flags	flags_parse(int, Str *, Heap);
LOCAL Str	init_file_create(Flags, Heap);

extern char *realpath(const char *, char *);
extern char *getcwd(char *, unsigned);

/*
 * main(argc, argv)
 *	 This routine will link the "argc" options in "argv".
 */
int
main(
	int		argc,
	Str		*argv)
{
	char		chr;
	Str		cmd;
	Strvec		command;
	Str		compiler;
	Flags		flags;
	Heap		heap;
	unsigned	index;
	Str		init_file_name;
	Str		libmath_name;
	Str		library;
	Str		library_path;
	Str		libstipple_name;
	Str		libutil_name;
	Str		object_file;
	Vec(Str)	object_files;
	Str		path;
	Str		path_list;
	unsigned	path_size;
	Str		program_name;
	unsigned	size;
	Str		strt0_name;
	Stat_rec	status;
	char		full_path_name[1024];	/* Should be MAXPATHLEN */

	program_name = argv[0];

	argc--;
	argv++;
	if (argc == 0) {
		(void)printf("Usage: stl -[options] object_files ...\n");
		return 0;
	}

	heap = heap_standard_create();
	flags = flags_parse(argc, argv, heap);
	(void)fflush(stdout);

	/* Figure out where stl came from: */
	path_list = getenv("PATH");
	assert(path_list != (Str)0);
	size = strlen(path_list);
	if (program_name[0] != '/') {
		path = (Str)heap_alloc(heap, size + strlen(program_name) + 1);
		path[0] = '\0';
		path_size = 0;
		for (index = 0; index < size; index++) {
			chr = path_list[index];
			if ((chr != ':') || (index + 1 == size)) {
				path[path_size] = chr;
				path_size += 1;
				path[path_size] = '\0';
			}
			if ((chr == ':') || (index + 1 == size)) {
				strcat(path, "/");
				strcat(path, program_name);
				if (stat(path, &status) == 0) {
					break;
				}
				path[0] = '\0';
				path_size = 0;
			}
		}
	} else {
		path = strdup(program_name);
	}
	full_path_name[0] = '\0';
	path = realpath(path, full_path_name);
	if (path == (Str)0) {
	    (void)fprintf(stderr,
	      "Could not convert `%s' into a real path\n", path);
	    assert(0);
	}
	size = strlen(full_path_name);
	for (index = size - 1; index != 0; index--) {
		chr = full_path_name[index];
		if (chr == '/') {
			full_path_name[index] = '\0';
			break;
		}
	}
	/* (void)printf("Exec directory: %s\n", full_path_name); */

	library_path = (Str)strdup(path);
	if (flags->cross_compile) {
	    Str last_slash = rindex(library_path, '/');
	    assert (last_slash != (Str)0);
	    (void)strcpy(last_slash, "/win-x86");
	}
	strt0_name = strprintf(heap,"%s/strt0.o", library_path);
	libstipple_name = strprintf(heap, "%s/libstipple.a", library_path);
	libutil_name = strprintf(heap, "%s/libutil.a", library_path);
	libmath_name = "-lm";

	/* Create and compile the initialization file: */
	init_file_name = init_file_create(flags, heap);

	command = strvec_create(heap);
	compiler = "<error>";
	if (flags->cross_compile) {
		compiler = flags->compiler_name;
	} else {
		if (flags->c_plus_plus) {
			compiler = "g++";
		} else {
			compiler = "gcc";
		}
	}
	strvec_print(command, compiler);
#ifdef SOLARIS
	strvec_print(command, " -Bstatic");
#endif /* SOLARIS */
	if (flags->debug_c) {
		strvec_print(command, " -g");
	}
	if (flags->out_file_name != (Str)0) {
		strvec_print(command, " -o %s", flags->out_file_name);
	}
	if (flags->profile) {
		strvec_print(command, " -xpg");
	}
	strvec_print(command, " %s", strt0_name);
	strvec_print(command, " %s", init_file_name);
	object_files = flags->object_files;
	VEC_LOOP(Str, object_files, object_file) {
		strvec_print(command, " %s", object_file);
	}
	strvec_print(command, " %s", libstipple_name);
	strvec_print(command, " %s", libutil_name);
	strvec_print(command, " %s", libmath_name);
#ifdef SOLARIS
	strvec_print(command, " -Bdynamic");
#endif /* SOLARIS */
#ifdef LINUX
	if (!flags->cross_compile) {
	    strvec_print(command, " -export-dynamic");
	}
#endif /* LINUX */
	VEC_LOOP(Str, flags->libraries, library) {
		strvec_print(command, " %s", library);
	}
#ifdef SOLARIS
	strvec_print(command, " -ldl -Bstatic");
#endif /* SOLARIS */
#ifdef LINUX
	if (!flags->cross_compile) {
	    strvec_print(command, " -ldl");
	    strvec_print(command, " -lc");
	}
#endif /* LINUX */
	cmd = strvec_str_get(command, heap);
	if (flags->verbose) {
		(void)printf("link command: ");
		(void)printf("%s\n", cmd);
	}
	(void)fflush(stdout);
	if (system(cmd) != 0) {
		error_fatal("Program did not link");
	}
	return 0;
}

/*
 * flags_parse(argc, argv, heap)
 *	This routine will parse the "argc" options in "argv" into a flags
 *	object allocated from "heap" and return it.
 */
LOCAL Flags
flags_parse(
	int		argc,
	Str		*argv,
	Heap		heap)
{
	Str		arg;
	int		index;
	Flags		flags;
	int		length;

	/* Allocate and initialize the flags object: */
	flags = heap_allocate(heap, Flags);
	flags->c_plus_plus = 0;
	flags->compiler_name = "cc";
	flags->cross_compile = 0;
	flags->debug_c = 1;
	flags->interface_files = vec_create(Str, heap);
	flags->libraries = vec_create(Str, heap);
	flags->object_files = vec_create(Str, heap);
	flags->out_dir = (Str)0;
	flags->out_file_name = (Str)0;
	flags->profile = 0;
	flags->use_files = vec_create(Str, heap);
	flags->verbose = 0;

	for (index = 0; index < argc; index++) {
		arg = argv[index];
		/* (void)printf("arg[%d]:'%s'\n", index, arg); */
		length = strlen(arg);
		if (arg[0] == '-') {
			switch (arg[1]) {
			    case 'C':
				flags->c_plus_plus = 1;
				break;
			    case 'd':
				vec_append(Str, flags->libraries, "-shared");
				break;
			    case 'E':
				index++;
				if (index == argc) {
					error_fatal("No directory after -O");
				}
				if (flags->out_dir != (Str)0) {
					error_fatal("Too many -E options");
				}
				flags->out_dir = argv[index];
				break;
			    case 'l':
			    case 'L':
				vec_append(Str, flags->libraries, arg);
				break;
			    case 'o':
				index++;
				if (index == argc) {
					error_fatal("No directory after -o");
				}
				if (flags->out_file_name != (Str)0) {
					error_fatal("Too many -o options");
				}
				flags->out_file_name = argv[index];
				break;
			    case 'G':
				flags->debug_c = 1;
				break;
			    case 's':
				vec_append(Str, flags->libraries, "-static");
				break;
			    case 'W':
				flags->cross_compile = 1;
				break;
			    case 'w':
				index++;
				if (index == argc) {
					error_fatal("No file name after -w");
				}
				flags->compiler_name = argv[index];
				break;
			    case 'v':
				flags->verbose = 1;
				break;
			    case 'z':
				flags->profile = 1;
				break;
			    default:
				error_fatal("Unknown option '%s'", arg);
			}
		} else if ((length >= 2) &&
		    (strncmp(arg + length - 2, ".o", 2) == 0)) {
			vec_append(Str, flags->object_files, arg);
		} else if ((length >= 4) &&
		    (strncmp(arg + length - 4, ".sti", 4) == 0)) {
			vec_append(Str, flags->interface_files, arg);
		} else if ((length >= 4) &&
		    (strncmp(arg + length - 4, ".stu", 4) == 0)) {
			vec_append(Str, flags->use_files, arg);
		} else {
			error_fatal("File with unrecongnized suffix '%s'\n",
				    arg);
		}
	}
	return flags;
}

/*
 * init_file_create(flags, heap)
 *	Will create an initialization file based on "flags".  The
 *	name of the resulting file is returned as a string allocated
 *	from "heap".
 */
LOCAL Str
init_file_create(
	Flags		flags,
	Heap		heap)
{
	Str		c_extern;
	Str		base_name;
	Str		out_dir;
	Stdio		out_file;
	Strvec		out_file_name;
	Str		out_name;
	Str		interface_file;
	Vec(Str)	interface_files;
	Str		module_name;
	Vec(Str)	module_names;
	Str		pointer;
	int		size;

	/* Extract all of the module names: */
	module_names = vec_create(Str, heap);
	interface_files = flags->interface_files;
	VEC_LOOP(Str, interface_files, interface_file) {
		pointer = strrchr(interface_file, '/');
		if (pointer == (Str)0) {
			pointer = interface_file;
		} else {
			pointer++;
		}
		module_name = strdupl(pointer, heap);
		pointer = strrchr(module_name, '.');
		assert(pointer != (Str)0);
		pointer[0] = '\0';
		vec_append(Str, module_names, module_name);
	}

	out_dir = (flags->out_dir == (Str)0) ? "/tmp" : flags->out_dir;
	if (out_dir[0] != '/') {
		char		cwd[2000];

		assert(getcwd(cwd, sizeof(cwd)) != (Str)0);
		out_dir = strprintf(heap, "%s/%s", cwd, out_dir);
		/* Total kludge! */
		if (strncmp(out_dir, "/tmp_mnt", 8) == 0) {
			out_dir += 8;
		}
	}
	out_file_name = strvec_create(heap);
	base_name = strrchr(flags->out_file_name, '/');
	if (base_name == (char *)0) {
		base_name = flags->out_file_name;
	} else {
		base_name += 1;
	}
	if (flags->c_plus_plus) {
		strvec_print(out_file_name,
			     "%s/%s__init.cpp", out_dir, base_name);
	} else {
		strvec_print(out_file_name,
			     "%s/%s__init.c", out_dir, base_name);
	}
	out_name = strvec_str_get(out_file_name, heap);

	out_file = fopen(out_name, "w");
	if (out_file == (Stdio)0) {
		error_fatal("Could not open %s for writing", out_name);
	}
	(void)fprintf(out_file, "%s", run__time__enums);
	(void)fprintf(out_file, "%s", run__time__type_defs);
	(void)fprintf(out_file, "typedef void *routine__info__type;\n");
	(void)fprintf(out_file, "%s", run__time__struct_defs);
	c_extern = "";
	(void)fflush(stdout);
	if (flags->c_plus_plus) {
		c_extern = " \"C\"";
		(void)fflush(stdout);
	}
	VEC_LOOP(Str, module_names, module_name) {
		(void)fprintf(out_file,
			"extern%s module___object %s__module__object;\n",
			c_extern, module_name);
		(void)fprintf(out_file,
			"extern%s void %s__module__initialize(void);\n",
			c_extern, module_name);
	}
	(void)fprintf(out_file,
		      "extern%s void program__modules__initialize(void);\n",
		      c_extern);
	(void)fprintf(out_file, "\n");
	size = vec_size(Str, module_names);
	(void)fprintf(out_file, "int module__objects__count = %d;\n", size);
	(void)fprintf(out_file,
		      "module___object *module___objects[%d] = {\n", size);
	VEC_LOOP(Str, module_names, module_name) {
		(void)fprintf(out_file,
			      "\t&%s__module__object,\n", module_name);
	}
	(void)fprintf(out_file, "};\n");
	(void)fprintf(out_file, "\n");
	(void)fprintf(out_file,
		      "char *module__coverage__file = \"%s: %s/%s.counts\";\n",
		      "STIPPLE_COVERAGE_FILE", out_dir, flags->out_file_name);
	(void)fprintf(out_file, "\n");
	(void)fprintf(out_file, "void program__modules__initialize(void)\n");
	(void)fprintf(out_file, "{\n");
	VEC_LOOP(Str, module_names, module_name) {
		(void)fprintf(out_file, "\t%s__module__initialize();\n",
			      module_name);
	}
	(void)fprintf(out_file, "}\n");
	(void)fclose(out_file);
	return out_name;
}
