/*
 * Copyright (c) 2013,2021,2023 Red Hat.
 * Copyright (c) 2006, Ken McDonell.  All Rights Reserved.
 *
 * 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.
 */
#include <QString>
#include <QMessageBox>
#include <QColor>

#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include "main.h"
#include "openviewdialog.h"
#include "saveviewdialog.h"
#include <pcp/libpcp.h>

/*
 * View file parsing routines and global variables follow.  These are
 * currently not part of any class, and may need to be reworked a bit
 * to use more portable (Qt) file IO interfaces (for a Windows port).
 */
static char	_fname[MAXPATHLEN];
static uint	_line;
static uint	_errors;
static uint	_width;
static uint	_height;
static uint	_points;
static uint	_xpos;
static uint	_ypos;

#define MAXWDSZ 256

// parser states
#define S_BEGIN		0
#define S_VERSION	1
#define S_TOP		2
#define S_CHART		3
#define S_PLOT		4

// config file styles (mode)
#define M_UNKNOWN	0
#define M_PMCHART	1
#define M_KMCHART	2

// global attributes
#define G_WIDTH		1
#define G_HEIGHT	2
#define G_POINTS	3
#define G_XPOS		4
#define G_YPOS		5

// host mode
#define H_DYNAMIC	1
#define H_LITERAL	2

// error severity
#define E_INFO	0
#define E_CRIT	1
#define E_WARN	2

// version numbers we're willing and able to support
#define P1_1	101
#define P1_2	102
#define P2_0	200
#define P2_1	201
#define K1	1

// instance / matching / not-matching
#define IM_NONE		0
#define IM_INST		1
#define IM_MATCH	2
#define IM_NOT_MATCH	3

const char *_style[] = { "None", "Line", "Bar", "Stack", "Area", "Util" };
#define stylestr(x) _style[(int)x]

static void err(int severity, int do_where, QString msg)
{
    if (do_where) {
	QString	where = QString("%1[%2] ").arg(_fname).arg(_line);
	msg.prepend(where);
    }
    if (Cflag) {
	if (severity == E_CRIT)
	    msg.prepend("Error: ");
	else if (severity == E_WARN)
	    msg.prepend("Warning: ");
	// else do nothing for E_INFO
	msg.append("\n");
	fflush(stderr);
	pmprintf("%s", (const char *)msg.toLatin1());
	pmflush();
    }
    else {
	if (severity == E_CRIT)
	    QMessageBox::critical(pmchart, pmGetProgname(),  msg);
	else if (severity == E_WARN)
	    QMessageBox::warning(pmchart, pmGetProgname(),  msg);
	else
	    QMessageBox::information(pmchart, pmGetProgname(),  msg);
    }
    _errors++;
}

static char *getwd(FILE *f)
{
    static char	buf[MAXWDSZ];
    static int	lastc = 0;
    char	*p;
    int		c;
    int		quote = 0;

    if ((char)lastc == '\n') {
eol:
	buf[0] = '\n';
	buf[1] = '\0';
	_line++;
	lastc = 0;
	goto done;
    }

    // skip to first non-white space
    p = buf;
    while ((c = fgetc(f)) != EOF) {
	if ((char)c == '\n')
	    goto eol;
	if (!isspace((char)c)) {
	    // got one
	    if ((char)c == '"') {
		quote = 1;
	    }
	    else {
		*p++ = c;
	    }
	    break;
	}
    }
    if (feof(f)) return NULL;

    for ( ; p < &buf[MAXWDSZ]; ) {
	if ((c = fgetc(f)) == EOF) break;
	if ((char)c == '\n') {
	    lastc = c;
	    break;
	}
	if (quote == 0 && isspace((char)c)) break;
	if (quote && (char)c == '"') break;
	*p++ = c;
    }

    if (p == &buf[MAXWDSZ]) {
	char msg[256];
	p[-1] = '\0';
	pmsprintf(msg, sizeof(msg),
		"Word truncated after %d characters!\n\"%20.20s ... %20.20s\"",
		(int)sizeof(buf)-1, buf, &p[-21]);
	err(E_CRIT, true, QString(msg));
    }
    else
	*p = '\0';

done:
    if (pmDebugOptions.appl0 && pmDebugOptions.appl2) {
	if (buf[0] == '\n')
	    fprintf(stderr, "openView getwd=EOL\n");
	else
	    fprintf(stderr, "openView getwd=\"%s\"\n", buf);
    }

    return buf;
}

static void eol(FILE *f)
{
    char	*w;

    while ((w = getwd(f)) != NULL && w[0] != '\n') {
	QString	msg = QString("Syntax error: unexpected word \"");
	msg.append(w);
	msg.append("\"");
	err(E_CRIT, true, msg);
    }
}

static void skip2eol(FILE *f)
{
    char	*w;

    while ((w = getwd(f)) != NULL && w[0] != '\n') {
	;
    }
}

