/*
 * Copyright (c) 2003 Benedikt Meurer (benedikt.meurer@unix-ag.uni-siegen.de)
 *               2004 Jean-François Wauthy (pollux@xfce.org)
 *
 * 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 Library 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /* !HAVE_CONFIG_H */

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>

#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <glib.h>
#include <libxfce4util/libxfce4util.h>

#include "option.h"
#include "xfprintsettings.h"

#ifndef HAVE_STRLCPY
#define strlcpy		g_strlcpy
#endif

/* list of supported papersizes  */
const XfprintOption papersizes[] = {
	{ "10x14", "10x14" },
	{ "DIN A3", "A3" },
	{ "DIN A4", "A4" },
	{ "DIN A4 (Deskjet)", "A4dj" },
	{ "DIN A5", "A5" },
	{ "DIN B4", "B4" },
	{ "DIN B5", "B5" },
	{ "Executive", "Executive" },
	{ "Folio", "Folio" },
	{ "Ledger", "Ledger" },
	{ "Legal", "Legal" },
	{ "Letter", "Letter" },
	{ "Letter (Deskjet)", "Letterdj" },
	{ "Quatro", "Quatro" },
	{ "Statement", "Statement" },
	{ "Tabloid", "Tabloid" },
	{ NULL, NULL }
};

/* list of supported encodings */
const XfprintOption encodings[] = {
	{ "US-ASCII", "ascii" },
	{ "HP", "hp" },
	{ "IBM Codepage 437", "ibm-cp437" },
	{ "IBM Codepage 850", "ibm-cp850" },
	{ "ISO-8859-1", "iso1" },
	{ "ISO-8859-2", "iso2" },
	{ "ISO-8859-3", "iso3" },
	{ "ISO-8859-4", "iso4" },
	{ "ISO-8859-5", "iso5" },
	{ "ISO-8859-7", "iso7" },
	{ "ISO-8859-9", "iso9" },
	{ "ISO-8859-10", "iso10" },
	{ "ISO-8859-13", "iso13" },
	{ "ISO-8859-15", "iso15" },
	{ "KOI8", "koi8" },
	{ "MAC", "mac" },
	{ "MS Codepage 1250", "ms-cp1250" },
	{ NULL, NULL }
};

/* list of supported style sheets */
const XfprintOption languages[] = {
	{ "Autoselect", "__auto__" },
	{ "Ada", "ada" },
	{ "Autoconf", "autoconf" },
	{ "AWK", "awk" },
	{ "B", "b" },
	{ "bc", "bc" },
	{ "C", "c" },
	{ "C shell", "csh" },
	{ "Fortran", "fortran" },
	{ "GNU Make", "gmake" },
	{ "HTML", "html" },
	{ "IDL", "idl" },
	{ "Java", "java" },
	{ "JavaScript", "js" },
	{ "Lex", "lex" },
	{ "Make", "make" },
	{ "Modula 2", "modula2" },
	{ "Modula 3", "modula3" },
	{ "Perl", "perl" },
	{ "Python", "python" },
	{ "Sather", "sather" },
	{ "Tex", "tex" },
	{ "Z shell", "zsh" },
	{ NULL, NULL }
};

/* list of supported non-printable format handlings */
const XfprintOption non_printable_fmts[] = {
	{ "Octal", "octal" },
	{ "Hexadecimal", "hexa" },
	{ "Emacs", "emacs" },
	{ "Questionmark", "Questionmark" },
	{ "Left blank", "blank" },
	{ "Caret", "caret" },
	{ NULL, NULL }
};

/* list of supported highlight levels */
const XfprintOption hilevels[] = {
	{ "None", "none" },
	{ "Normal", "normal" },
	{ "Heavy", "heavy" },
	{ NULL, NULL }
};

static void load_settings_from_file(const gchar *, XfprintSettings *);
static void save_settings_to_file(const gchar *, const XfprintSettings *);

void
xfprintsettings_save(const XfprintSettings *settings)
{
	gchar *path;

	g_return_if_fail(settings != NULL);

	path = xfce_resource_save_location (XFCE_RESOURCE_CONFIG,
					    "xfce4/printsettings.xml", TRUE);
	save_settings_to_file(path, settings);
	g_free(path);
}

