%{
/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"
#include "rclex.h"

/* set a string option after freeing the old one */
#define SETSTR(opt, val) do {	\
	if ((opt)) free((opt));	\
	(opt) = (val);		\
} while (0)

/* used for the dgroup_t that decor are being added to */
static dgroup_t	*dgroup_current	= NULL;

/* used to build subparams */
#define			SUBP_STACK_GRAN		10
static int		subparams_stack_loc	= 0;
static int		subparams_stack_size	= 0;
static subparams_t	**subparams_stack	= NULL;

/* table for plugin names mapped to forplug files */
typedef struct forplug {
	char	*plugin;	/* name of the plugin */
	char	*incfile;	/* path to conditionaly parsed file */

	SLIST_ENTRY(forplug) fp_list;
} forplug_t;
static SLIST_HEAD(, forplug) forplug_tab = SLIST_HEAD_INITIALIZER(forplug_tab);

/* yacc error reporting */
static int yyerror(char *s) {
	warnx("%s, %d: %s", filename, yylineno, s);
	return 0;
}

/* add a param; for the plugin parameter parsing */
static int rcfile_addparam(char *name, char *value, subparams_t *subparams) {
	param_t *param;

	/* make the paramater, name->value pair */
	param = plugin_param(name, value);
	if (!param)
		goto failure;

	/* initialize subparams for the new parameter */
	param->subparams.count = subparams ? subparams->count : 0;
	param->subparams.params = subparams ? subparams->params : NULL;
	if (subparams)
		free(subparams);

	/* add the new parameter onto the current subparam */
	if (plugin_subparams_add(subparams_stack[subparams_stack_loc - 1],
			param) != 0)
		goto failure;

	return 0;

failure:
	if (!param) {
		free(name);
		free(value);
	} else
		plugin_param_free(param);
	yyerror("couldn't add plugin parameter");

	return -1;
}

/* add a plugin -> filename to the forplug table */
static forplug_t *rcfile_forplug(char *plugin, char *incfile) {
	forplug_t *fp_tab;

	/* add an entry to the table */
	fp_tab = malloc(sizeof(forplug_t));
	if (!fp_tab)
		return NULL;
	fp_tab->plugin = plugin;
	fp_tab->incfile = incfile;
	SLIST_INSERT_HEAD(&forplug_tab, fp_tab, fp_list);

	return fp_tab;
}

/* find a forplug entry for a plugin */
static forplug_t *rcfile_find_forplug(char *plugin) {
	forplug_t *fp_tab;

	SLIST_FOREACH(fp_tab, &forplug_tab, fp_list)
		if (strcmp(plugin, fp_tab->plugin) == 0)
			break;

	return fp_tab;
}

/* make a filename using filedir for relative paths */
static char *rcfile_filename(char *name) {
	if (name[0] != '/') {
		char *tmp = malloc(strlen(name) + strlen(filedir) + 1);
		if (!tmp)
			return NULL;
		snprintf(tmp, strlen(name) + strlen(filedir) + 1,
			"%s%s", filedir, name);
		return tmp;
	} else
		return strdup(name);
}

%}
%union {
	double		flnum;		/* floating point values */
	int		num;		/* numeric values */
	char		*str;		/* string values */
	void		*vptr;		/* some void * */
	subparams_t	*subparams;	/* subparameters for a plugin */
}

%token <num> OPTIONS KEYS PIXMAPS DECOR STYLE PLUGDAT FORPLUG
%token <num> OPAQUEMOVE MOUSEMOD ANIMDELAY DESKTOPCNT DESKTOPWID DESKTOPHEI
%token <num> FULLSCRZOOM LINEWIDTH LINEFG WANTMITSHM WSPACESLIDE RELRESIZE
%token <num> FOCUSNEW XINCORRECT
%token <num> FOCUS FOCUSTYPE PLACEMENT PLACETYPE PLACENONZEROS PLACETRANS
%token <num> TITLEFONT TITLE TITLECOLOR KEYNAME PLACEINTERACT INTERACTTIME
%token <num> DBUTTONLINES
%token <num> DECTYPE DECEDGE ACTION
%token <num> GROUPS DEFAULT TRANSIENT INTERNAL
%token <num> LOAD PARAM FPARAM PLUGPARAM

%token <num> INTEGER BOOLVAL MODIFIER NOMASK CYCLETYPE MVDIR
%token <str> STRING IDENT
%token <flnum> FLOAT