static void xpect(const char *want, const char *got)
{
    QString     msg = QString("Syntax error: expecting \"");
    msg.append(want);
    msg.append("\", found ");
    if (got == NULL)
	msg.append("End-of-File");
    else if (got[0] == '\n')
	msg.append("End-of-Line");
    else {
	msg.append("\"");
	msg.append(got);
	msg.append("\"");
    }
    err(E_CRIT, true, msg);
}

static QColor colorSpec(QString colorName, int *sequence)
{
    if (colorName == "#-cycle")
	return nextColor("#-cycle", sequence);
    if (ColorScheme::lookupScheme(colorName) == true)
	return nextColor(colorName, sequence);
    QColor color = ColorScheme::colorSpec(colorName);
    if (!color.isValid()) {
	QString errmsg;
	errmsg.append("Invalid color name: ");
	errmsg.append(colorName);
	err(E_CRIT, true, errmsg);
	color = Qt::white;
    }
    return color;
}

void OpenViewDialog::globals(int *w, int *h, int *pts, int *x, int *y)
{
    // Note: we use global variables here so that all views specified
    // on the command line get input into these values (the maximum
    // observed value is always used), without clobbering each other.
    //
    *w = _width;
    *h = _height;
    *pts = _points;
    *x = _xpos;
    *y = _ypos;
}