XfprintSettings *
xfprintsettings_defaults(void)
{
	XfprintSettings *settings;

	settings = g_new0(XfprintSettings, 1);

	/* set default values */
	settings->sheets.landscape = FALSE;
	settings->sheets.fillcols = TRUE;
	settings->sheets.cols = 1;
	settings->sheets.rows = 1;
#ifdef DEFAULT_LETTER
	settings->sheets.papersize = "Letter";
#else
	settings->sheets.papersize = "A4dj";
#endif
	settings->sheets.borders = FALSE;
	settings->sheets.reverse = FALSE;
	settings->vpages.linenumbers = 0;
	settings->vpages.lpp = 0;
	settings->vpages.cpl = 0;
	settings->vpages.tabsize = 8;
	settings->vpages.nonprtfmt = "caret";
	settings->pprint.language = "__auto__";
	settings->pprint.highlight = "normal";
	settings->pprint.strip = 0;
	settings->input.encoding = "iso1";
	settings->input.all = TRUE;
	settings->input.from = 1;
	settings->input.to = 1;
	settings->input.cut = TRUE;
	settings->input.interpret = TRUE;
	settings->input.binary = FALSE;
	settings->headings.headers = FALSE;
	strcpy(settings->headings.header, "%a");
	strcpy(settings->headings.underlay, "");
	strcpy(settings->headings.ctitle, "#?1|$t1|$n|");
	strcpy(settings->headings.ltitle, "#?2||$e $T|");
	strcpy(settings->headings.rtitle, "#?2|$t2|$Q|");
	strcpy(settings->headings.cfooter, "#?l|#!s-$f-, -||");
	strcpy(settings->headings.lfooter, "#?l!%E!#?v|%E|%s./%s#|!");
	strcpy(settings->headings.rfooter, "#?l!%s./%s#!#?v|%s./%s#|%E|!");
	settings->copies = 1;

	return(settings);
}

XfprintSettings *
xfprintsettings_load(void)
{
	XfprintSettings *settings;
	gchar *path;

	path = xfce_resource_lookup (XFCE_RESOURCE_CONFIG,
				     "xfce4/printsettings.xml");
	settings = xfprintsettings_defaults();
	load_settings_from_file(path, settings);
	g_free(path);

	return(settings);
}

void
xfprintsettings_free(XfprintSettings *settings)
{
	if (settings)
		g_free(settings);
}

/*
 * XML stuff
 */

typedef enum _SettingsParserState SettingsParserState;
enum _SettingsParserState
{
	START,
	PRINTSETTINGS
};

typedef struct _SettingsParser SettingsParser;
struct _SettingsParser
{
	XfprintSettings		*settings;
	SettingsParserState	state;
};

