/* This file is part of cfpeek
   Copyright (C) 2011-2021 Sergey Poznyakoff

   Cfpeek 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 3, or (at your option)
   any later version.

   Cfpeek 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 cfpeek.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "cfpeek.h"
#include "grecs/locus.h"

int file_index;
int flags;
int quiet_option;
int reduce_option;
int sort_option;
unsigned max_matches;

char *script_file;
char *script_expr;
char *script_init_expr;
char *script_done_expr;

#define MODE_GLOB 0
#define MODE_LITERAL 1
int mode = MODE_GLOB;

enum locate_cmd {
	locate_undefined,
	locate_up,
	locate_down,
	locate_parent,
	locate_child,
	locate_sibling
};

struct locate_instr {
	enum locate_cmd cmd;
	union {
		char *s;
		int n;
	} v;
};

static struct grecs_list *locate_list;

static struct locate_instr *
new_locate_instr()
{
	struct locate_instr *p = grecs_zalloc(sizeof(*p));
	if (!locate_list)
		locate_list = grecs_list_create();
	grecs_list_append(locate_list, p);
	return p;
}

static struct grecs_node *
run_locate(struct grecs_node *node)
{
	int i;
	struct grecs_list_entry *ep;

	if (!locate_list)
		return node;
	for (ep = locate_list->head; ep; ep = ep->next) {
		struct locate_instr *instr = ep->data;

		switch (instr->cmd) {
		case locate_undefined:
			grecs_error(NULL, 0,
				    "%s:%d: internal error: "
				    "locate_instr not initialized",
				    __FILE__, __LINE__);
			exit(EX_SOFTWARE);
			
		case locate_up:
			for (i = 0; i < instr->v.n && node; i++)
				node = node->up;
			break;
			
		case locate_down:
			for (i = 0; i < instr->v.n && node; i++)
				node = node->down;
			break;
			
		case locate_parent:
			while (node && strcmp(node->ident, instr->v.s))
				node = node->up;
			break;
			
		case locate_child:
			node = grecs_find_node(node->down, instr->v.s);
			break;
			
		case locate_sibling:
			node = grecs_find_node(node->up->down, instr->v.s);
		}
	}
	return node;
}

#include "cmdline.h"

static void
_print_diag(grecs_locus_t const *locus, int err, int errcode, const char *msg)
{
	fflush(stdout);
	if (!locus)
		fprintf(stderr, "%s: ", program_name);
	if (locus) {
		YY_LOCATION_PRINT(stderr, *locus);
		fprintf(stderr, ": ");
	}
	if (!err)
		fprintf(stderr, "warning: ");
	fprintf(stderr, "%s", msg);
	if (errcode)
		fprintf(stderr, ": %s", strerror(errno));
	fputc('\n', stderr);
}

static int
node_ident_cmp(struct grecs_node const *a, struct grecs_node const *b)
{
	return strcmp(a->ident, b->ident);
}


struct literal_match_buf {
	const char *pattern;
	struct grecs_node *node;
};

static struct grecs_node *
literal_first_node(struct grecs_node *tree, const char *pattern, void **pptr)
{
	struct grecs_node *node = grecs_find_node(tree, pattern);
	if (node) {
		struct literal_match_buf *buf = grecs_malloc(sizeof(*buf));
		buf->pattern = pattern;
		buf->node = node;
		*pptr = buf;
	}
	return node;
}

static struct grecs_node *
literal_next_node(void *ptr)
{
	struct literal_match_buf *buf = ptr;
	if (buf->node)
		buf->node = grecs_find_node(buf->node->next, buf->pattern);
	return buf->node;
}

static void
literal_free(void *ptr)
{
	grecs_free(ptr);
}

static struct grecs_node *
glob_first_node(struct grecs_node *tree, const char *pattern, void **pptr)
{
	grecs_match_buf_t match_buf;
	struct grecs_node *node = grecs_match_first(tree, pattern, &match_buf);
	*pptr = match_buf;
	return node;
}

static struct grecs_node *
glob_next_node(void *ptr)
{
	grecs_match_buf_t match_buf = ptr;
	return grecs_match_next(match_buf);
}

static void
glob_free(void *ptr)
{
	grecs_match_buf_t match_buf = ptr;
	grecs_match_buf_free(match_buf);
}

struct cfpeek_engine {
	struct grecs_node *(*first_node)(struct grecs_node *, const char *,
					 void **);
	struct grecs_node *(*next_node)(void *);
	void (*free_buf)(void *);
};

struct cfpeek_engine cfpeek_engine[] = {
	{ glob_first_node, glob_next_node, glob_free },
	{ literal_first_node, literal_next_node, literal_free },
};


static void
cfpeek_print(struct grecs_node *node)
{
	if (script_run(node)) {
		grecs_print_node(node, flags, stdout);
		fputc('\n', stdout);
	}
}

static void
print_node(struct grecs_node *node)
{
	node = run_locate(node);
	if (!node)
		return;
	cfpeek_print(node);
}

static enum grecs_tree_recurse_res
eval_rec(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
{
	if (op == grecs_tree_recurse_set)
		script_run(node);
	return grecs_tree_recurse_ok;
}

static void
scan_tree(struct grecs_node *tree)
{
	if (script_file || script_expr) {
		grecs_tree_recurse(tree, eval_rec, NULL);
	} else
		cfpeek_print(tree);
}

int
main(int argc, char **argv)
{
	struct grecs_node *tree;
	struct cfpeek_engine *cfp;
	int rc = EX_OK;

	grecs_print_diag_fun = _print_diag;
	parse_options(argc, argv);
	
	argc -= file_index;
	argv += file_index;
	if (argc < 1) {
		grecs_error(NULL, 0, "not enough arguments");
		exit(EX_USAGE);
	}
	tree = grecs_parse(argv[0]);
	if (!tree)
		exit(EX_PARSE);
	if (cmdline_tree) {
		grecs_tree_join(tree, cmdline_tree);
		grecs_tree_free(cmdline_tree);
	}
	if (reduce_option)
		grecs_tree_reduce(tree, NULL, 0);
	if (sort_option)
		grecs_tree_sort(tree, node_ident_cmp);

	if (argc < 2) {
		flags |= GRECS_NODE_FLAG_DESCEND;
		scan_tree(tree);
		script_done();
		exit(EX_OK);
	}

	cfp = &cfpeek_engine[mode];
	while (--argc) {
		void *match_buf = NULL;
		char *arg = *++argv;
		struct grecs_node *node = cfp->first_node(tree, arg,
							  &match_buf);
		if (!node) {
			if (!quiet_option)
				grecs_error(NULL, 0, "%s: not found", arg);
			rc = EX_NOTFOUND;
		} else if (quiet_option <= 1) {
			int i = 0;
			do {
				print_node(node);
				if (max_matches && ++i == max_matches)
					break;
			} while (node = cfp->next_node(match_buf));
		}
		cfp->free_buf(match_buf);
	}
	script_done();
	exit(rc);
}
