#include "global.hh"

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include "config.hh"
#include "main.hh"
#include "menus.hh"
#include "style.hh"
#include "misc.hh"
#include "commands.hh"
#include "bindings.hh"
#include "pager.hh"

//********************************************************************

ConfigParser::ConfigParser ()
{
	fid = NULL;
	first_fpos = NULL;
	dir = NULL;
	filename = NULL;
	mode = 0;
}

int
ConfigParser::error (const char *tpl, ...)
{
	va_list ap;

	fprintf (stderr, "%s: %d: ", filename, line_num);
	va_start (ap, tpl);
	vfprintf (stderr, tpl, ap);
	va_end (ap);
	fprintf (stderr, "%s\n", line);
}

//********************************************************************
int
ConfigParser::get_filename (char *&name)
{
	char *rname;
	stripcpy (rname);

	// expand tildas to $HOME
	int hl = strlen (home_dir);
	int tc = 0;
	char *i;
	for (i=rname; *i; i++)
		if (*i == '~') tc++;

	name = new char [strlen (rname) + tc*(hl-1) + 1];
	// hl-1 : every tilda (one char) will be replaced by hl chars,
	// additional space needed is hl-1 chars

	int j, k;
	for (j=0, k=0; rname[j]; j++)
		if (rname[j] == '~') {
			memcpy (name+k, home_dir, hl);
			k += hl;
		}
		else {
			name[k] = rname[j];
			k++;
		};

	name[k] = 0;
	delete rname;

	// add a working directory if neccessary
	if (dir && name[0] != '/')
	{
		char *nname = new char[strlen(name) + strlen(dir) + 2];
		// +2 : one for slash, one for ending null
		sprintf (nname, "%s/%s", dir, name);
		delete name;
		name = nname;
	}
}

int
ConfigParser::fpos_push ()
{
	FPos *t = new FPos;
	fgetpos (fid, &t->fpos);

	t->next = first_fpos;
	first_fpos = t;
}

int
ConfigParser::fpos_pop ()
{
	if (!first_fpos)
		return 0;

	fsetpos (fid, &first_fpos->fpos);
	FPos *t = first_fpos;
	first_fpos = first_fpos->next;
	delete t;
}

