/*
    c99tree - parse tree utility
    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 "tree_print.h"
#include "c99tree.h"
#include "genht/htsi.h"
#include "genht/hash.h"

/* use the true versions in this file so that the original locations are inherited */
#undef Nalloc
#undef Nalloc_wloc
#undef Nalloc_tok
#undef Nclone
#undef Nop
#undef Nop_tok

const char *node_type_names[] = {
"CALL", "STATEMENT", "TYPE", "DECL", "FUNCDEF", "ARGLIST", "ARG",
"BLOCK", "CLASS", "TYPEOF", "ATTRLIST", "ATTR", "PTR", "INDEX",
"QUALIFIER", "ID", "ELLIPSIS", "ENUM", "INIT", "STRUCT", "BITS", "ASM",
"LABEL", "CONDITION", "THEN", "ELSE", "OP", "ICONST",
"FCONST", "SCONST", "SCONSTLIST", "DEBUG", "UNION", "PRAGMA", "TYPECUSTOM",
"TYPESPEC", "TYPENAME", "TYPEDEF",  "EXPRLIST", "EXPRBRACKET",
"COMMENT", "INITLIST",  "GRAMMAR_TREE", "TOKEN_TREE", "TOKEN_STR",
"TOKEN_PPDIR", "GENTREX", "GENTREX_RESULT", "GENTREX_MAP", "SYMLINK", NULL};

htsi_t *node_type_hash = NULL;

node_type_t Ntype(const char *typenam)
{
	const htsi_entry_t *ent;
	ent = (const htsi_entry_t *)htsi_getentry(node_type_hash, (htsi_key_t)typenam);
	if (ent == NULL)
		return T_INVALID;
	return (node_type_t)ent->value;
}

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

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


void tree_init()
{
	int n;

	node_type_hash = htsi_alloc(hash, keyeq);

	for(n = 0; n < T_INVALID; n++)
		htsi_set(node_type_hash, (htsi_key_t)node_type_names[n], n);
}

void tree_uninit()
{
	htsi_free(node_type_hash);
}

void Nloc_cpy_subtree(node_t *dest, node_t *child)
{
	node_t *n;
	int m;

	for(n = child; n != NULL; n = n->next) {

		m = 0;
		if (n->loc.soffs >= 0) {
			if ((dest->loc.soffs < 0) || (n->loc.soffs < dest->loc.soffs)) {
				Nloc_cpy_start(dest, n);
			 m = 1;
			}
		}

		if (n->loc.eoffs > dest->loc.eoffs) {
			Nloc_cpy_end(dest, n);
			m = 1;
		}
		
		if (m)
			loc_cpy_misc(&dest->loc, &n->loc);

		if (n->children != NULL)
			Nloc_cpy_subtree(dest, n->children);
	}
}

static int node_uid_counter = 0;
node_t *Nalloc_wloc(node_type_t type, char *name, node_t *child, const loc_t *start, const loc_t *end YLINEARG)
{
	node_t *n;

	n = malloc(sizeof(node_t));
	n->type = type;
	n->uid  = ++node_uid_counter;
	n->name = name;
	n->next = NULL;
	n->parent = NULL;
	n->children = child;
	n->redefinable = 0;

	if ((start == NULL) || (end == NULL)) {
		/* if either start or end is uncertain, use current location and child */
		loc_cpy(&(n->loc), loc_current);
		if (child != NULL) {
			n->loc.eoffs = -1;
			Nloc_cpy_subtree(n, child);
		}
	}

	/* copy end first, so start overrides the misc section */
	if (end != NULL) {
		loc_cpy_misc(&n->loc, end);
		loc_cpy_end(&n->loc, end);
	}

	if (start != NULL) {
		loc_cpy_misc(&n->loc, start);
		loc_cpy_start(&n->loc, start);
	}

#ifdef PARSER_NALLOC_DEBUG
	n->loc.yline = yline;
#endif

	return n;
}