%type <num> modmask modmaskifiers
%type <vptr> keyarg decarg pixmapid groupid 
%type <str> filename
%type <flnum> floatval
%type <subparams> subparams

%%

rcfile		: /* empty */
		| rcfile error
		| rcfile OPTIONS '{' options_stmts '}'
		| rcfile KEYS '{' keys_stmts '}'
		| rcfile STYLE '{' style_stmts '}'
		| rcfile FORPLUG STRING filename ';' {
				if (rcfile_forplug($3, $4) == NULL) {
					free($3);
					free($4);
					yyerror("out of memory: couldn't make forplug entry");
					YYERROR;
				}
			}
		| rcfile LOAD {
				if (inside_forplug) {
					yyerror("plugins may not be loaded "
						"from a forplug include");
					YYERROR;
				}
			}
		  STRING subparams {
				plugin_t *plugin;
				forplug_t *forplug;
				
				forplug = rcfile_find_forplug($4);

				if ((plugin = plugin_load($4, $5, forplug ? 0 : 1)) == NULL) {
					if ($5)
						free($5);
					yyerror("unable to load plugin");
					YYERROR;
				}

				if (forplug)
					rclex_include(forplug->incfile, plugin);
				if ($5)
					free($5);
			}
		| rcfile PLUGDAT {
				if (!inside_forplug) {
					yyerror("plugdat section only valid "
						"in a forplug include");
					YYERROR;
				}
			}
		  subparams {
		  		plugin_subparams_merge(&inside_forplug->params, $4);
		  		if ($4)
		  			free($4);
		  	}
		;

options_stmts	: /* empty */
		| options_stmts options_stmt ';'
		;

keys_stmts	: /* empty */
		| keys_stmts keys_stmt ';'
		;

style_stmts	: /* empty */
		| style_stmts style_stmt ';'
		| style_stmts PIXMAPS '{' pixmaps_stmts '}'
		| style_stmts DECOR '{' decor_stmts '}'
		;

pixmaps_stmts	: /* empty */
		| pixmaps_stmts pixmaps_stmt ';'
		;

decor_stmts	: /* empty */
		| decor_stmts GROUPS '{' groups_stmts '}'
		| decor_stmts decor_stmt ';'
		;

groups_stmts	: /* empty */
		| groups_stmts groups_stmt ';'
		;

subparams	: ';'	{ $$ = NULL; }
		| '{'	{
				if (++subparams_stack_loc > subparams_stack_size) {
					subparams_t **tmp;

					subparams_stack_size += SUBP_STACK_GRAN;
					tmp = realloc(subparams_stack,
						subparams_stack_size * sizeof(subparams_t *));
					if (!tmp) {
						yyerror("not enough memory while"
							" parsing plugin parameters");
						YYERROR;
					}
					subparams_stack = tmp;
					
				}
				subparams_stack[subparams_stack_loc - 1] =
					calloc(1, sizeof(subparams_t));
			}
		  params '}' {
				$$ = subparams_stack[subparams_stack_loc - 1]; 
				subparams_stack[--subparams_stack_loc] = NULL;
			}
		;

params		: /* empty */
		| params FPARAM STRING filename subparams {
				if (rcfile_addparam($3, $4, $5) == -1)
					YYERROR;
			}
		| params PARAM STRING STRING subparams {
				if (rcfile_addparam($3, $4, $5) == -1)
					YYERROR;
			}
		;

options_stmt	: error
		| DESKTOPCNT	INTEGER		{ if ($2 > 0) options.desktop_count = $2; }
		| DESKTOPWID	INTEGER		{ if ($2 > 0) options.desktop_width = $2; }
		| DESKTOPHEI	INTEGER		{ if ($2 > 0) options.desktop_height = $2; }
		| FOCUS		FOCUSTYPE	{ options.focus = $2; }
		| FULLSCRZOOM	BOOLVAL		{ options.fullscreen_zoom = $2; }
		| OPAQUEMOVE	BOOLVAL		{ options.opaquemove = $2; }
		| FOCUSNEW	BOOLVAL		{ options.focus_new = $2; }
		| MOUSEMOD	modmask		{ options.mouse_modifier = $2; }
		| ANIMDELAY	INTEGER		{ options.anim_delay = $2; }
		| PLACETRANS	BOOLVAL		{ options.place_transients = $2; }
		| PLACENONZEROS	BOOLVAL		{ options.place_nonzeros = $2; }
		| PLACEINTERACT	BOOLVAL		{ options.place_interactive = $2; }
		| INTERACTTIME	INTEGER		{ options.interact_timeout = $2; }
		| PLACEMENT	PLACETYPE	{ options.placement = $2; }
		| LINEWIDTH	INTEGER		{ if ($2 > 0) options.linewidth = $2; }
		| LINEFG	STRING		{ SETSTR(options.linefgclr, $2); }
		| WANTMITSHM	BOOLVAL		{ options.wantmitshm = $2; }
		| WSPACESLIDE	BOOLVAL		{ options.workspace_slide = $2; }
		| RELRESIZE	BOOLVAL		{ options.relative_resize = $2; }
		| XINCORRECT	BOOLVAL		{ options.xinerama_correctloc = $2; }
		;