int
ConfigParser::parse (char *ifilename, int quiet)
{
	line_num = 0;

	// a little trick ...
	p = ifilename;
	get_filename (filename);
	//  filename = ifilename;

	fid = fopen (filename, "r");
	if (!fid)
	{
		if (!quiet)
			fprintf (stderr, "swm: can't open file %s\n", filename);
		return 0;
	}

	// get the directory name
	int len = strlen (filename);
	int i, j;
	j = 0;
	for (i=0; i<len; i++)
	{
		if (filename[i] == '\\')
			i++;
		else if (filename[i] == '/')
			j = i;
	}

	dir = new char[j+1]; // one for ending null
	strncpy (dir, filename, j);
	dir[j] = 0;

	while (get_line ())
	{
		// order is important! if one keyword is the same as the initial
		// part of another one, the longer one must come first!

		// not handled properly: more than one assignment (with stripcpy)
		// (the old one remains undeleted); but it does not matter much

		/**/ if (match ("UnlockScrolling")) {
			dsk.lock = 0;
		}
		else if (match ("Parse"))
		{
			//#warning ***IMPROVE_ME***

			// stack of files; here push (line_num etc.); if getline == -1,
			// then try to pop back (return) to the parent file

			skip_space ();

			ConfigParser *cps;
			lps = cps = new ConfigParser;

			char *fp;
			get_filename (fp);
			cps->parse (fp);
			delete cps;
			delete fp;

			lps = this;
		}
		else if (match ("CenterPlacement")) swm_flags |= SWM_CENTER_PLACEMENT;
		else if (match ("ManualPlacement")) swm_flags &= ~SWM_CENTER_PLACEMENT;
		else if (match ("OpaqueMove")) swm_flags |= SWM_OPAQUE_MOVE;
		else if (match ("OpaqueResize")) swm_flags |= SWM_OPAQUE_RESIZE;
		else if (match ("GestureStickiness")) num (gest_stick);
		else if (match ("OriginStickiness")) num (origin_stick);
		else if (match ("BorderStickiness")) num (border_stick);
		else if (match ("Font")) swm_font.parse ();
		else if (match ("DoubleClick")) num(double_click_ms);
		else if (match ("desktop")) {
			/**/ if (match (".size")) {
				num (dsk.cols);
				skip (" x\t");
				num (dsk.rows);
			}
			else if (match (".start")) {
				num (dsk.x_start);
				num (dsk.y_start);							
			}
		}
		else if (match ("pointer.fore")) pointer_fore.parse ();
		else if (match ("pointer.back")) pointer_back.parse ();

		else if (match ("root.pixmap")) get_filename (root_xf);
		else if (match ("root.color")) root_mycolor.parse ();

		else if (match ("frame.pixmap")) get_filename (dwin_style.bkg_xf);
		else if (match ("frame.back")) dwin_style.frame.parse ();
		else if (match ("frame.title")) dwin_style.title.parse ();
		else if (match ("frame.border")) dwin_style.border.parse ();

		else if (match ("outline.color")) dwin_style.outline.parse ();
		else if (match ("outline.wd")) num (outline_wd);

		else if (match ("button.close.free")) get_filename (close_free_xf);
		else if (match ("button.close.hover")) get_filename (close_hover_xf);
		else if (match ("button.close.down")) get_filename (close_down_xf);
		else if (match ("button.mx.free")) get_filename (mx_free_xf);
		else if (match ("button.mx.hover")) get_filename (mx_hover_xf);
		else if (match ("button.mx.down")) get_filename (mx_down_xf);
		else if (match ("button.hide.free")) get_filename (hide_free_xf);
		else if (match ("button.hide.hover")) get_filename (hide_hover_xf);
		else if (match ("button.hide.down")) get_filename (hide_down_xf);
		else if (match ("button.winops.free")) get_filename (winops_free_xf);
		else if (match ("button.winops.hover")) get_filename (winops_hover_xf);
		else if (match ("button.winops.down")) get_filename (winops_down_xf);

		else if (match ("WindowStyles")) parse_window_styles ();
		else if (match ("Anim")) parse_anim_style ();
		else if (match ("Pager")) parse_pager_style ();
		else if (match ("Log")) parse_log_style ();
		else if (match ("Menu")) parse_menu_style ();
		else if (match ("NewMenu")) parse_menu ();
		else if (match ("Mouse")) bind_list.add (new MouseBind);
		else if (match ("Key")) bind_list.add (new KeyBind);
		else if (match ("Active")) {
			if (match (".click"))	active_list.add (new ActiveArea(AA_CLICK));
			else if (match (".hover"))	active_list.add (new ActiveArea(AA_HOVER));
		}
		else
			error ("bad command: ");
	};

	fclose (fid);
	return 1;
}

int
ConfigParser::get_line ()
{
	while (getline (&line, &line_size, fid) != -1)
	{
		line_num++;
		reset_line ();

		/**/ if (mode & CM_COMMENT) {
			if (match ("*/")) mode &= ~CM_COMMENT;
		}
		else if (mode & CM_SHELL) {
			if (match ("%%shell%%")) mode &= ~CM_SHELL;
			reset_line ();
			return 1;
		}
		else if (match ("%%shell%%")) {
			mode |= CM_SHELL;
			reset_line ();
			return 1;
		}
		else if (match ("/*")) {
			mode |= CM_COMMENT;
		}
		else if (eol () || *p == '#' || *p == ';') {
			;
			// line empty or commented out
		}
		else {
			// ordinary line
			return 1;
		}
	}
	
	// EOF or error
	return 0;
}
