/*
 * 'sparse' library helper routines.
 *
 * Copyright (C) 2003 Transmeta Corp.
 *               2003-2004 Linus Torvalds
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>

#include <sys/types.h>

#include "lib.h"
#include "allocate.h"
#include "token.h"
#include "parse.h"
#include "symbol.h"
#include "expression.h"
#include "evaluate.h"
#include "scope.h"
#include "linearize.h"
#include "target.h"
#include "machine.h"
#include "bits.h"

static int prettify(const char **fnamep)
{
	const char *name = *fnamep;
	int len = strlen(name);

	if (len > 2 && !memcmp(name, "./", 2)) {
		name += 2;
		len -= 2;
	}

	*fnamep = name;
	return len;
}

static const char *show_include_chain(int stream, const char *base)
{
	static char buffer[200];
	int len = 0;

	while ((stream = stream_prev(stream)) >= 0) {
		const char *p = stream_name(stream);
		int pretty_len;

		if (p == base)
			break;

		pretty_len = prettify(&p);
		if (pretty_len <= 0)
			break;

		/*
		 * At worst, we'll need " (through %s, ...)" in addition to the
		 * new filename
		 */
		if (pretty_len + len + 20 > sizeof(buffer)) {
			if (!len)
				return "";
			memcpy(buffer+len, ", ...", 5);
			len += 5;
			break;
		}

		if (!len) {
			memcpy(buffer, " (through ", 10);
			len = 10;
		} else {
			buffer[len++] = ',';
			buffer[len++] = ' ';
		}

		memcpy(buffer+len, p, pretty_len);
		len += pretty_len;
	}
	if (!len)
		return "";

	buffer[len] = ')';
	buffer[len+1] = 0;
	return buffer;
}

static const char *show_stream_name(struct position pos)
{
	const char *name = stream_name(pos.stream);
	static const char *last;

	if (name == base_filename)
		return name;
	if (name == last)
		return name;
	last = name;

	fprintf(stderr, "%s: note: in included file%s:\n",
		base_filename,
		show_include_chain(pos.stream, base_filename));
	return name;
}

static void do_warn(const char *type, struct position pos, const char * fmt, va_list args)
{
	static char buffer[512];

	/* Shut up warnings if position is bad_token.pos */
	if (pos.type == TOKEN_BAD)
		return;

	vsprintf(buffer, fmt, args);	

	fflush(stdout);
	fprintf(stderr, "%s:%d:%d: %s%s%s\n",
		show_stream_name(pos), pos.line, pos.pos,
		diag_prefix, type, buffer);
}

static int show_info = 1;

void info(struct position pos, const char * fmt, ...)
{
	va_list args;

	if (!show_info)
		return;
	va_start(args, fmt);
	do_warn("", pos, fmt, args);
	va_end(args);
}

static void do_error(struct position pos, const char * fmt, va_list args)
{
	static int errors = 0;
        die_if_error = 1;
	show_info = 1;
	/* Shut up warnings if position is bad_token.pos */
	if (pos.type == TOKEN_BAD)
		return;
	/* Shut up warnings after an error */
	has_error |= ERROR_CURR_PHASE;
	if (errors > fmax_errors) {
		static int once = 0;
		show_info = 0;
		if (once)
			return;
		fmt = "too many errors";
		once = 1;
	}

	do_warn("error: ", pos, fmt, args);
	errors++;
}	

void warning(struct position pos, const char * fmt, ...)
{
	va_list args;

	if (Wsparse_error) {
		va_start(args, fmt);
		do_error(pos, fmt, args);
		va_end(args);
		return;
	}

	if (!fmax_warnings || has_error) {
		show_info = 0;
		return;
	}

	if (!--fmax_warnings) {
		show_info = 0;
		fmt = "too many warnings";
	}

	va_start(args, fmt);
	do_warn("warning: ", pos, fmt, args);
	va_end(args);
}

void sparse_error(struct position pos, const char * fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	do_error(pos, fmt, args);
	va_end(args);
}