bool OpenViewDialog::openView(const char *path)
{
    Tab			*tab;
    Chart		*cp = NULL;
    int			ct = pmchart->tabWidget()->currentIndex();
    int			m;
    ColorScheme		scheme;
    FILE		*f;
    int			is_popen = 0;
    char		*w;
    int			state = S_BEGIN;
    int			mode = M_UNKNOWN;
//  int			h_mode;
    int			version;
    QString		errmsg;
    QRegularExpression	regex;
    QRegularExpressionMatch match;
    int			sep = pmPathSeparator();
    int			sts = 0;

    if (strcmp(path, "-") == 0) {
	f = stdin;
	strcpy(_fname, "stdin");
    }
    else if (path[0] == '/') {
	strcpy(_fname, path);
	if ((f = fopen(_fname, "r")) == NULL)
	    goto noview;
    }
    else {
	QString homepath = QDir::toNativeSeparators(QDir::homePath());

	strcpy(_fname, path);
	if ((f = fopen(_fname, "r")) == NULL) {
	    // not found, try users pmchart dir
	    pmsprintf(_fname, sizeof(_fname),
			"%s%c" ".pcp%c" "pmchart%c" "%s",
			(const char *)homepath.toLatin1(), sep, sep, sep, path);
	    if ((f = fopen(_fname, "r")) == NULL) {
		// not found, try system pmchart dir
		pmsprintf(_fname, sizeof(_fname),
			    "%s%c" "config%c" "pmchart%c" "%s",
			    pmGetConfig("PCP_VAR_DIR"), sep, sep, sep, path);
		if ((f = fopen(_fname, "r")) == NULL)
		    goto noview;
	    }
	}
	// check for executable and __pmProcessPipe() as needed
	//
	if (fgetc(f) == '#' && fgetc(f) == '!') {
	    char	cmd[MAXPATHLEN];
	    __pmExecCtl_t	*argp = NULL;
	    pmsprintf(cmd, sizeof(cmd), "%s", _fname);
	    fclose(f);
	    if (__pmProcessUnpickArgs(&argp, cmd) < 0)
		goto nopipe;
	    if (__pmProcessPipe(&argp, "r", PM_EXEC_TOSS_NONE, &f) < 0)
		goto nopipe;
	    is_popen = 1;
	}
	else {
	    rewind(f);
	}
    }

    _line = 1;
    _errors = 0;
    console->post(PmChart::DebugView, "Load View: %s", _fname);

    while ((w = getwd(f)) != NULL) {
	if (state == S_BEGIN) {
	    // expect #pmchart
	    if (strcasecmp(w, "#pmchart") == 0)
		mode = M_PMCHART;
	    else if (strcasecmp(w, "#kmchart") == 0)
		mode = M_KMCHART;
	    else {
		xpect("#pmchart\" or \"#kmchart", w);
		goto abandon;
	    }
	    eol(f);
	    state = S_VERSION;
	    continue;
	}

	if (w[0] == '\n')
	    // skip empty lines and comments
	    continue;

	if (w[0] == '#') {
	    // and comments
	    skip2eol(f);
	    continue;
	}

	if (state == S_VERSION) {
	    // expect version X.X host [dynamic|static]
	    if (strcasecmp(w, "version") != 0) {
		xpect("version", w);
		goto abandon;
	    }
	    w = getwd(f);
	    if (w == NULL || w[0] == '\n') {
		xpect("<version number>", w);
		goto abandon;
	    }
	    version = 0;
	    if (mode == M_PMCHART) {
		if (strcmp(w, "2.1") == 0)
		    version = P2_1;
		else if (strcmp(w, "2.0") == 0)
		    version = P2_0;
		else if (strcmp(w, "1.1") == 0)
		    version = P1_1;
		else if (strcmp(w, "1.2") == 0)
		    version = P1_2;
	    }
	    else if (mode == M_KMCHART) {
		if (strcmp(w, "1") == 0)
		    version = K1;
	    }
	    if (version == 0) {
		xpect("<version number>", w);
		goto abandon;
	    }
	    w = getwd(f);
	    if (w == NULL || w[0] == '\n') {
		if (mode == M_KMCHART) {
		    // host [literal|dynamic] is optional for new pmchart
//		    h_mode = H_DYNAMIC;
		    state = S_TOP;
		    continue;
		}
		else {
		    xpect("host", w);
		    goto abandon;
		}
	    }
	    if (strcasecmp(w, "host") != 0) {
		xpect("host", w);
		goto abandon;
	    }
	    w = getwd(f);
	    if (w != NULL && strcasecmp(w, "literal") == 0) {
// NYI:		h_mode = H_LITERAL;
	    }
	    else if (w != NULL && strcasecmp(w, "dynamic") == 0) {
// NYI:		h_mode = H_DYNAMIC;
	    }
	    else {
		xpect("literal\" or \"dynamic", w);
		goto abandon;
	    }
	    eol(f);
	    state = S_TOP;
	}

	else if (state == S_TOP) {
	    if (strcasecmp(w, "chart") == 0) {
new_chart:
		char		*title = NULL;
		Chart::Style	style = Chart::NoStyle;
		int		autoscale = 1;
		char		*endnum;
		double		ymin = 0;
		double		ymax = 0;
		int		legend = 1;
		int		antialias = 1;

		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("title\" or \"style", w);
		    goto abort_chart;
		}
		if (strcasecmp(w, "title") == 0) {
		    // optional title "<title>"
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("<title>", w);
			goto abort_chart;
		    }
		    if ((title = strdup(w)) == NULL) nomem();
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("style", w);
			goto abort_chart;
		    }
		}
		if (strcasecmp(w, "style") == 0) {
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("<chart style>", w);
			goto abort_chart;
		    }
		    if (strcasecmp(w, "plot") == 0)
			style = Chart::LineStyle;
		    else if (strcasecmp(w, "line") == 0)
			style = Chart::LineStyle;
		    else if (strcasecmp(w, "bar") == 0)
			style = Chart::BarStyle;
		    else if (strcasecmp(w, "stacking") == 0)
			style = Chart::StackStyle;
		    else if (strcasecmp(w, "area") == 0)
			style = Chart::AreaStyle;
		    else if (strcasecmp(w, "utilization") == 0)
			style = Chart::UtilisationStyle;
		    else if (strcasecmp(w, "event") == 0)
			style = Chart::EventStyle;
		    else {
			xpect("<chart style>", w);
			goto abort_chart;
		    }
		}

		// down to the optional bits
		//	- scale
		//	- legend
		if ((w = getwd(f)) == NULL || w[0] == '\n')
		    goto done_chart;
		if (strcasecmp(w, "scale") == 0) {
		    // scale [from] ymin [to] ymax
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("from or <ymin>", w);
			goto abort_chart;
		    }
		    if (strcasecmp(w, "from") == 0) {
			if ((w = getwd(f)) == NULL || w[0] == '\n') {
			    xpect("<ymin>", w);
			    goto abort_chart;
			}
		    }
		    ymin = strtod(w, &endnum);
		    if (*endnum != '\0') {
			xpect("<ymin>", w);
			goto abort_chart;
		    }
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("to or <ymax>", w);
			goto abort_chart;
		    }
		    if (strcasecmp(w, "to") == 0) {
			if ((w = getwd(f)) == NULL || w[0] == '\n') {
			    xpect("<ymax>", w);
			    goto abort_chart;
			}
		    }
		    ymax = strtod(w, &endnum);
		    if (*endnum != '\0') {
			xpect("<ymax>", w);
			goto abort_chart;
		    }
		    autoscale = 0;
		    if ((w = getwd(f)) == NULL || w[0] == '\n')
			goto done_chart;
		}
		if (strcasecmp(w, "legend") == 0) {
		    // optional legend on|off
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("on\" or \"off", w);
			goto abort_chart;
		    }
		    if (strcasecmp(w, "on") == 0)
			legend = 1;
		    else if (strcasecmp(w, "off") == 0)
			legend = 0;
		    else {
			xpect("on\" or \"off", w);
			goto abort_chart;
		    }
		    if ((w = getwd(f)) == NULL || w[0] == '\n')
			goto done_chart;
		}
		if (strcasecmp(w, "antialiasing") == 0) {
		    // optional antialiasing on|off
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("on\" or \"off", w);
			goto abort_chart;
		    }
		    if (strcasecmp(w, "on") == 0)
			antialias = 1;
		    else if (strcasecmp(w, "off") == 0)
			antialias = 0;
		    else {
			xpect("on\" or \"off", w);
			goto abort_chart;
		    }
		    if ((w = getwd(f)) == NULL || w[0] == '\n')
			goto done_chart;
		}