keys_stmt	: error
		| KEYNAME STRING modmask keyarg {
				int keycode;
				keycode = XKeysymToKeycode(display, XStringToKeysym($2));
				if (keycode == 0 && strncmp($2, "KeyCode", 7) == 0)
					keycode = atoi($2 + 7);
				if (keycode > 0)
					if (keys_add(keycode, $3, $1, (void *) $4) == NULL) {
						free($2);
						yyerror("couldn't allocate memory for keybind_t");
						YYERROR;
					}
				free($2);
			}
		;

style_stmt	: error
		| TITLEFONT	STRING	{ SETSTR(options.title_font, $2); } 
		| TITLECOLOR	STRING	{ SETSTR(options.titleclr, $2); }
		;

pixmaps_stmt	: error
		| IDENT filename {
				if (pixmap_ident($1) != NULL) {
					free($1);
					free($2);
					yyerror("duplicate pixmap identifier");
					YYERROR;
				}

				if (pixmap_add($1, $2) == NULL) {
					free($1);
					free($2);
					yyerror("couldn't allocate memory for pixmap");
					YYERROR;
				}
			}
		;

groups_stmt	: error
		| addgroup	decorlist	{ dgroup_current = NULL; }
		| DEFAULT	groupid		{ options.dgroup_default = $2; }
		| TRANSIENT	groupid		{ options.dgroup_trans = $2; }
		| INTERNAL	groupid		{ options.dgroup_internal = $2; }
		;

decor_stmt	: error
		| IDENT DECTYPE DECEDGE INTEGER INTEGER INTEGER floatval floatval
					BOOLVAL BOOLVAL ACTION ACTION ACTION
					pixmapid pixmapid decarg {
				decor_t *decor;

				if (decor_ident($1) != NULL) {
					free($1);
					yyerror("duplicate decoration identifier\n");
					YYERROR;
				}

				decor = calloc(1, sizeof(decor_t));
				if (!decor) {
					free($1);
					yyerror("couldn't allocate memory for decoration.");
					YYERROR;
				}
				decor->ident = $1;
				decor->type = $2;
				decor->edge = $3;
				decor->offset = $4;
				decor->lenmod = $5;
				decor->soffset = $6;
				decor->offsetmult = $7;
				decor->lenmult = $8;
				decor->flags.shaped = $9;
				decor->flags.button = $10;
				decor->lclick_action = $11;
				decor->mclick_action = $12;
				decor->rclick_action = $13;
				decor->pixmap = $14;
				decor->focpixmap = $15;
				decor->pressedmap = $16;
				decor_add(decor);
			}
		;

filename	: STRING {
				char *tmp = rcfile_filename($1);

				free($1);
				if (!tmp) {
					yyerror("couldn't get memory for filename");
					YYERROR;
				}

				$$ = tmp;
			}
		;

floatval	: INTEGER	{ $$ = (double) $1; }
		| FLOAT
		;

modmask		: '(' modmaskifiers ')'	{ $$ = $2; }
		| '(' NOMASK ')' { $$ = 0; }
		;

modmaskifiers	: MODIFIER
		| modmaskifiers '|' MODIFIER	{ $$ |= $3; }
		;

keyarg		: /* empty */		{ $$ = (void *) 0; }
		| STRING		{ $$ = (void *) $1; }
		| INTEGER		{ $$ = (void *) $1; }
		| MVDIR			{ $$ = (void *) $1; }
		| CYCLETYPE		{ $$ = (void *) $1; }
		| INTEGER ',' INTEGER {
				point_t *pt;

				pt = malloc(sizeof(point_t));
				if (!pt) {
					yyerror("couldn't get memory for keyarg, point_t");
					YYERROR;
				}

				pt->x = $1;
				pt->y = $3;
				$$ = (void *) pt;
			}
		;