void expression_error(struct expression *expr, const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	do_error(expr->pos, fmt, args);
	va_end(args);
	expr->ctype = &bad_ctype;
}

NORETURN_ATTR
void error_die(struct position pos, const char * fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	do_warn("error: ", pos, fmt, args);
	va_end(args);
	exit(1);
}

NORETURN_ATTR
void die(const char *fmt, ...)
{
	va_list args;
	static char buffer[512];

	va_start(args, fmt);
	vsnprintf(buffer, sizeof(buffer), fmt, args);
	va_end(args);

	fprintf(stderr, "%s%s\n", diag_prefix, buffer);
	exit(1);
}

////////////////////////////////////////////////////////////////////////////////

static struct token *pre_buffer_begin = NULL;
static struct token **pre_buffer_next = &pre_buffer_begin;

void add_pre_buffer(const char *fmt, ...)
{
	va_list args;
	unsigned int size;
	struct token *begin, *end;
	char buffer[4096];

	va_start(args, fmt);
	size = vsnprintf(buffer, sizeof(buffer), fmt, args);
	va_end(args);
	begin = tokenize_buffer(buffer, size, &end);
	*pre_buffer_next = begin;
	pre_buffer_next = &end->next;
}

static void create_builtin_stream(void)
{
	// Temporary hack
	add_pre_buffer("#define _Pragma(x)\n");

	/* add the multiarch include directories, if any */
	if (multiarch_dir && *multiarch_dir) {
		add_pre_buffer("#add_system \"/usr/include/%s\"\n", multiarch_dir);
		add_pre_buffer("#add_system \"/usr/local/include/%s\"\n", multiarch_dir);
	}

	/* We add compiler headers path here because we have to parse
	 * the arguments to get it, falling back to default. */
	add_pre_buffer("#add_system \"%s/include\"\n", gcc_base_dir);
	add_pre_buffer("#add_system \"%s/include-fixed\"\n", gcc_base_dir);

	add_pre_buffer("#define __builtin_stdarg_start(a,b) ((a) = (__builtin_va_list)(&(b)))\n");
	add_pre_buffer("#define __builtin_va_start(a,b) ((a) = (__builtin_va_list)(&(b)))\n");
	add_pre_buffer("#define __builtin_ms_va_start(a,b) ((a) = (__builtin_ms_va_list)(&(b)))\n");
	add_pre_buffer("#define __builtin_va_arg(arg,type)  ({ type __va_arg_ret = *(type *)(arg); arg += sizeof(type); __va_arg_ret; })\n");
	add_pre_buffer("#define __builtin_va_alist (*(void *)0)\n");
	add_pre_buffer("#define __builtin_va_arg_incr(x) ((x) + 1)\n");
	add_pre_buffer("#define __builtin_va_copy(dest, src) ({ dest = src; (void)0; })\n");
	add_pre_buffer("#define __builtin_ms_va_copy(dest, src) ({ dest = src; (void)0; })\n");
	add_pre_buffer("#define __builtin_va_end(arg)\n");
	add_pre_buffer("#define __builtin_ms_va_end(arg)\n");
	add_pre_buffer("#define __builtin_va_arg_pack()\n");
}

static struct symbol_list *sparse_tokenstream(struct token *token)
{
	int builtin = token && !token->pos.stream;

	// Preprocess the stream
	token = preprocess(token);

	if (dump_macro_defs || dump_macros_only) {
		if (!builtin)
			dump_macro_definitions();
		if (dump_macros_only)
			return NULL;
	}

	if (preprocess_only) {
		while (!eof_token(token)) {
			int prec = 1;
			struct token *next = token->next;
			const char *separator = "";
			if (next->pos.whitespace)
				separator = " ";
			if (next->pos.newline) {
				separator = "\n\t\t\t\t\t";
				prec = next->pos.pos;
				if (prec > 4)
					prec = 4;
			}
			printf("%s%.*s", show_token(token), prec, separator);
			token = next;
		}
		putchar('\n');

		return NULL;
	}

