/*
    c99tree - source location handling
    Copyright (C) 2010  Tibor Palinkas

    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; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "tree.h"
#include "genht/htsp.h"
#include "genht/htss.h"
#include "genht/hash.h"

loc_t loc_invalid;

/* --- hash --- */
static int keyeq(char *a, char *b)
{
	return !strcmp(a, b);
}

static unsigned int hash(char *key)
{
	return murmurhash(key, strlen(key));
}

static htsp_t *loc_files = NULL;

static loc_file_t *loc_file_lookup(const char *fn, const char *path)
{
	loc_file_t *f;

	if (loc_files == NULL)
		loc_files = htsp_alloc(hash, keyeq);

	f = (loc_file_t *)htsp_get(loc_files, (htsp_key_t)path);
	if (f != NULL)
		return f;

	f = malloc(sizeof(loc_file_t));
	f->fn      = strdup(fn);
	f->path    = strdup(path);
	htsp_set(loc_files, (htsp_key_t)path, (htsp_value_t)f);
	return f;
}

void loc_files_destroy()
{
	htsp_entry_t *e;
	loc_file_t *f;

	if (loc_files == NULL)
		return;
	for (e = htsp_first(loc_files); e; e = htsp_next(loc_files, e)) {
		f = e->value;
		free(f->fn);
		free(f->path);
		free(e->value);
	}
	htsp_free(loc_files);
	loc_files = NULL;
}

static loc_t **stack = NULL;
static int alloced = 0, used = 0;
loc_t *loc_current = NULL;

void loc_file_open(const char *fn, const char *path)
{
	/* push */
	if (used == alloced) {
		alloced += 16;
		stack = realloc(stack, sizeof(loc_t) * alloced);
	}
	loc_current = stack[used] = malloc(sizeof(loc_t));
	if (fn == NULL)
		loc_current->file = NULL;
	else
		loc_current->file = loc_file_lookup(fn, path);
	loc_current->soffs = loc_current->eoffs = 0;
	loc_current->sline = loc_current->eline = 1;
	loc_current->scol  = loc_current->ecol  = 1;
	loc_current->offs = 0;
	loc_current->twin_parent = NULL;
	loc_current->twin_parent_uid = -1;
	loc_current->cpp_line = 1;
	if (fn == NULL)
		loc_current->cpp_file = NULL;
	else
		loc_current->cpp_file = loc_cpp_file_lookup(fn);
	loc_current->macro_level = 0;
	loc_current->is_local = (fn == NULL) || (used == 0);

	used++;
/*	fprintf(stderr, "LOC stack open %d %d %s\n", used, loc_current->is_local, fn);*/
}

void loc_file_close(void)
{
	used--;
	if (loc_current != NULL)
		free(loc_current);
	if (used > 0)
		loc_current = stack[used-1];
	else
		loc_current = NULL;
/*	fprintf(stderr, "LOC stack close %d %d\n", used, loc_current == NULL ? -1 : loc_current->is_local);*/
}

void loc_switch_file(const char *fn, const char *path)
{
	if (loc_current->file == NULL) {
		overwrite:;
		loc_file_close();
		loc_file_open(fn, path);
		loc_current->cpp_file = loc_current->file->fn;
		return;
	}

	if (strcmp(loc_current->file->fn, fn) == 0) {
		return;
	}

	if (strcmp(loc_current->file->fn, "<prinit>") == 0) {
		/* A line from <prinit> means the next line is coming from a preprocessor hack
		   Since <prinit> may contain #includes, we may be knee deep into an #include
		   recursion. Clean them up and replace (overwrite) the remaining single
		   level of location with <prinit>. */
		while(used > 1)
			loc_file_close();

		goto overwrite;
	}

	if ((used > 1) && (strcmp(stack[used-2]->file->fn, fn) == 0))
		loc_file_close();
	else
		loc_file_open(fn, path);

	loc_current->cpp_file = loc_current->file->fn;
}


const char *loc_update_end(const char *token)
{
	const char *s, *ps;
	int slen;

	if (loc_invalid.soffs != -1) {
		loc_invalid.soffs = loc_invalid.eoffs = -1;
		loc_invalid.sline = loc_invalid.eline = -1;
		loc_invalid.scol = loc_invalid.ecol = -1;
	}
	loc_invalid.is_local = loc_current->is_local;

/*	fprintf(stderr, "TOKEN: '%s'", token);*/

	if (loc_current->macro_level > 0) {
/*		fprintf(stderr, " [in_macro]\n");*/
		return token;
	}

	/* copy end to start */
	loc_current->soffs = loc_current->offs;
	loc_current->sline = loc_current->eline;
	loc_current->scol  = loc_current->ecol;

	/* update end counting lines */
	slen = strlen(token);
	loc_current->offs += slen;
	loc_current->eoffs = loc_current->soffs + slen - 1;

	for(ps = s = token; (s = strchr(s, '\n')) != NULL; s++) {
		loc_current->ecol = 0;
		loc_current->eline++;
		loc_current->cpp_line++;
		ps = s;
	}

	/* ps is the last partial line */
	loc_current->ecol += strlen(ps);
/*	fprintf(stderr, " %d\n", loc_current->cpp_line);*/
	return token;
}