done_chart:
		if (pmDebugOptions.appl2) {
		    fprintf(stderr, "openView: new chart: style=%s",
				    stylestr(style));
		    if (title != NULL)
			fprintf(stderr, " title=\"%s\"", title);
		    if (autoscale)
			fprintf(stderr, " autoscale=yes");
		    else
			fprintf(stderr, " ymin=%.1f ymax=%.1f", ymin, ymax);
		    if (legend)
			fprintf(stderr, " legend=yes");
		    if (!antialias)
			fprintf(stderr, " antialias=no");
		    fputc('\n', stderr);
		}
		if (Cflag == 0 || Cflag == 2) {
		    tab = pmchart->activeTab();
		    cp = new Chart(tab, tab->splitter());
		    cp->setStyle(style);
		    cp->setScheme(scheme.name());
		    if (title != NULL)
			cp->changeTitle(title, mode == M_KMCHART);
		    if (legend == 0)
			cp->setLegendVisible(false);
		    if (antialias == 0)
			cp->setAntiAliasing(false);
		    cp->setScale(autoscale, ymin, ymax);
		    activeGroup->addGadget(cp);
		    tab->addGadget(cp);
		}
		state = S_CHART;
		if (title != NULL) free(title);
		continue;

abort_chart:
		// unrecoverable error in the chart clause of the view
		// specification, abandon this one
		if (title != NULL) free(title);
		goto abandon;
	    }
	    else if (strcasecmp(w, "global") == 0) {
		char *endnum;
		uint value, attr;

		// Global window attributes (geometry and visible points)
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w);
		    goto abandon;
		}
		else if (strcasecmp(w, "width") == 0)
		    attr = G_WIDTH;
		else if (strcasecmp(w, "height") == 0)
		    attr = G_HEIGHT;
		else if (strcasecmp(w, "points") == 0)
		    attr = G_POINTS;
		else if (strcasecmp(w, "xpos") == 0)
		    attr = G_XPOS;
		else if (strcasecmp(w, "ypos") == 0)
		    attr = G_YPOS;
		else {
		    xpect("width\", \"height\", \"xpos\", \"ypos\", or \"points", w);
		    goto abandon;
		}
		w = getwd(f);
		if (w == NULL || w[0] == '\n') {
		    xpect("<global attribute value>", w);
		    goto abandon;
		}
		value = (uint)strtoul(w, &endnum, 0);
		if (*endnum != '\0') {
		    xpect("<global attribute value>", w);
		    goto abandon;
		}
		switch (attr) {
		    case G_WIDTH:
			_width = qMax(_width, value);
			break;
		    case G_HEIGHT:
			_height = qMax(_height, value);
			break;
		    case G_POINTS:
			_points = qMax(_points, value);
			break;
		    case G_XPOS:
			_xpos = qMax(_xpos, value);
			break;
		    case G_YPOS:
			_ypos = qMax(_ypos, value);
			break;
		}
		eol(f);
	    }
	    else if (strcasecmp(w, "scheme") == 0) {
		//
		// scheme <name> <color> <color>...
		// provides finer-grained control over the color selections
		// for an individual chart.  The default color scheme is
		// named #-cycle.  A scheme can be used in place of a direct
		// color name specification, and the color for a plot is
		// then defined as the next unused color from that scheme.
		//
		w = getwd(f);
		if (w == NULL || w[0] == '\n') {
		    xpect("<color scheme value>", w);
		    goto abandon;
		}
		else if (strcmp(w, "#-cycle") == 0) {
		    xpect("<non-default color scheme name>", w);
		    goto abandon;
		}
		if (ColorScheme::lookupScheme(w) == true) {
		    // duplicate - ignore (probably using a seen view again)
		    skip2eol(f);
		    continue;
		}
		scheme.setName(QString(w));
		scheme.clear();
		w = getwd(f);
		while (w && w[0] != '\n') {
		    scheme.addColor(QString(w));
		    w = getwd(f);
		}
		if (scheme.size() < 2) {
		    xpect("<list of color names>", w);
		    goto abandon;
		}
		globalSettings.colorSchemes.append(scheme);
	    }
	    else if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) {
new_tab:
		QString label, host;
		int samples = globalSettings.sampleHistory;
		int points = globalSettings.visibleHistory;
		char *endnum;

		w = getwd(f);
		if (w == NULL || w[0] == '\n') {
		    xpect("<tab label>", w);
		    goto abandon;
		}
		label = w;

		w = getwd(f);
		if (w == NULL || w[0] == '\n')
		    goto done_tab;

		// default "host" specification for the tab is optional
		if (strcasecmp(w, "host") == 0) {
		    w = getwd(f);
		    if (w == NULL || w[0] == '\n') {
			xpect("<host>", w);
			goto abandon;
		    }
		    host = w;
		    w = getwd(f);
		    if (w == NULL || w[0] == '\n')
			goto done_tab;
		}
		if (strcasecmp(w, "points") != 0) {
		    xpect("<tab points>", w);
		    goto abandon;
		}
		w = getwd(f);
		if (w)
	 	    points = (uint)strtoul(w, &endnum, 0);
		if (w == NULL || w[0] == '\n' || *endnum != '\0') {
		    xpect("<tab points value>", w);
		    goto abandon;
		}

		w = getwd(f);
		if (w == NULL || w[0] == '\n')
		    goto done_tab;
		if (strcasecmp(w, "samples") != 0) {
		    xpect("<tab samples>", w);
		    goto abandon;
		}
		w = getwd(f);
		if (w)
		    samples = (uint)strtoul(w, &endnum, 0);
		if (w == NULL || w[0] == '\n' || *endnum != '\0') {
		    xpect("<tab samples value>", w);
		    goto abandon;
		}

done_tab:
		tab = pmchart->activeTab();
		bool isArchive = tab->isArchiveSource();

		if (host != QString()) {
		    if (isArchive)
			archiveGroup->use(PM_CONTEXT_ARCHIVE, host);
		    else
			liveGroup->use(PM_CONTEXT_HOST, host);
		}

		if (tab->gadgetCount() == 0) {	// edit the initial tab
		    TabWidget *tabWidget = pmchart->tabWidget();
		    tabWidget->setTabText(tabWidget->currentIndex(), label);
		}
		else {		// create a completely new tab from scratch
		    tab = new Tab;
		    if (isArchive)
			tab->init(pmchart->tabWidget(), archiveGroup, label);
		    else
			tab->init(pmchart->tabWidget(), liveGroup, label);
		    pmchart->addActiveTab(tab);
		}
		activeGroup->setSampleHistory(samples);
		activeGroup->setVisibleHistory(points);
	    }
	    else {
		xpect("chart\", \"global\", \"scheme\" or \"tab", w);
		goto abandon;
	    }
	}

	else if (state == S_CHART) {
	    int		optional;
	    char	*legend = NULL;
	    char	*color = NULL;
	    char	*host = NULL;
	    int		inst_match_type = IM_NONE;
	    int		numinst = -1;
	    int		nextinst = -1;
	    int		*instlist = NULL;
	    char	**namelist = NULL;
	    pmMetricSpec pms;
	    int		abort = 1;	// default @ skip

	    memset(&pms, 0, sizeof(pms));
	    if (strcasecmp(w, "view") == 0 || strcasecmp(w, "tab") == 0) {
		// new tab
		state = S_TOP;
		goto new_tab;
	    }
	    if (strcasecmp(w, "chart") == 0) {
		// new chart
		state = S_TOP;
		goto new_chart;
	    }
	    if (strcasecmp(w, "plot") == 0) {
		optional = 0;
	    }
	    else if (strcasecmp(w, "optional-plot") == 0) {
		optional = 1;
	    }
	    else {
		xpect("plot\" or \"optional-plot", w);
		goto skip;
	    }
	    if ((w = getwd(f)) == NULL || w[0] == '\n') {
		xpect("title\" or \"color", w);
		goto skip;
	    }
	    if (strcasecmp(w, "title") == 0 ||
	        (mode == M_KMCHART && strcasecmp(w, "legend") == 0)) {
		// optional title "<title>" or
		// (for new pmchart) legend "<title>"
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("<legend title>", w);
		    goto skip;
		}
		if ((legend = strdup(w)) == NULL) nomem();
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("color", w);
		    goto skip;
		}
	    }
	    // color <color> is mandatory for pmchart, optional for
	    // new pmchart (where the default is color #-cycle)
	    if (strcasecmp(w, "color") == 0 || strcasecmp(w, "colour") == 0) {
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("<color>", w);
		    goto skip;
		}
		if ((color = strdup(w)) == NULL) nomem();
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("host", w);
		    goto skip;
		}
	    }
	    else if (mode == M_PMCHART) {
		xpect("color", w);
		goto skip;
	    }
	    // host <host> is mandatory for pmchart, optional for
	    // new pmchart (where the default is host *)
	    if (strcasecmp(w, "host") == 0) {
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("<host>", w);
		    goto skip;
		}
		if (strcmp(w, "*") == 0)
		    host = NULL;	// just like the new pmchart default
		else {
		    if ((host = strdup(w)) == NULL) nomem();
		}
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("metric", w);
		    goto skip;
		}
	    }
	    else if (mode == M_PMCHART) {
		xpect("host", w);
		goto skip;
	    }
	    // metric is mandatory
	    if (strcasecmp(w, "metric") == 0) {
		if ((w = getwd(f)) == NULL || w[0] == '\n') {
		    xpect("<metric>", w);
		    goto skip;
		}
		if ((pms.metric = strdup(w)) == NULL) nomem();
	    }
	    else {
		xpect("metric", w);
		goto skip;
	    }
	    pms.ninst = 0;
	    pms.inst[0] = NULL;
	    if ((w = getwd(f)) != NULL && w[0] != '\n') {
		// optional parts
		//	instance ...
		//	matching ...
		//	not-matching ...
		if (strcasecmp(w, "instance") == 0) {
		    inst_match_type = IM_INST;
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("<instance>", w);
			goto skip;
		    }
		    pms.ninst = 1;
		    if ((pms.inst[0] = strdup(w)) == NULL) nomem();
		}
		else if (strcasecmp(w, "matching") == 0) {
		    inst_match_type = IM_MATCH;
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("<pattern>", w);
			goto skip;
		    }
		    pms.ninst = 1;
		    pms.inst[0] = strdup(w);
		}
		else if (strcasecmp(w, "not-matching") == 0) {
		    inst_match_type = IM_NOT_MATCH;
		    if ((w = getwd(f)) == NULL || w[0] == '\n') {
			xpect("<pattern>", w);
			goto skip;
		    }
		    pms.ninst = 1;
		    pms.inst[0] = strdup(w);
		}
		else {
		    xpect("instance\" or \"matching\" or \"not-matching", w);
		    goto skip;
		}
		if (mode == M_PMCHART) {
		    // pmchart has this lame "instance extends to end
		    // of line" syntax ... sigh
		    while ((w = getwd(f)) != NULL && w[0] != '\n') {
			pms.inst[0] = (char *)realloc(pms.inst[0], strlen(pms.inst[0]) + strlen(w) + 2);
			if (pms.inst[0] == NULL) nomem();
			// if more than one space in the input, touch luck!
			strcat(pms.inst[0], " ");
			strcat(pms.inst[0], w);
		    }
		    if (pms.inst[0] != NULL) {
			pms.ninst = 1;
		    }
		}
		else {
		    // expect end of line after instance/pattern
		    // (pmchart uses quotes to make instance a single
		    // lexical element in the line)
		    eol(f);
		}
	    }

	    abort = 0;
	    if (pmDebugOptions.appl2) {
		fprintf(stderr, "openView: new %s", optional ? "optional-plot" : "plot");
		if (legend != NULL) fprintf(stderr, " legend=\"%s\"", legend);
		if (color != NULL) fprintf(stderr, " color=%s", color);
		if (host != NULL) fprintf(stderr, " host=%s", host);
		fprintf(stderr, " metric=%s", pms.metric);
		if (pms.ninst == 1) {
		    fprintf(stderr, " inst=%s", pms.inst[0]);
		    if (inst_match_type == IM_NONE)
			fprintf(stderr, " match=none (botch?)");
		    else if (inst_match_type == IM_INST)
			fprintf(stderr, " match=instance");
		    else if (inst_match_type == IM_MATCH)
			fprintf(stderr, " match=matching");
		    else if (inst_match_type == IM_NOT_MATCH)
			fprintf(stderr, " match=not-matching");
		}
		fputc('\n', stderr);
	    }
	    if (Cflag == 0 || Cflag == 2) {
		pms.isarch = (activeGroup == archiveGroup);
		if (host != NULL) {
		    // host literal, add to the list of sources
		    if (activeGroup == archiveGroup) {
			QString hostname = host;
			if (archiveGroup->use(PM_CONTEXT_HOST, hostname) < 0) {
			    QString msg = QString("\nHost \"%1\" cannot be matched to an open archive for metric %2")
						.arg(host).arg(pms.metric);
			    errmsg.append(msg);
			    goto skip;
			}
			QmcSource source = archiveGroup->context()->source();
			pms.source = source.sourceAscii();
		    }
		    else {
			pms.source = strdup(host);
		    }
		}
		else if (activeGroup->numContexts() > 0) {
		    // no explicit host, use current default source
		    QmcSource source = activeGroup->context()->source();
		    pms.source = strdup((const char *)
					source.source().toLatin1());
		}
		else {
		    // already reported context errors, just get out
		    _errors++;
		    goto abandon;
		}
		// expand instances when not specified for metrics
		// with instance domains and all instances required,
		// or matching or not-matching instances required
		//
		if (inst_match_type != IM_INST) {
		    pmID	pmid;
		    pmDesc	desc;

		    // if pmLookupName() or pmLookupDesc() fail, we'll
		    // notice in addPlot() and report the error below,
		    // so no need to do anything special here
		    //
		    if (pmLookupName(1, (const char **)&pms.metric, &pmid) < 0)
			goto try_plot;
		    if (pmLookupDesc(pmid, &desc) < 0)
			goto try_plot;
		    if (desc.indom == PM_INDOM_NULL) {
			if (inst_match_type == IM_MATCH ||
			    inst_match_type == IM_NOT_MATCH) {
			    // a bit embarrassing
			    QString	msg = QString("\nMetric \"%1\" for\n%2 %3: no instance domain, cannot handle matching specification")
				.arg(pms.metric).arg(pms.isarch ? "archive" : "host").arg(pms.source);
			    errmsg.append(msg);
			    goto skip;
			}
			goto try_plot;
		    }

		    if (pms.isarch)
			numinst = pmGetInDomArchive(desc.indom, &instlist, &namelist);
		    else
			numinst = pmGetInDom(desc.indom, &instlist, &namelist);
		    if (numinst < 0) {
			QString	msg = QString("\nMetric \"%1\" for\n%2 %3: empty instance domain")
				.arg(pms.metric).arg(pms.isarch ? "archive" : "host").arg(pms.source);
			errmsg.append(msg);
			goto skip;
		    }
		    if (inst_match_type != IM_NONE) {
			regex.setPattern(pms.inst[0]);
			if (!regex.isValid()) {
			    errmsg = "Invalid regular expression:\n  ";
			    errmsg.append(pms.inst[0]);
			    errmsg.append("\n\n");
			    errmsg.append(regex.errorString());
			    goto skip;
			}
		    }
		    pms.ninst = numinst ? 1 : 0;
		    if (pms.inst[0] != NULL) {
			free(pms.inst[0]);
			pms.inst[0] = NULL;
		    }
		}

try_plot:
		if (numinst > 0) {
		    pms.inst[0] = NULL;
		    for (nextinst++ ; nextinst < numinst; nextinst++) {
			sts = 0;
			if (inst_match_type == IM_MATCH ||
			    inst_match_type == IM_NOT_MATCH) {
			    match = regex.match(QString(namelist[nextinst]));
			    if (match.hasMatch())
			        sts = match.capturedStart(0);
			    else
			        sts = -1;
			}
			switch (inst_match_type) {
			    case IM_MATCH:
				if (sts != -1)
				    pms.inst[0] = namelist[nextinst];
				break;
			    case IM_NOT_MATCH:
				if (sts == -1)
				    pms.inst[0] = namelist[nextinst];
				break;
			    case IM_NONE:
				pms.inst[0] = namelist[nextinst];
				break;
			}
			if (pms.inst[0] != NULL)
			    break;
		    }
		    if (nextinst == numinst)
			goto skip;
		}
		m = cp->addItem(&pms, QString(legend));
		if (m < 0) {
		    if (!optional) {
			QString	msg;
			if (pms.inst[0] != NULL)
			    msg = QString("\nFailed to plot metric \"%1[%2]\" for\n%3 %4:\n")
				.arg(pms.metric).arg(pms.inst[0])
				.arg(pms.isarch ? "archive" : "host").arg(pms.source);
			else
			    msg = QString("\nFailed to plot metric \"%1\" for\n%2 %3:\n")
				.arg(pms.metric).arg(pms.isarch ? "archive" : "host")
				.arg(pms.source);
			if (m == PM_ERR_CONV)
			    msg.append("Units for this metric are not compatible with other plots in this chart");
			else
			    msg.append(pmErrStr(m));
			errmsg.append(msg);
		    }
		}
		else if (color != NULL && strcmp(color, "#-cycle") != 0) {
		    int seq = cp->sequence();
		    cp->setStroke(m, cp->style(), colorSpec(color, &seq));
		    cp->setSequence(seq);
		}
		if (numinst > 0)
		    // more instances still to be processed for this metric
		    goto try_plot;

	    }