	// Parse the resulting C code
	while (!eof_token(token))
		token = external_declaration(token, &translation_unit_used_list, NULL);
	return translation_unit_used_list;
}

static struct symbol_list *sparse_file(const char *filename)
{
	int fd;
	struct token *token;

	if (strcmp(filename, "-") == 0) {
		fd = 0;
	} else {
		fd = open(filename, O_RDONLY);
		if (fd < 0)
			die("No such file: %s", filename);
	}
	base_filename = filename;

	// Tokenize the input stream
	token = tokenize(NULL, filename, fd, NULL, includepath);
	close(fd);

	return sparse_tokenstream(token);
}

/*
 * This handles the "-include" directive etc: we're in global
 * scope, and all types/macros etc will affect all the following
 * files.
 *
 * NOTE NOTE NOTE! "#undef" of anything in this stage will
 * affect all subsequent files too, i.e. we can have non-local
 * behaviour between files!
 */
static struct symbol_list *sparse_initial(void)
{
	int i;

	// Prepend any "include" file to the stream.
	// We're in global scope, it will affect all files!
	for (i = 0; i < cmdline_include_nr; i++)
		add_pre_buffer("#argv_include \"%s\"\n", cmdline_include[i]);

	return sparse_tokenstream(pre_buffer_begin);
}

struct symbol_list *sparse_initialize(int argc, char **argv, struct string_list **filelist)
{
	char **args;
	struct symbol_list *list;

	base_filename = "command-line";

	// Initialize symbol stream first, so that we can add defines etc
	init_symbols();

	// initialize the default target to the native 'machine'
	target_config(MACH_NATIVE);

	args = argv;
	for (;;) {
		char *arg = *++args;
		if (!arg)
			break;

		if (arg[0] == '-' && arg[1]) {
			args = handle_switch(arg+1, args);
			continue;
		}
		add_ptr_list(filelist, arg);
	}
	handle_switch_finalize();

	// Redirect stdout if needed
	if (dump_macro_defs || preprocess_only)
		do_output = 1;
	if (do_output && outfile && strcmp(outfile, "-")) {
		if (!freopen(outfile, "w", stdout))
			die("error: cannot open %s: %s", outfile, strerror(errno));
	}

	if (fdump_ir == 0)
		fdump_ir = PASS_FINAL;

	list = NULL;
	if (filelist) {
		// Initialize type system
		target_init();
		init_ctype();

		predefined_macros();
		create_builtin_stream();
		init_builtins(0);

		list = sparse_initial();

		/*
		 * Protect the initial token allocations, since
		 * they need to survive all the others
		 */
		protect_token_alloc();
	}
	/*
	 * Evaluate the complete symbol list
	 * Note: This is not needed for normal cases.
	 *	 These symbols should only be predefined defines and
	 *	 declaratons which will be evaluated later, when needed.
	 *	 This is also the case when a file is directly included via
	 *	 '-include <file>' on the command line *AND* the file only
	 *	 contains defines, declarations and inline definitions.
	 *	 However, in the rare cases where the given file should
	 *	 contain some definitions, these will never be evaluated
	 *	 and thus won't be able to be linearized correctly.
	 *	 Hence the evaluate_symbol_list() here under.
	 */
	evaluate_symbol_list(list);
	return list;
}

struct symbol_list * sparse_keep_tokens(char *filename)
{
	struct symbol_list *res;

	/* Clear previous symbol list */
	translation_unit_used_list = NULL;

	new_file_scope();
	res = sparse_file(filename);

	/* And return it */
	return res;
}


struct symbol_list * __sparse(char *filename)
{
	struct symbol_list *res;

	res = sparse_keep_tokens(filename);

	/* Drop the tokens for this file after parsing */
	clear_token_alloc();

	/* And return it */
	return res;
}

struct symbol_list * sparse(char *filename)
{
	struct symbol_list *res = __sparse(filename);

	if (has_error & ERROR_CURR_PHASE)
		has_error = ERROR_PREV_PHASE;
	/* Evaluate the complete symbol list */
	evaluate_symbol_list(res);

	return res;
}