decarg		: /* empty */		{ $$ = NULL; }
		| pixmapid
		;

addgroup	: IDENT {
				if (dgroup_ident($1) != NULL) {
					free($1);
					yyerror("duplicate decoration group identifier");
					YYERROR;
				}

				if ((dgroup_current = dgroup_add($1)) == NULL) {
					free($1);
					yyerror("couldn't allocate memory for decoration group");
					YYERROR;
				}
			}
		;

decorlist	: /* empty */
		| decorlist IDENT {
				decor_t *decor;

				decor = decor_ident($2);
				free($2);
				if (decor == NULL) {
					yyerror("undeclared decoration group identifier");
					YYERROR;
				}
				if (dgroup_add_decor(dgroup_current, decor) == -1)
					warnx("out of mem trying to add decor to dgroup");
			}
		| decorlist IDENT ':' TITLE ':' INTEGER ':' INTEGER {
				decor_t *decor;

				decor = decor_ident($2);
				free($2);
				if (decor == NULL) {
					yyerror("undeclared decoration group identifier");
					YYERROR;
				}
				if (dgroup_add_decor(dgroup_current, decor) == -1)
					warnx("out of mem trying to add decor to dgroup");

				/* now add to the title for the group */
				dgroup_current->title = decor;
				dgroup_current->title_x = $6;
				dgroup_current->title_y = $8;
			}
		;

groupid		: IDENT {
				dgroup_t *dgroup = dgroup_ident($1);
				free($1);
				if (dgroup == NULL) {
					yyerror("undeclared decoration group identifier");
					YYERROR;
				}
				$$ = dgroup;
			}
		;

pixmapid	: IDENT {
				pixmap_t *pixmap = pixmap_ident($1);
				free($1);
				if (pixmap == NULL) {
					yyerror("undeclared pixmap identifier");
					YYERROR;
				}
				$$ = pixmap;
			}
		;
		

%%

/* exported parsing function */
void rcfile_parse() {
	forplug_t *fp_tab, *next;
	char rcfn[MAXPATHLEN];
	FILE *fp;

	/*
	 * get space for filename/dir strings, and for the subparams
	 * stack.  the subparams stack is used for parsing of plugin
	 * subparams in the yacc grammar.
	 */
	filedir = malloc(MAXPATHLEN);
	if (!filedir)
		return;
	filename = malloc(MAXPATHLEN);
	if (!filename)
		goto free1;
	subparams_stack_size = SUBP_STACK_GRAN;
	subparams_stack = calloc(subparams_stack_size, sizeof(subparams_t *));
	if (!subparams_stack)
		goto free2;

	/* build file location strings */	
	snprintf(filedir, MAXPATHLEN, "%s/.golem/", homedir_path);
	snprintf(filename, MAXPATHLEN, "%s", "golemrc");
	snprintf(rcfn, sizeof(rcfn), "%s%s", filedir, filename);

	/*
	 * attempt to open the user's golemrc, if not, try for
	 * the system-wide rc file.
	 */
	fp = fopen(rcfn, "r");
	if (!fp) {
		snprintf(filedir, MAXPATHLEN, "%s/", PKG_SYSCONFDIR);
		snprintf(rcfn, sizeof(rcfn), "%s%s", filedir, filename);
		fp = fopen(rcfn, "r");
		if (!fp)
			goto free3;
		warnx("unable to read from ~/.golem/golemrc, using system-wide file");
	}

	/*
	 * parse the file, when the flexer reaches EOF for the
	 * file, it will call fclose() on it, so it is not
	 * necessary to call that here.
	 */
	yyin = fp;
	yyparse();

	/* free the forplug table */
	fp_tab = SLIST_FIRST(&forplug_tab);
	while (fp_tab) {
		next = SLIST_NEXT(fp_tab, fp_list);
		free(fp_tab->incfile);
		free(fp_tab->plugin);
		free(fp_tab);
		fp_tab = next;
	}
	SLIST_INIT(&forplug_tab);

free3:	
	free(subparams_stack);
	subparams_stack = NULL;
free2:	
	free(filename);
	filename = NULL;
free1:	
	free(filedir);
	filedir = NULL;
}