static void
start_element_handler(GMarkupParseContext *ctx, const gchar *element_name,
	const gchar **attr_names, const gchar **attr_values,
	gpointer user_data, GError **error)
{
	XfprintSettingsVirtualPages *v;
	XfprintSettingsPrettyPrint *p;
	XfprintSettingsHeadings *h;
	XfprintSettingsSheets *s;
	XfprintSettingsInput *i;
	SettingsParser *parser;
	gint n;
	
	parser = (SettingsParser *)user_data;

	i = &parser->settings->input;
	h = &parser->settings->headings;
	p = &parser->settings->pprint;
	s = &parser->settings->sheets;
	v = &parser->settings->vpages;

	switch (parser->state) {
	case START:
		if (strcmp(element_name, "printsettings") != 0)
			break;

		parser->state = PRINTSETTINGS;
		break;

	case PRINTSETTINGS:
		for (n = 0; attr_names[n] != NULL; n++) {
			if (!strcmp(element_name, "sheets")) {
				if (!strcmp(attr_names[n], "mode")) {
					s->landscape = !strcmp(attr_values[n],
						"landscape");
				}
				else if (!strcmp(attr_names[n], "major")) {
					s->fillcols = !strcmp(attr_values[n],
						"cols");
				}
				else if (!strcmp(attr_names[n], "cols")) {
					s->cols = strtol(attr_values[n], NULL,
						10);
				}
				else if (!strcmp(attr_names[n], "rows")) {
					s->rows = strtol(attr_values[n], NULL,
						10);
				}
				else if (!strcmp(attr_names[n], "papersize")) {
					s->papersize = xfprint_option(papersizes,
						attr_values[n]);
				}
				else if (!strcmp(attr_names[n], "borders")) {
					s->borders = !strcmp(attr_values[n],
						"true");
				}
				else if (!strcmp(attr_names[n], "reverse")) {
					s->reverse = !strcmp(attr_values[n],
						"true");
				}
			}
			else if (!strcmp(element_name, "vpages")) {
			        if (!strcmp(attr_names[n], "linenumbers")) {
					v->linenumbers = strtol(attr_values[n],
						NULL, 10);
				}
				if (!strcmp(attr_names[n], "linesperpage")) {
					v->lpp = strtol(attr_values[n],
						NULL, 10);
				}
				if (!strcmp(attr_names[n], "charsperline")) {
					v->cpl = strtol(attr_values[n],
						NULL, 10);
				}
				if (!strcmp(attr_names[n], "tabsize")) {
					v->tabsize = strtol(attr_values[n],
						NULL, 10);
				}
				else if (!strcmp(attr_names[n], "nonprtfmt")) {
					v->nonprtfmt = xfprint_option(
						non_printable_fmts,
						attr_values[n]);
				}
			}
			else if (!strcmp(element_name, "pprint")) {
				if (!strcmp(attr_names[n], "highlight")) {
					p->highlight = xfprint_option(hilevels,
						attr_values[n]);
				}
				else if (!strcmp(attr_names[n], "strip")) {
					p->strip = strtol(attr_values[n], NULL,
						10);
				}
			}
			else if (!strcmp(element_name, "input")) {
				if (!strcmp(attr_names[n], "encoding")) {
					i->encoding = xfprint_option(encodings,
						attr_values[n]);
				}
				else if (!strcmp(attr_names[n], "cut")) {
					i->cut = !strcmp(attr_values[n],
						"true");
				}
				else if (!strcmp(attr_names[n], "interpret")) {
					i->interpret = !strcmp(attr_values[n],
						"true");
				}
				else if (!strcmp(attr_names[n], "binary")) {
					i->binary = !strcmp(attr_values[n],
						"true");
				}
			}
			else if (!strcmp(element_name, "headings")) {
				if (!strcmp(attr_names[n], "headers")) {
					h->headers = !strcmp(attr_values[n],
						"true");
				}
				else if (!strcmp(attr_names[n], "header")) {
					strlcpy(h->header, attr_values[n],
						sizeof(h->header));
				}
				else if (!strcmp(attr_names[n], "underlay")) {
					strlcpy(h->underlay, attr_values[n],
						sizeof(h->underlay));
				}
				else if (!strcmp(attr_names[n], "ctitle")) {
					strlcpy(h->ctitle, attr_values[n],
						sizeof(h->ctitle));
				}
				else if (!strcmp(attr_names[n], "ltitle")) {
					strlcpy(h->ltitle, attr_values[n],
						sizeof(h->ltitle));
				}
				else if (!strcmp(attr_names[n], "rtitle")) {
					strlcpy(h->rtitle, attr_values[n],
						sizeof(h->rtitle));
				}
				else if (!strcmp(attr_names[n], "cfooter")) {
					strlcpy(h->cfooter, attr_values[n],
						sizeof(h->cfooter));
				}
				else if (!strcmp(attr_names[n], "lfooter")) {
					strlcpy(h->lfooter, attr_values[n],
						sizeof(h->lfooter));
				}
				else if (!strcmp(attr_names[n], "rfooter")) {
					strlcpy(h->rfooter, attr_values[n],
						sizeof(h->rfooter));
				}
			}
		}
		break;

	default:
		g_warning("start unknown element \"%s\"", element_name);
		break;
	}
}

static void
end_element_handler(GMarkupParseContext *ctx, const gchar *element_name,
	gpointer user_data, GError **error)
{
	SettingsParser *parser;

	parser = (SettingsParser *)user_data;

	switch (parser->state) {
	case START:
		/* XXX - this shouldn't happen */
		break;

	case PRINTSETTINGS:
		if (!strcmp(element_name, "printsettings"))
			parser->state = START;
		break;
		
	default:
		g_warning("end unknown element \"%s\"", element_name);
		break;
	}
}

static void
error_handler(GMarkupParseContext *ctx, GError *error, gpointer user_data)
{
	if (error && error->message)
		g_warning(" %s", error->message);
}

static GMarkupParser markup_parser = {
	start_element_handler,
	end_element_handler,
	NULL,
	NULL,
	error_handler
};