skip:
	    if (legend != NULL) free(legend);
	    if (color != NULL) free(color);
	    if (host != NULL) free(host);
	    if (instlist != NULL) free(instlist);
	    if (namelist != NULL) free(namelist);
	    if (pms.source != NULL) free(pms.source);
	    if (pms.metric != NULL) free(pms.metric);
	    if (pms.inst[0] != NULL) free(pms.inst[0]);

	    if (abort)
		goto abandon;

	    continue;
	}
	
	else {
	    QString	msg = QString("Botch, state=%1").arg(state);
	    err(E_CRIT, true, msg);
	    goto abandon;
	}

	continue;

abandon:
	// giving up on the whole view specification
	break;

    }

    if (!errmsg.isEmpty()) {
	err(E_CRIT, true, errmsg);
    }

    if (f != stdin) {
	if (is_popen)
	    __pmProcessPipeClose(f);
	else
	    fclose(f);
    }

    if (_errors)
	return false;

    if (ct != pmchart->tabWidget()->currentIndex())	// new Tabs added
	pmchart->setActiveTab(ct, true);

    if ((Cflag == 0 || Cflag == 2) && cp != NULL) {
	activeGroup->setupWorldView();
	pmchart->activeTab()->showGadgets();
    }
    return true;

noview:
    errmsg = QString("Cannot open view file \"%1\"\n%2")
	    .arg(_fname).arg(strerror(errno));
    err(E_CRIT, false, errmsg);
    return false;

