/*
    c99tree - main
    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 <strings.h>
#include "tree.h"
#include "tree_print.h"
#include "tree_load.h"
#include "c99tree.h"
#include "revision.h"
#include "scope.h"
#include "gtx.h"

int verbose_options[VO_end];
const char *verbose_option_names[] = {
	"typedef",
	NULL
};

int preprocessed = 0;
int yydebug = 0;
char *filename = NULL;

/* --- error handling --- */
void werror(const char *s)
{
	fprintf(stderr, "werror: %s\n", s);
	exit(2);
}

void yyerror(const char *s)
{
	char *fl;

	if (loc_current->file != NULL)
		fl = loc_current->file->path;
	else
		fl = "<stdin>";

	fprintf(stderr, "yyerror: %s ('%s') at %s", s, yytext, fl);

	if (loc_current->sline == loc_current->eline)
		fprintf(stderr, ":%d %d-%d", loc_current->sline, loc_current->scol, loc_current->ecol-1);
	else
		fprintf(stderr, ":%d-%d", loc_current->sline, loc_current->eline);

	fprintf(stderr, "(byteoffs: %d-%d)\n", loc_current->soffs, loc_current->eoffs);

	exit(2);
}

/* --- command line argument handling --- */
void help(const char *prg, const char *topic)
{
	if (topic == NULL)
		topic = "";

	if (strcasecmp(topic, "format") == 0) {
		fprintf(stderr, "Available input formats:\n");
		fprintf(stderr, "  -tc         C source file (default)\n");
		fprintf(stderr, "  -tind       space indented tree\n");
#ifdef LOAD_XML
		fprintf(stderr, "  -txml       xml\n");
#endif
		fprintf(stderr, "\n");
		fprintf(stderr, "Available output formats:\n");
		fprintf(stderr, "  -Tind       space indented tree (default)\n");
		fprintf(stderr, "  -Tweb3      web3.0\n");
		fprintf(stderr, "  -Txml       xml (long style means separate closing tag)\n");
		fprintf(stderr, "  -Tplist     property list\n");
		fprintf(stderr, "  -Tjson      json\n");
		fprintf(stderr, "  -Tperl      perl\n");
		fprintf(stderr, "  -Tc         C source file\n");
		fprintf(stderr, "  -Tdot       digraph for dot (graphviz)\n");
		fprintf(stderr, "  -Tawk       awk script (function that builds the tree in an array)\n");

	}
	else if (strcasecmp(topic, "verbose") == 0) {
		fprintf(stderr, "Available verbose options (off by default, turned on by -v*):\n");
		fprintf(stderr, "  -vtypedef   expand typedef'd types when referenced\n");
		fprintf(stderr, "  -vall       turn on all of the above -v options\n");
	}
	else if (strcasecmp(topic, "location") == 0) {
		fprintf(stderr, "Location format is similar to the format string of printf(3),\n");
		fprintf(stderr, "with the following directives:\n");
		fprintf(stderr, "  %%f          file name (as referenced)\n");
		fprintf(stderr, "  %%p          path name (file name with path if looked up)\n");
		fprintf(stderr, "  %%l          start line of the token\n");
		fprintf(stderr, "  %%L          end line of the token\n");
		fprintf(stderr, "  %%c          start column of the token\n");
		fprintf(stderr, "  %%C          end column of the token\n");
		fprintf(stderr, "  %%e          macro expansion nesting level\n");
		fprintf(stderr, "  %%E          1 if line is local (not included)\n");
		fprintf(stderr, "  %%o          start byte offset of the token\n");
		fprintf(stderr, "  %%O          end byte offset of the token\n");
		fprintf(stderr, "  %%m          cpp #line macro location: file name\n");
		fprintf(stderr, "  %%M          cpp #line macro location: line\n");
		fprintf(stderr, "  %%y          cgram.y line number (requires compile time option)\n");
		fprintf(stderr, "  %%%%          a  plain %% character\n");

		fprintf(stderr, "\n");
		fprintf(stderr, "Any other character is copied as is.\n");
		fprintf(stderr, "For example, the default format is -L%s\n", loc_format);
	}
	else {
		fprintf(stderr, "c99tree - parse preprocessed C source file into a computer readable tree\n");
		fprintf(stderr, "Usage:\n");
		fprintf(stderr, " %s [-tformat] [-Tformat] [-ytypename] [options] [filename]\n", prg);
		fprintf(stderr, " %s --help [topic]\n", prg);
		fprintf(stderr, " %s -V\n\n", prg);
		fprintf(stderr, "  -tformat         input format (try --help format)\n");
		fprintf(stderr, "  -Tformat         output format (try --help format)\n");
		fprintf(stderr, "  -ytypename       register a new base type (multiple of this is allowed)\n");
		fprintf(stderr, "  -Ytypename       same as y, but make the new type replacable/redefinable\n");
		fprintf(stderr, "  -i               print nodes coming from include files\n");
		fprintf(stderr, "  -P               input has been preprocessed with c99tree-cpp\n");
		fprintf(stderr, "  -l               set output style 'long'\n");
		fprintf(stderr, "  -Lformat         change location format (see --help location)\n");
		fprintf(stderr, "  -d               debug bison parser\n");
		fprintf(stderr, "  -g gentrex_expr  run gentrex expression on the grammar tree after processing\n");
		fprintf(stderr, "  -v               set verbosity options (try --help verbose)\n");
		fprintf(stderr, "  filename         read input from filename instead of stdin\n");
		fprintf(stderr, "  --help           this help screen or help screen about topic\n");
		fprintf(stderr, "  -V               print svn revision and exit\n");

		if (!preprocessed) {
			fprintf(stderr, "\n");
			fprintf(stderr, "Note: c99tree-cpp shall be used for the preprocessing (for custom directives)\n");
		}
		fprintf(stderr, "\n");
	}

/*	fprintf(stderr, "\n(options marked with * are not fully functional)\n"); */
	exit(0);
}