static void
load_settings_from_file(const gchar *filename, XfprintSettings *settings)
{
	GMarkupParseContext *ctx;
	SettingsParser parser;
	gchar *contents;
	GError *error;
	struct stat sb;
#ifdef HAVE_MMAP
	void *addr;
#endif
	int fd;

	error = NULL;

	parser.settings = settings;
	parser.state = START;

#ifdef O_SHLOCK
	if ((fd = open(filename, O_RDONLY | O_SHLOCK, 0)) < 0)
#else
	if ((fd = open(filename, O_RDONLY, 0)) < 0)
#endif
		return;

	if (fstat(fd, &sb) < 0)
		goto finished;

#ifdef HAVE_MMAP
	/* Try to mmap(2) the config file, as this save us a lot of
	 * kernelspace -> userspace copying
	 */
#ifdef MAP_FILE
	addr = mmap(NULL, sb.st_size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
#else
	addr = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
#endif

	if (addr != NULL) {
		/* nice, mmap did the job */
		contents = addr;
	}
	else {
#endif
		if ((contents = malloc(sb.st_size * sizeof(gchar))) == NULL)
			goto finished;

		/* read(2):
		 * --------
		 * The system guarantees to read the number of bytes requested
		 * if the descriptor references a normal file that has that 
		 * many bytes left before the end-of-file.
		 */
		if (read(fd, contents, sb.st_size) < sb.st_size)
			goto finished2;
#ifdef HAVE_MMAP
	}
#endif

	ctx = g_markup_parse_context_new(&markup_parser, 0, &parser, NULL);

	if (!g_markup_parse_context_parse(ctx, contents, sb.st_size, &error)) {
		g_print(error->message);
		goto finished3;
	}
	
	if (!g_markup_parse_context_end_parse(ctx, NULL))
		goto finished3;

finished3:
	g_markup_parse_context_free(ctx);

finished2:
#ifdef HAVE_MMAP
	if (addr != NULL)
		munmap(addr, sb.st_size);
	else
#endif
		free(contents);

finished:
	(void)close(fd);
}

static void
save_settings_to_file(const gchar *filename, const XfprintSettings *settings)
{
	FILE *fp;
	int fd;

#ifdef O_EXLOCK
	if ((fd = open(filename, O_CREAT|O_EXLOCK|O_TRUNC|O_WRONLY, S_IRUSR | S_IWUSR))< 0)
#else
	if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR)) < 0)
#endif
		return;

	if ((fp = fdopen(fd, "w")) == NULL) {
		(void)close(fd);
		return;
	}

	fprintf(fp,
		"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
		"<!DOCTYPE printsettings SYSTEM \"printsettings.dtd\">\n"
		"\n");

	fprintf(fp, "<printsettings>\n");

	fprintf(fp,
		"\t<sheets mode=\"%s\" major=\"%s\" cols=\"%d\" rows=\"%d\" "
		"papersize=\"%s\" borders=\"%s\" reverse=\"%s\" />\n",
		settings->sheets.landscape ? "landscape" : "portrait",
		settings->sheets.fillcols ? "cols" : "rows",
		settings->sheets.cols,
		settings->sheets.rows,
		settings->sheets.papersize,
		settings->sheets.borders ? "true" : "false",
		settings->sheets.reverse ? "true" : "false");

	fprintf(fp, "\t<vpages ");
	if (settings->vpages.linenumbers > 0)
		fprintf(fp, "linenumbers=\"%d\" ",settings->vpages.linenumbers);
	if (settings->vpages.lpp > 0)
		fprintf(fp, "linesperpage=\"%d\" ", settings->vpages.lpp);
	if (settings->vpages.cpl > 0)
		fprintf(fp, "charsperline=\"%d\" ", settings->vpages.cpl);
	fprintf(fp, "tabsize=\"%d\" nonprtfmt=\"%s\" />\n",
		settings->vpages.tabsize,
		settings->vpages.nonprtfmt);

	fprintf(fp,
		"\t<pprint highlight=\"%s\" strip=\"%d\" />\n",
		settings->pprint.highlight,
		settings->pprint.strip);

	fprintf(fp,
		"\t<input encoding=\"%s\" cut=\"%s\" interpret=\"%s\" "
		"binary=\"%s\" />\n",
		settings->input.encoding,
		settings->input.cut ? "true" : "false",
		settings->input.interpret ? "true" : "false",
		settings->input.binary ? "true" : "false");

	fprintf(fp,
		"\t<headings headers=\"%s\" header=\"%s\" underlay=\"%s\" "
		"ctitle=\"%s\" ltitle=\"%s\" rtitle=\"%s\" cfooter=\"%s\" "
		"lfooter=\"%s\" rfooter=\"%s\" />\n",
		settings->headings.headers ? "true" : "false",
		settings->headings.header,
		settings->headings.underlay,
		settings->headings.ctitle,
		settings->headings.ltitle,
		settings->headings.rtitle,
		settings->headings.cfooter,
		settings->headings.lfooter,
		settings->headings.rfooter);

	fprintf(fp, "</printsettings>\n");

	(void)fflush(fp);
	(void)fclose(fp);
}

