/*
    c99tree - execute gentrex scripts on the tree
    Copyright (C) 2015  Tibor 'Igor2' 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 <ctype.h>
#include "tree.h"
#include "gtx.h"
#include "gentrex/src/helper_map_az.h"
#include "gentrex/src/helper_intop.h"

static gtx_list_t *gtx_head = NULL, *gtx_tail = NULL;
static gentrex_map_az_t map, map_init;
static gentrex_api_t api;

void *tree_child(void *user_ctx_, void *node_)
{
	node_t *node = node_;
	return node->children;
}

void *tree_parent(void *user_ctx_, void *node_)
{
	node_t *node = node_;

	return node->parent;
}

void *tree_next(void *user_ctx_, void *node_)
{
	node_t *node = node_;
	return node->next;
}

typedef enum node_field_e {
	NF_INVALID,
	NF_UID,
	NF_NAME,
	NF_TYPE,
	NF_LOC_FILE,
	NF_LOC_SLINE,
	NF_LOC_ELINE,
	NF_LOC_SCOL,
	NF_LOC_ECOL,
	NF_LOC_SOFFS,
	NF_LOC_EOFFS,
	NF_LOC_IS_LOCAL,
	NF_LOC_MACRO_LEVEL,
} node_field_t;

typedef struct op_cookie_s {
	node_field_t fld;
	int ival;
	intop_t iop;
} op_cookie_t;

void *op_cookie_alloc(void *user_tree_ctx, gentrex_op_t opt, const char *tagname, const char *str_or_pattern, const char *custom_operator)
{
	op_cookie_t *ck;
	int ival;
	node_field_t fld = NF_INVALID;

	ival = atoi(str_or_pattern);

	if (strcmp(tagname, "uid") == 0)
		fld = NF_UID;
	else if (strcmp(tagname, "name") == 0)
		fld = NF_NAME;
	else if (strcmp(tagname, "type") == 0) {
		fld = NF_TYPE;
		ival = Ntype(str_or_pattern);
	}
	else if (strncmp(tagname, "loc_", 4) == 0) {
		tagname += 4;
		if (strcmp(tagname, "file") == 0)
			fld = NF_LOC_FILE;
		else if (strcmp(tagname, "sline") == 0)
			fld = NF_LOC_SLINE;
		else if (strcmp(tagname, "eline") == 0)
			fld = NF_LOC_ELINE;
		else if (strcmp(tagname, "scol") == 0)
			fld = NF_LOC_SCOL;
		else if (strcmp(tagname, "ecol") == 0)
			fld = NF_LOC_SCOL;
		else if (strcmp(tagname, "soffs") == 0)
			fld = NF_LOC_SOFFS;
		else if (strcmp(tagname, "eoffs") == 0)
			fld = NF_LOC_EOFFS;
		else if (strcmp(tagname, "is_local") == 0)
			fld = NF_LOC_IS_LOCAL;
		else if (strcmp(tagname, "macro_level") == 0)
			fld = NF_LOC_MACRO_LEVEL;
	}
	
	if (fld == NF_INVALID) {
		fprintf(stderr, "Error: invalid gentrex tag (node field): '%s'\n", tagname);
		return NULL;
	}

	ck = malloc(sizeof(op_cookie_t));
	ck->fld = fld;
	ck->ival = ival;
	if (opt == GTX_OP_C_OP)
		ck->iop = intop_parse(custom_operator);

	return ck;
}

void op_cookie_free(void *user_tree_ctx, gentrex_op_t opt, const char *tagname, const char *str_or_pattern, void *op_cookie, const char *custom_operator)
{
	free(op_cookie);
}

int tag_eq(void *user_tree_ctx, void *node_, void *op_cookie, const char *tagname, const char *str)
{
	node_t *node = node_;
	op_cookie_t *ck = op_cookie;

	if (ck == NULL)
		return 0;

	switch(ck->fld) {
		case NF_UID:              return node->uid == ck->ival;
		case NF_NAME:             return !strcmp(node->name, str);
		case NF_LOC_FILE:         return !strcmp(node->loc.file->fn, str);
		case NF_LOC_SLINE:        return node->loc.sline == ck->ival;
		case NF_LOC_ELINE:        return node->loc.eline == ck->ival;
		case NF_LOC_SCOL:         return node->loc.scol == ck->ival;
		case NF_LOC_ECOL:         return node->loc.ecol == ck->ival;
		case NF_LOC_SOFFS:        return node->loc.soffs == ck->ival;
		case NF_LOC_EOFFS:        return node->loc.eoffs == ck->ival;
		case NF_LOC_IS_LOCAL:     return node->loc.is_local == ck->ival;
		case NF_LOC_MACRO_LEVEL:  return node->loc.macro_level == ck->ival;
		case NF_TYPE:             return node->type == ck->ival;
		case NF_INVALID:
			abort();
	}
	return 0;
}

int tag_c_op(void *user_tree_ctx, void *node_, void *op_cookie, const char *tagname, const char *str, const char *c_op)
{
	node_t *node = node_;
	op_cookie_t *ck = op_cookie;
	int ival;

	if (ck == NULL)
		return 0;

	switch(ck->fld) {
		case NF_NAME:
		case NF_LOC_FILE:
			ival = -1;
			break;
		case NF_UID:             ival = node->uid; break;
		case NF_LOC_SLINE:       ival = node->loc.sline; break;
		case NF_LOC_ELINE:       ival = node->loc.eline; break;
		case NF_LOC_SCOL:        ival = node->loc.scol; break;
		case NF_LOC_ECOL:        ival = node->loc.ecol; break;
		case NF_LOC_SOFFS:       ival = node->loc.soffs; break;
		case NF_LOC_EOFFS:       ival = node->loc.eoffs; break;
		case NF_LOC_IS_LOCAL:    ival = node->loc.is_local; break;
		case NF_LOC_MACRO_LEVEL: ival = node->loc.macro_level; break;
		case NF_TYPE:            ival = node->type; break;
		case NF_INVALID:
			abort();
	}

	intop_cmp(ival, ck->iop, ck->ival, return 0);

	return 0;
}

void gtx_init(void)
{
	memset(&api, 0, sizeof(api));
	api.user_map_ctx = &map;
	api.map_get = gentrex_map_az_get;
	api.map_set = gentrex_map_az_set;

	api.next   = tree_next;
	api.child  = tree_child;
	api.parent = tree_parent;

	api.op_cookie_alloc = op_cookie_alloc;
	api.op_cookie_free  = op_cookie_free;
	api.tag_eq    = tag_eq;
	api.tag_c_op  = tag_c_op;
}

void gtx_list_append(char *arg)
{
	gtx_list_t *l;
	const char *err;
	char *s, *script, *name;

	for(s = arg; *s != '\0' && (isalnum(*s) || (*s == '_')) ;s++) ;

	if (*s == '=') {
		name = arg;
		script = s+1;
		*s = '\0';
		if (*name == '\0')
			name = NULL;
	}
	else {
		name = NULL;
		script = arg;
	}

	l = malloc(sizeof(gtx_list_t));
	l->name = name;
	l->script = gentrex_alloc(&api, script, &err);
	if (l->script == NULL) {
		fprintf(stderr, "Error compiling gentrex script '%s': %s\n", script, err);
		exit(1);
	}
	l->next = NULL;

	if (gtx_tail != NULL) {
		gtx_tail->next = l;
		gtx_tail = l;
	}
	else
		gtx_head = gtx_tail = l;
}

void gtx_run(node_t *root)
{
	gtx_list_t *l;
	node_t *res_root;
	int n;

	if (gtx_head == NULL)
		return;

	api.user_tree_ctx = root;

	/* save the initial map for restoring in each iteration */
	memcpy(&map_init, &map, sizeof(map));

	res_root = Nalloc(T_GENTREX, NULL, NULL);
	root->next->next = res_root;

	for(l = gtx_head, n = 0; l != NULL; l = l->next, n++) {
		node_t *scr;
		const char *name;
		char name_buff[64];
		
		name = l->name;
		if (name == NULL) {
			name = name_buff;
			sprintf(name_buff, "gtx_%d", n);
		}
		scr = Nalloc(T_GENTREX_RESULT, strdup(name), NULL);
		for(;;) {
			node_t *res, *lnk;
			char uids[32];
			int m;

			/* restore the initial map for each subsequent query */
			memcpy(&map, &map_init, sizeof(map));

			res = gentrex_execute(l->script, root);
			if (res == NULL)
				break;

			sprintf(uids, "%d", res->uid);
			lnk = Nalloc(T_SYMLINK, strdup(uids), NULL);
			Nchappend(scr, lnk);
			lnk->parent = scr;

			for(m = 0; m < 26; m++) {
				if (map.map[m].straight != NULL) {
					char name[64];
					node_t *mnode;

					sprintf(name, "%c=%d", m+'a', ((node_t *)map.map[m].straight)->uid);
					mnode = Nalloc(T_SYMLINK, strdup(name), NULL);
					Nchappend(lnk, mnode);
					mnode->parent = lnk;
				}
			}

		}
		gentrex_free(l->script);

		scr->parent = res_root;
		Nchappend(res_root, scr);
	}
}