void loc_cpy(loc_t *dst, const loc_t *src)
{
	if (src != NULL)
		memcpy(dst, src, sizeof(loc_t));
	else
		memset(dst, 0, sizeof(loc_t));
}

void loc_cpy_misc(loc_t *dst, const loc_t *src)
{
	dst->file = src->file;
	dst->offs = src->offs;
	dst->cpp_line = src->cpp_line;
	dst->cpp_file = src->cpp_file;
	dst->macro_level = src->macro_level;
	dst->is_local = src->is_local;
	dst->twin_parent = src->twin_parent;
}


void loc_cpy_start(loc_t *dst, const loc_t *src)
{
 dst->sline = src->sline;
 dst->scol = src->scol;
 dst->soffs = src->soffs;
}

void loc_cpy_end(loc_t *dst, const loc_t *src)
{
 dst->eline = src->eline;
 dst->ecol = src->ecol;
 dst->eoffs = src->eoffs;
/* dst->eoffs_rec = src->eoffs_rec; - this is calculated by tree postporcess */
}

void loc_set(loc_t *dst, const loc_t *start, const loc_t *end)
{
	if (start != NULL)
		loc_cpy_start(dst, start);
	if (end != NULL)
		loc_cpy_end(dst, end);
}

static int sappend(char *at, char *end, const char *what, int len)
{
	if (end - at < len)
		return -1;

	if (len == 1)
		*at = *what;
	else
		memcpy(at, what, len);

	return len;
}

static int iappend(char *at, char *end, int i)
{
	char buff[64];
	int l;

	l = sprintf(buff, "%d", i);
	return sappend(at, end, buff, l);
}

static char loc_buff[1024];
char *loc_format;
const char *loc_print(const node_t *node)
{
	char *s, *f, *end;
	int len;

	if (loc_format == NULL)
		return "";

	end = loc_buff + sizeof(loc_buff) - 1;
	s   = loc_buff;
	*s  = '\0';
	for(f = loc_format; *f != '\0'; f++) {
		switch(*f) {
			case '%':
				f++;
				switch(*f) {
					case 'f': /* %f -> file name */
						if ((node->loc.file == NULL) || (node->loc.file->fn == NULL))
							len = sappend(s, end, "?", 1);
						else
							len = sappend(s, end, node->loc.file->fn, strlen(node->loc.file->fn));
						break;
					case 'p': /* %p -> path name */
						if ((node->loc.file == NULL) || (node->loc.file->path == NULL))
							len = sappend(s, end, "?", 1);
						else
							len = sappend(s, end, node->loc.file->path, strlen(node->loc.file->path));
						break;
					case 'o': /* %o -> start offset */
						len = iappend(s, end, node->loc.soffs);
						break;
					case 'O': /* %O -> end offset */
						len = iappend(s, end, node->loc.eoffs);
						break;
					case 'l': /* %l -> start line */
						len = iappend(s, end, node->loc.sline);
						break;
					case 'L': /* %L -> end line */
						len = iappend(s, end, node->loc.eline);
						break;
					case 'c': /* %c -> start col */
						len = iappend(s, end, node->loc.scol);
						break;
					case 'C': /* %C -> end col */
						len = iappend(s, end, node->loc.ecol);
						break;
					case 'e': /* %e -> macro Expansion nesting level */
						len = iappend(s, end, node->loc.macro_level);
						break;
					case 'E': /* %E -> is local (is in target source, not included) */
						len = iappend(s, end, node->loc.is_local);
						break;
#ifdef PARSER_NALLOC_DEBUG
					case 'y': /* %y -> line in the .y */
						len = iappend(s, end, node->loc.yline);
						break;
#endif
					case 'm':
						if (node->loc.cpp_file != NULL)
							len = sappend(s, end, node->loc.cpp_file, strlen(node->loc.cpp_file));
						else
							len = sappend(s, end, "<none>", 6);
						break;
					case 'M':
						len = iappend(s, end, node->loc.cpp_line);
						break;
					case '%': /* %% -> % */
						len = sappend(s, end, f, 1);
						break;
					default: /* %* -> %* */
						len = sappend(s, end, f-1, 2);
				}
				break;
			default:
				len = sappend(s, end, f, 1);
		}
		if (len < 0)
			break;
		s += len;
	}
	*s = '\0';
	return loc_buff;
}