node_t *Nalloc(node_type_t type, char *name, node_t *child YLINEARG)
{
	return Nalloc_wloc(type, name, child, NULL, NULL YLINEPARM);
}

node_t *Nalloc_tok(node_type_t type, strloc_t *token, node_t *child YLINEARG)
{
	node_t *n;

	n = malloc(sizeof(node_t));
	n->type = type;
	n->uid  = ++node_uid_counter;
	n->name = token->str;
	n->next = NULL;
	n->parent = NULL;
	n->children = child;
	n->redefinable = 0;

	loc_cpy(&(n->loc), &(token->loc));

#ifdef PARSER_NALLOC_DEBUG
	n->loc.yline = yline;
#endif

	return n;
}

node_t *Nappend(node_t *dest, node_t *src)
{
	node_t *odest;

	odest = dest;

	for(; dest->next != NULL; dest = dest->next) ;
	dest->next = src;

	return odest;
}

static void Nprint_(void **nprint_ctx, const char *prefix, node_t *node, int local_only)
{
	char *pr2;
	node_t *n;
	int l, r;

	if (!local_only || node->loc.is_local)
		r = Nprint_cb(nprint_ctx,prefix, node, PS_OPENING);
	else
		r = 0;

	if ((r == 0) && (node->children != NULL)) {
		l = strlen(prefix);
		pr2 = malloc(l + 2);
		*pr2 = *prefix;
		memcpy(pr2+1, prefix, l+1);
		for(n = node->children; n != NULL; n = n->next)
			Nprint_(nprint_ctx, pr2, n, local_only);
		free(pr2);
	}

	if (!local_only || node->loc.is_local)
		Nprint_cb(nprint_ctx, prefix, node, PS_CLOSING);
}

void Nprint(void **nprint_ctx, const char *prefix, node_t *node, int local_only)
{
	node_t *n;
	for(n = node; n != NULL; n = n->next)
		Nprint_(nprint_ctx, prefix, n, local_only);
}

node_t *Nop(const char *op, node_t *o1, node_t *o2, loc_t *start, loc_t *end YLINEARG)
{
	node_t *res;

	res = Nalloc_wloc(T_OP, strdup(op), o1, start, end YLINEPARM);
	if (o2 != NULL)
		Nappend(o1, o2);
	return res;
}

node_t *Nop_tok(const strloc_t *op, node_t *o1, node_t *o2 YLINEARG)
{
	node_t *res;

	res = Nalloc_wloc(T_OP, strdup(op->str), o1, &op->loc, &op->loc YLINEPARM);
	if (o2 != NULL)
		Nappend(o1, o2);
	return res;
}

node_t *Nclone(const node_t *node, invalidate_loc_t invalidate_loc YLINEARG)
{
	node_t **prev, *t, *ret;
	const node_t *n;

	if (node == NULL)
		return NULL;

	prev = &ret;
	for(n = node; n != NULL; n = n->next) {
		int cpy;
		*prev = t = Nalloc(n->type, n->name == NULL ? NULL : strdup(n->name), Nclone(n->children, invalidate_loc YLINEPARM) YLINEPARM);

		switch(invalidate_loc) {
			case INV_LOC_ALL:        cpy = 0; break;
			case INV_LOC_NONE:       cpy = 1; break;
			case INV_LOC_ALL_BUT_ID: cpy = t->type != T_ID; break;
		}
		if (cpy)
			loc_cpy(&t->loc, &loc_invalid);
		prev = &(t->next);
	}
	*prev = NULL;
	return ret;
}

void Nfree(node_t *node)
{
	node_t *next;
	node_t *n;

	for(n = node; n != NULL; n = next) {
		if (n->children != NULL)
			Nfree(n->children);
		next = n->next;
		if (n->name != NULL)
			free(n->name);
		free(n);
	}
}

node_t *Nlast(node_t *node)
{
	while(node->next != NULL) node = node->next;
	return node;
}