static void output_format(const char *fmt)
{
	if (strcmp(fmt, "xml") == 0)
		Nprint_cb = Nprint_xml;
	else if (strcmp(fmt, "plist") == 0)
		Nprint_cb = Nprint_plist;
	else if (strcmp(fmt, "json") == 0)
		Nprint_cb = Nprint_json;
	else if (strcmp(fmt, "perl") == 0)
		Nprint_cb = Nprint_perl;
	else if (strcmp(fmt, "ind") == 0)
		Nprint_cb = Nprint_ind;
	else if (strcmp(fmt, "awk") == 0)
		Nprint_cb = Nprint_awk;
	else if (strcmp(fmt, "web3") == 0)
		Nprint_cb = Nprint_web3;
	else if (strcmp(fmt, "c") == 0)
		Nprint_cb = Nprint_c;
	else if (strcmp(fmt, "dot") == 0)
		Nprint_cb = Nprint_dot;
	else {
		fprintf(stderr, "c99tree error: unkown output format %s\n", fmt);
		exit(3);
	}
}

void set_verbose(const char *name)
{
	int n;

	if (strcmp(name, "all") == 0) {
		for(n = 0; n < VO_end; n++)
			verbose_options[n]++;
		return;
	}

	for(n = 0; n < VO_end; n++) {
		if (strcmp(name, verbose_option_names[n]) == 0) {
			verbose_options[n]++;
			return;
		}
	}

	fprintf(stderr, "Error: unknown verbose option name '%s'\n", name);
	exit(1);
}

int main(int argc, char *argv[])
{
	char *cmd;
	int n;
	node_t *root;
	node_t node_base_type;
	char *input_format = "c";
	int local_only = 1;

	gtx_init();

	memset(&node_base_type, 0, sizeof(node_t));
	node_base_type.type=T_TYPENAME;

	memset(verbose_options, 0, sizeof(verbose_options));
	loc_format = "%m:%M@%f:%l";
/*	loc_format = "%o:%O";*/
	tree_init();
	scope_enter(); /* master scope for global typedefs */

	for(n = 1; n < argc; n++) {
		cmd = argv[n];
		if (*cmd == '-') {
			while(*cmd == '-') cmd++;
			switch(*cmd) {
				case 'h': help(argv[0], argv[n+1]); break;
				case 'T': output_format(cmd+1); break;
				case 't': input_format = cmd+1; break;
				case 'd': yydebug = 1; break;
				case 'l': format_long = 1; break;
				case 'L': loc_format = cmd+1; break;
				case 'v': set_verbose(cmd+1); break;
				case 'V': printf("c99tree %s\n", svn_revision); return 0;
				case 'i': local_only = 0; break;
				case 'P': preprocessed = 1; break;
				case 'g': if (cmd[1] == 't') { n++; gtx_list_append(argv[n]); } break;
				case 'y':
				case 'Y':
					{
						node_t *n = Nclone(&node_base_type, INV_LOC_NONE);
						n->name = strdup(cmd+1);
						n->redefinable = (*cmd == 'Y');
						scope_add_typedef(cmd+1, n);
						break;
					}
			}
		}
		else {
			if (filename != NULL) {
				fprintf(stderr, "c99tree-cc error: multiple filenames on command line (%s, %s).\n", filename, cmd);
				return 1;
			}
			filename = cmd;
		}
	}

	/* parse the file */
	root = tree_load(filename, input_format);
	tree_postproc(root);

	gtx_run(root);

	/* print the result */
	if (root != NULL) {
		void *nprint_ctx = NULL;
		if (Nprint_cb(&nprint_ctx, "", root, PS_HEAD) == 0) {
			Nprint(&nprint_ctx, " ", root, local_only);
			Nprint_cb(&nprint_ctx, "", root, PS_TAIL);
		}
	}

	loc_file_close();
	if (loc_current != NULL)
		fprintf(stderr, "Internal error: location stack got confused. Handling of #includes is broken.\n");

	/* free up memory */
	tree_load_cleanup(input_format);

	Nfree(root);
	loc_destroy();
	tree_uninit();
	scope_leave();

	return 0;
}