/* shift bytes from *in to *buff until character term found or buffer overrun */
static int sshift(const char **in, char term, char *buff, int bufflen)
{
	/* make sure we leave string empty if term is the first char */
	*buff = '\0';

	while(bufflen) {
		if (**in == term) {
			*buff = '\0';
			return 0;
		}

		/* end of string here means we didn't find term */
		if (**in == '\0') {
			fprintf(stderr, "Location shifting error: term not found\n");
			return 1;
		}

		*buff = **in;
		(*in)++;
		buff++;
		bufflen--;
	}

	fprintf(stderr, "Location shifting error: buff too short\n");
	return 1;
}

/* shift bytes from *in until character term found; convert result to integer in *i */
static int ishift(const char **in, char term, int *i)
{
	char buff[32];
	char *end;

	if (sshift(in, term, buff, sizeof(buff))) {
		fprintf(stderr, "(location shifting error while scanning an integer)\n");
		return 1;
	}

	*i = strtol(buff, &end, 0);
	if ((end == NULL) || (*end != '\0')) {
		fprintf(stderr, "Location shifting error: '%s' is not an integer\n", buff);
		return 1;
	}
	return 0;
}

/* shift a byte from *in and compare to expected */
static int cshift(const char **in, char expected)
{
	if (**in == expected) {
		(*in)++;
		return 0;
	}
	fprintf(stderr, "Location shifting error: expected %c, got %c\n", expected, **in);
	return 1;
}

static loc_t loc_scan_buff;
loc_t *loc_scan(const char *in)
{
	char *f;
	const char *i;
	char fn[512];
	char pn[512];

	*pn = *fn = '\0';
	i = in;
	loc_scan_buff.is_local = -1;
	loc_scan_buff.macro_level = -1;
	for(f = loc_format; *f != '\0'; f++) {
		switch(*f) {
			case '%':
				f++;
				switch(*f) {
					case 'f': /* %f -> file name */
						if (sshift(&i, f[1], fn, sizeof(fn)))
							return NULL;
						break;
					case 'p': /* %p -> path name */
						if (sshift(&i, f[1], pn, sizeof(pn)))
							return NULL;
						break;
					case 'o': /* %o -> start offset */
						if (ishift(&i, f[1], &(loc_scan_buff.soffs)))
							return NULL;
						break;
					case 'O': /* %O -> end offset */
						if (ishift(&i, f[1], &(loc_scan_buff.eoffs)))
							return NULL;
						break;
					case 'l': /* %l -> start line */
						if (ishift(&i, f[1], &(loc_scan_buff.sline)))
							return NULL;
						break;
					case 'L': /* %L -> end line */
						if (ishift(&i, f[1], &(loc_scan_buff.eline)))
							return NULL;
						break;
					case 'c': /* %c -> start col */
						if (ishift(&i, f[1], &(loc_scan_buff.scol)))
							return NULL;
						break;
					case 'C': /* %C -> end col */
						if (ishift(&i, f[1], &(loc_scan_buff.ecol)))
							return NULL;
						break;
					case 'e': /* %e -> macro Expansion nesting level */
						if (ishift(&i, f[1], &(loc_scan_buff.macro_level)))
							return NULL;
						break;
					case 'E': /* %E -> is local (is in target source, not included) */
						if (ishift(&i, f[1], &(loc_scan_buff.is_local)))
							return NULL;
						break;

					case 'y': /* %y -> line in the .y */
#ifdef PARSER_NALLOC_DEBUG
						if (ishift(&i, f[1], &(loc_scan_buff.yline)))
							return NULL;
#else
						{
							int ignore;
							if (ishift(&i, f[1], &ignore))
								return NULL;
						}
#endif
						break;
					case 'm':
						{
							char fn[1024];
							if (sshift(&i, f[1], fn, sizeof(fn)))
								return NULL;
							loc_scan_buff.cpp_file = loc_cpp_file_lookup(fn);
							break;
						}
					case 'M':
						if (ishift(&i, f[1], &(loc_scan_buff.cpp_line)))
							return NULL;
						break;
					case '%': /* %% -> % */
						if (cshift(&i, '%'))
							return NULL;
						break;
					default: /* %* -> %* */
						if (cshift(&i, '%'))
							return NULL;
						if (cshift(&i, *f))
							return NULL;
				}
				break;
			default:
				if (cshift(&i, *f))
					return NULL;
		}
	}
	loc_scan_buff.file = loc_file_lookup(fn, pn);
	return &loc_scan_buff;
}

htss_t *cpp_files = NULL;

const char *loc_cpp_file_lookup(const char *fn)
{
	const char *ret;

	if (cpp_files == NULL)
		cpp_files = htss_alloc(hash, keyeq);

	ret = htss_get(cpp_files, (char *)fn);
	if (ret == NULL) {
		ret = strdup(fn);
		htss_set(cpp_files, (char *)ret, (char *)ret);
	}
	return ret;
}


void loc_destroy()
{
	loc_files_destroy();
	free(stack);
	stack = NULL;
	alloced = used = 0;
}