nopipe:
    errmsg = QString("Cannot execute \"%1\"\n%2")
	    .arg(_fname).arg(strerror(errno));
    err(E_CRIT, false, errmsg);
    return false;
}

void SaveViewDialog::setGlobals(int width, int height, int points, int x, int y)
{
    _width = width;
    _height = height;
    _points = points;
    _xpos = x;
    _ypos = y;
}

static void saveScheme(FILE *f, QString scheme)
{
    ColorScheme	*cs = ColorScheme::findScheme(scheme);
    int		m;

    if (cs) {
	fprintf(f, "scheme %s", (const char *)cs->name().toLatin1());
	for (m = 0; m < cs->size(); m++)
	    fprintf(f, " %s", (const char *)cs->colorName(m).toLatin1());
	fprintf(f, "\n\n");
    }
}

void SaveViewDialog::saveChart(FILE *f, Chart *cp, bool hostDynamic)
{
    const char	*s;
    double	ymin, ymax;
    bool	autoscale;

    fprintf(f, "chart");
    if (cp->title() != QString())
	fprintf(f, " title \"%s\"", (const char*)cp->title().toLatin1());
    switch (cp->style()) {
	case Chart::LineStyle:
	    s = "plot";
	    break;
	case Chart::BarStyle:
	    s = "bar";
	    break;
	case Chart::StackStyle:
	    s ="stacking";
	    break;
	case Chart::AreaStyle:
	    s = "area";
	    break;
	case Chart::UtilisationStyle:
	    s = "utilization";
	    break;
	case Chart::EventStyle:
	    s = "event";
	    break;
	case Chart::NoStyle:
	default:
	    s = "none";
	    break;
    }
    fprintf(f, " style %s", s);
    if (cp->style() != Chart::UtilisationStyle) {
	cp->scale(&autoscale, &ymin, &ymax);
	if (!autoscale)
	    fprintf(f, " scale %f %f", ymin, ymax);
    }
    if (!cp->legendVisible())
	fprintf(f, " legend off");
    if (!cp->antiAliasing())
	fprintf(f, " antialiasing off");
    fputc('\n', f);
    for (int m = 0; m < cp->metricCount(); m++) {
	QString legend;
	if (cp->activeItem(m) == false)
	    continue;
	fprintf(f, "\tplot");
	legend = cp->legend(m);
	if (legend != QString())
	    fprintf(f, " legend \"%s\"", (const char *)legend.toLatin1());
	fprintf(f, " color %s", (const char *)cp->color(m).name().toLatin1());
	if (hostDynamic == false) {
	    char localHostname[MAXHOSTNAMELEN];
	    QString host = cp->metricContext(m)->source().host();
	    if (QString::compare(host, "local:") == 0 || QString::compare(host, "localhost") == 0) {
		gethostname(localHostname, sizeof(localHostname));
		host = localHostname;
	    }
	    fprintf(f, " host %s", (const char *)host.toLatin1());
	}
        fprintf(f, " metric %s", (const char *)
                cp->metricName(m).toLatin1());
	if (cp->metricPtr(m)->explicitInsts())
            fprintf(f, " instance \"%s\"", (const char*)cp->metricInstance(m).toLatin1());
	fputc('\n', f);
    }
}

bool SaveViewDialog::saveView(QString file, bool hostDynamic,
				bool sizeDynamic, bool allTabs, bool allCharts)
{
    FILE	*f;
    int		c, t;
    Tab		*tab;
    Gadget	*gadget;
    char	*path = strdup((const char *)file.toLatin1());
    QStringList	schemes;

    if ((f = fopen(path, "w")) == NULL)
	goto noview;

    fprintf(f, "#kmchart\nversion %d\n\n", K1);
    if (sizeDynamic == false) {
	fprintf(f, "global width %u\n", _width);
	fprintf(f, "global height %u\n", _height);
	fprintf(f, "global points %u\n", _points);
	fprintf(f, "global xpos %u\n", _xpos);
	fprintf(f, "global ypos %u\n", _ypos);
	fprintf(f, "\n");
    }

    for (c = 0; c < pmchart->activeTab()->gadgetCount(); c++) {
	gadget = pmchart->activeTab()->gadget(c);
	if (gadget->scheme() == QString() ||
	    schemes.contains(gadget->scheme()) == true)
	    continue;
	schemes.append(gadget->scheme());
    }
    for (c = 0; c < schemes.size(); c++)
	saveScheme(f, schemes.at(c));

    if (allTabs) {
	TabWidget *tabWidget = pmchart->tabWidget();
	for (t = 0; t < tabWidget->size(); t++) {
	    tab = tabWidget->at(t);
	    fprintf(f, "\ntab \"%s\"\n\n",
		    (const char *) tabWidget->tabText(t).toLatin1());
	    for (c = 0; c < tab->gadgetCount(); c++)
		tab->gadget(c)->save(f, hostDynamic);
	}
    }
    else {
	tab = pmchart->activeTab();
	if (!allCharts)
	    tab->currentGadget()->save(f, hostDynamic);
	else
	    for (c = 0; c < tab->gadgetCount(); c++)
		tab->gadget(c)->save(f, hostDynamic);
    }

    fflush(f);
    fclose(f);
    free (path);
    return true;

noview:
    QString errmsg = QString("Cannot open \"%1\" for writing\n%2")
	    .arg(path).arg(strerror(errno));
    err(E_CRIT, false, errmsg);
    free (path);
    return false;
}
