/*
 *  Copyright (c) by Allin Cottrell
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

/* TRAMO/SEATS, X-12-ARIMA plugin for gretl */

#include "libgretl.h"
#include <gtk/gtk.h>
#include "tramo_x12a.h"

#ifdef WIN32
# include <windows.h>
#else
# include <signal.h>
#endif

enum opt_codes {
    TRAMO_SEATS,
    TRAMO_ONLY,
    X12A
};

const char *x12a_series_strings[] = {
    "d11", "d12", "d13"
};

static int tramo_got_irfin;

const char *tramo_series_strings[] = {
    "safin.t", "trfin.t", "irfin.t", "irreg.t"
};

const char *tx_descrip_formats[] = {
    N_("seasonally adjusted %s"),
    N_("trend/cycle for %s"),
    N_("irregular component of %s")
};

const char *default_mdl = {
    "# ARIMA specifications that will be tried\n"
    "(0 1 1)(0 1 1) X\n"
    "(0 1 2)(0 1 1) X\n"
    "(2 1 0)(0 1 1) X\n"
    "(0 2 2)(0 1 1) X\n"
    "(2 1 2)(0 1 1)\n"
};  

#ifndef WIN32

#define SP_DEBUG 0

static int glib_spawn (const char *workdir, const char *fmt, ...)
{
    GError *error = NULL;
    gchar *sout = NULL;
    gchar *serr = NULL;
    gchar *argv[8];

    va_list ap;
    int i, ok, nargs;
    int status = 0, ret = 0;
    char *s;

    argv[0] = g_strdup(fmt);
    argv[1] = NULL;
    i = nargs = 1;

    va_start(ap, fmt);

    while ((s = va_arg(ap, char *))) {
	argv[i] = g_strdup(s);
	argv[++i] = NULL;
    }

    va_end(ap);

    nargs = i;

#if SP_DEBUG
    fputs("spawning the following:\n", stderr);
    for (i=0; i<nargs; i++) {
	fprintf(stderr, " argv[%d] = '%s'\n", i, argv[i]);
    }
#endif

    signal(SIGCHLD, SIG_DFL);

    ok = g_spawn_sync (workdir,
		       argv,
		       NULL,
		       G_SPAWN_SEARCH_PATH,
		       NULL,
		       NULL,
		       &sout,
		       &serr,
		       &status,
		       &error);

    if (!ok) {
	fprintf(stderr, "spawn: '%s'\n", error->message);
	g_error_free(error);
	ret = 1;
    } else if (serr && *serr) {
	fprintf(stderr, "stderr: '%s'\n", serr);
	ret = 1;
    } else if (status != 0) {
	fprintf(stderr, "status=%d: stdout: '%s'\n", status, sout);
	ret = 1;
    }

    if (serr != NULL) g_free(serr);
    if (sout != NULL) g_free(sout);

    for (i=0; i<nargs; i++) {
	if (ret != 0) {
	    if (i == 0) {
		fputc(' ', stderr);
	    }
	    fprintf(stderr, "%s ", argv[i]);
	    if (i == nargs - 1) {
		fputc('\n', stderr);
	    }
	}
	free(argv[i]);
    }

    return ret;
}

#endif /* !WIN32 */

static int tx_dialog (tx_request *request)
{
    GtkWidget *hbox, *vbox, *tmp;
    gint i, ret = 0;

    for (i=0; i<N_COMMON_OPTS; i++) {
	request->opt[i].check = NULL;
    }

    request->dialog = 
	gtk_dialog_new_with_buttons (
				     (request->code == TRAMO_SEATS)?
				     "TRAMO/SEATS" : "X-12-ARIMA",
				     NULL,
				     GTK_DIALOG_MODAL | 
				     GTK_DIALOG_DESTROY_WITH_PARENT,
				     GTK_STOCK_CANCEL,
				     GTK_RESPONSE_REJECT,
				     GTK_STOCK_OK,
				     GTK_RESPONSE_ACCEPT,
				     NULL);

    vbox = gtk_vbox_new(FALSE, 0);    

    if (request->code == TRAMO_SEATS) {
	gtk_dialog_set_has_separator(GTK_DIALOG(request->dialog), FALSE);
	show_tramo_options(request, vbox);
    } else {
	/* X-12-ARIMA */
	tmp = gtk_label_new (_("Save data"));
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);

	tmp = gtk_check_button_new_with_label(_("Seasonally adjusted series"));
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
	request->opt[D11].check = tmp;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tmp), FALSE);

	tmp = gtk_check_button_new_with_label(_("Trend/cycle"));
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
	request->opt[D12].check = tmp;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tmp), FALSE);

	tmp = gtk_check_button_new_with_label(_("Irregular"));
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
	request->opt[D13].check = tmp;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tmp), FALSE);

	tmp = gtk_hseparator_new();
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
    
	tmp = gtk_check_button_new_with_label(_("Generate graph"));
	gtk_widget_show(tmp);
	gtk_box_pack_start(GTK_BOX(vbox), tmp, FALSE, FALSE, 5);
	request->opt[TRIGRAPH].check = tmp;
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tmp), TRUE);
    }

    gtk_widget_show(vbox);
    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 5);
    gtk_widget_show(hbox);
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(request->dialog)->vbox),
		       hbox, FALSE, FALSE, 5);

    ret = gtk_dialog_run(GTK_DIALOG(request->dialog));

    if (ret == GTK_RESPONSE_ACCEPT) ret = 1;
    else ret = 0;

    return ret;
}

static void get_seats_command (char *seats, const char *tramo)
{
    char *p;

    strcpy(seats, tramo);
    p = strrchr(seats, SLASH);
    if (p != NULL) {
	strcpy(p + 1, "seats");
    } else {
	strcpy(seats, "seats");
    }
}

static int graph_series (double **Z, DATAINFO *pdinfo, int opt)
{
    FILE *fp = NULL;
    const double *obs;
    char title[32];
    int t;

    obs = gretl_plotx(pdinfo);
    if (obs == NULL) {
	return E_ALLOC;
    }

    if (gnuplot_init(PLOT_TRI_GRAPH, &fp)) {
	return E_FOPEN;
    }

    gretl_push_c_numeric_locale();

    if (pdinfo->pd == 4) {
	if ((pdinfo->t2 - pdinfo->t1) / 4 < 8) {
	    fputs("set xtics nomirror 0,1\n", fp); 
	    fputs("set mxtics 4\n", fp);
	}
    } else if (pdinfo->pd == 12) {
	if ((pdinfo->t2 - pdinfo->t1) / 12 < 8) {
	    fputs("set xtics nomirror 0,1\n", fp); 
	    fputs("set mxtics 12\n", fp);
	}
    }

    fputs("set size 1.0,1.0\nset multiplot\nset size 1.0,0.32\n", fp);

    /* irregular component */
    if (opt == TRAMO_SEATS && !tramo_got_irfin) {
	sprintf(title, "%s", G_("irregular"));
    } else {
	sprintf(title, "%s - 1", G_("irregular"));
    }

    fprintf(fp, "set bars 0\n"
	    "set origin 0.0,0.0\n"
	    "plot '-' using 1:($2-1.0) title '%s' w impulses\n",
	    title);

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) {
	double y = Z[D13 + 1][t];

	fprintf(fp, "%g %g\n", obs[t], 
		(opt == TRAMO_SEATS && tramo_got_irfin)? y / 100.0 : y);
    }
    fputs("e\n", fp);

    /* actual vs trend/cycle */

    fprintf(fp, "set origin 0.0,0.33\n"
	    "plot '-' using 1:2 title '%s' w l, \\\n"
	    " '-' using 1:2 title '%s' w l\n",
	    pdinfo->varname[0], G_("trend/cycle"));

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) { 
	fprintf(fp, "%g %g\n", obs[t], Z[0][t]);
    }
    fputs("e , \\\n", fp);

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) { 
	fprintf(fp, "%g %g\n", obs[t], Z[D12 + 1][t]);
    }
    fputs("e\n", fp);

    /* actual vs seasonally adjusted */

    fprintf(fp, "set origin 0.0,0.66\n"
	    "plot '-' using 1:2 title '%s' w l, \\\n"
	    " '-' using 1:2 title '%s' w l\n",
	    pdinfo->varname[0], G_("adjusted"));

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) {
	fprintf(fp, "%g %g\n", obs[t], Z[0][t]);
    }
    fputs("e\n", fp);

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) {
	fprintf(fp, "%g %g\n", obs[t], Z[D11 + 1][t]);
    }
    fputs("e\n", fp);

    fputs("set nomultiplot\n", fp);

    gretl_pop_c_numeric_locale();

    fclose(fp);

    return 0;
}

static void copy_variable (double **targZ, DATAINFO *targinfo, int targv,
			   double **srcZ, DATAINFO *srcinfo, int srcv)
{
    int t;

    for (t=0; t<targinfo->n; t++) {
	targZ[targv][t] = srcZ[srcv][t];
    }

    strcpy(targinfo->varname[targv], srcinfo->varname[srcv]);
    strcpy(VARLABEL(targinfo, targv), VARLABEL(srcinfo, srcv));
}

static void clear_tramo_files (const char *tpath, const char *varname)
{
    char tfname[MAXLEN];
    int i;

    for (i=D11; i<=TRIGRAPH; i++) {
	sprintf(tfname, "%s%cgraph%cseries%c%s", tpath, SLASH, SLASH, SLASH,
		tramo_series_strings[i]);
	remove(tfname);
    }

    sprintf(tfname, "%s%coutput%c%s.out", tpath, SLASH, SLASH, varname);
    remove(tfname);
}

static int add_series_from_file (const char *fname, int code,
				 double **Z, DATAINFO *pdinfo,
				 int v, int opt, char *errmsg)
{
    FILE *fp;
    char *p, line[128], sfname[MAXLEN], date[8];
    char varname[VNAMELEN];
    double x;
    int d, yr, per, err = 0;
    int t;

    if (opt == TRAMO_SEATS) {
	tramo_got_irfin = 1;
	sprintf(sfname, "%s%cgraph%cseries%c%s", fname, SLASH, SLASH, SLASH,
		tramo_series_strings[code]);
    } else {
	strcpy(sfname, fname);
	p = strrchr(sfname, '.');
	if (p != NULL) strcpy(p + 1, x12a_series_strings[code]);
    }

    fp = gretl_fopen(sfname, "r");

    if (fp == NULL) {
	int gotit = 0;

	/* this is a bit of a pest: under some configurations, tramo/seats
	   outputs a series "irfin"; sometimes that is not created, but
	   we do get an "irreg".  So if we can't find the one, try looking
	   for the other.
	*/
	if (opt == TRAMO_SEATS && code == D13) { 
	    sprintf(sfname, "%s%cgraph%cseries%c%s", fname, SLASH, SLASH, SLASH,
		    tramo_series_strings[code + 1]);
	    fp = gretl_fopen(sfname, "r");
	    if (fp != NULL) {
		gotit = 1;
	    }
	    tramo_got_irfin = 0;
	}

	if (!gotit) {
	    fprintf(stderr, "Couldn't open %s\n", sfname);
	    sprintf(errmsg, _("Couldn't open %s"), sfname);
	    return 1;
	}
    }

    /* formulate name of new variable to add */
    if (opt == TRAMO_SEATS) {
	sprintf(varname, "%.5s_%.2s", pdinfo->varname[0], 
		tramo_series_strings[code]);
    } else {
	sprintf(varname, "%.4s_%s", pdinfo->varname[0], 
		x12a_series_strings[code]);
    }

    /* copy varname and label into place */
    strcpy(pdinfo->varname[v], varname);
    sprintf(VARLABEL(pdinfo, v), _(tx_descrip_formats[code]), pdinfo->varname[0]);

    if (opt == TRAMO_SEATS) {
	strcat(VARLABEL(pdinfo, v), " (TRAMO/SEATS)");
    } else {
	strcat(VARLABEL(pdinfo, v), " (X-12-ARIMA)");
    }	

    for (t=0; t<pdinfo->n; t++) {
	Z[v][t] = NADBL;
    }

    gretl_push_c_numeric_locale();

    if (opt == TRAMO_SEATS) {
	int i = 0;

	t = pdinfo->t1;
	while (fgets(line, 127, fp)) {
	    i++;
	    if (i >= 7 && sscanf(line, " %lf", &x) == 1) {
		if (t >= pdinfo->n) {
		    fprintf(stderr, "t = %d >= pdinfo->n = %d\n", t, pdinfo->n);
		    err = 1;
		    break;
		}		
		Z[v][t++] = x;
	    }
	}
    } else {
	/* grab the data from the x12arima file */
	while (fgets(line, 127, fp)) {
	    if (*line == 'd' || *line == '-') {
		continue;
	    }
	    if (sscanf(line, "%d %lf", &d, &x) != 2) {
		err = 1; 
		break;
	    }
	    yr = d / 100;
	    per = d % 100;
	    sprintf(date, "%d.%d", yr, per);
	    t = dateton(date, pdinfo);
	    if (t < 0 || t >= pdinfo->n) {
		err = 1;
		break;
	    }
	    Z[v][t] = x;
	}
    }

    gretl_pop_c_numeric_locale();

    fclose(fp);

    return err;
}

static void set_opts (tx_request *request)
{
    int i;

    request->savevars = 0;

    for (i=0; i<N_COMMON_OPTS; i++) {
	if (request->opt[i].check != NULL && 
	    GTK_TOGGLE_BUTTON(request->opt[i].check)->active) {
	    request->opt[i].save = 1;
	    if (i != TRIGRAPH) {
		request->savevars++;
	    }
	} else {
	    request->opt[i].save = 0;
	} 
    }
}

static void cancel_savevars (tx_request *request)
{
    int i;

    request->savevars = 0;

    for (i=0; i<N_COMMON_OPTS; i++) {
	request->opt[i].save = 0;
    } 
}

static int write_tramo_file (const char *fname, 
			     double **Z, DATAINFO *pdinfo,
			     int v, tx_request *request) 
{
    int startyr, startper, tsamp = pdinfo->t2 - pdinfo->t1 + 1; 
    char *p, tmp[8];
    double x;
    FILE *fp;
    int t;

    fp = gretl_fopen(fname, "w");
    if (fp == NULL) {
	return 1;
    }

    gretl_push_c_numeric_locale();

    x = date(pdinfo->t1, pdinfo->pd, pdinfo->sd0);
    startyr = (int) x;
    sprintf(tmp, "%g", x);
    p = strchr(tmp, '.');
    if (p != NULL) {
	startper = atoi(p + 1);
    } else {
	startper = 1;
    }

    fprintf(fp, "%s\n", pdinfo->varname[v]);
    fprintf(fp, "%d %d %d %d\n", tsamp, startyr, startper, pdinfo->pd);

    for (t=pdinfo->t1; t<=pdinfo->t2; t++) {
	if (t && t % pdinfo->pd == 0) fputc('\n', fp);
	if (na(Z[v][t])) {
	    fputs("-99999 ", fp);
	} else {
	    fprintf(fp, "%g ", Z[v][t]);
	}
    }
    fputc('\n', fp);

    if (print_tramo_options(request, fp) == 0) {
	request->code = TRAMO_ONLY; /* not running SEATS */
    }

    gretl_pop_c_numeric_locale();

    fclose(fp);

    return 0;
}

static int write_spc_file (const char *fname, 
			   double **Z, DATAINFO *pdinfo, 
			   int v, int *varlist) 
{
    int startyr, startper;
    char *p, tmp[8];
    double x;
    FILE *fp;
    int i, t;

    fp = gretl_fopen(fname, "w");
    if (fp == NULL) {
	return 1;
    }

    gretl_push_c_numeric_locale();

    x = date(pdinfo->t1, pdinfo->pd, pdinfo->sd0);
    startyr = (int) x;
    sprintf(tmp, "%g", x);
    p = strchr(tmp, '.');
    if (p != NULL) {
	startper = atoi(p + 1);
    } else {
	startper = 1;
    }

    fprintf(fp, "series{\n period=%d\n title=\"%s\"\n", pdinfo->pd, 
	    pdinfo->varname[v]);
    fprintf(fp, " start=%d.%d\n", startyr, startper);
    fputs(" data=(\n", fp);

    i = 0;
    for (t=pdinfo->t1; t<=pdinfo->t2; t++) {
	if (na(Z[v][t])) {
	    fputs("-99999 ", fp); /* FIXME? */
	} else {
	    fprintf(fp, "%g ", Z[v][t]);
	}
	if ((i + 1) % 7 == 0) {
	    fputc('\n', fp);
	}
	i++;
    }
    fputs(" )\n}\n", fp);

    /* FIXME: make these values configurable */
    fputs("automdl{}\nx11{", fp);

    if (varlist[0] > 0) {
	if (varlist[0] == 1) {
	    fprintf(fp, " save=%s ", x12a_series_strings[varlist[1]]); 
	} else {
	    fputs(" save=( ", fp);
	    for (i=1; i<=varlist[0]; i++) {
		fprintf(fp, "%s ", x12a_series_strings[varlist[i]]);
	    }
	    fputs(") ", fp);
	}
    }

    fputs("}\n", fp);

    gretl_pop_c_numeric_locale();

    fclose(fp);

    return 0;
}

static void form_varlist (int *varlist, tx_request *request)
{
    int i, j = 1;

    varlist[0] = 0;

    for (i=0; i<TRIGRAPH; i++) {
	if (request->opt[TRIGRAPH].save || request->opt[i].save) {
	    varlist[0] += 1;
	    varlist[j++] = i;
	}
    }
}

static void copy_basic_data_info (DATAINFO *targ, DATAINFO *src)
{
    targ->sd0 = src->sd0;
    strcpy(targ->stobs, src->stobs); 
    targ->t1 = src->t1;
    targ->t2 = src->t2;
    targ->pd = src->pd;
    targ->structure = src->structure;
}

static int save_vars_to_dataset (double ***pZ, DATAINFO *pdinfo,
				 double **tmpZ, DATAINFO *tmpinfo,
				 int *varlist, tx_request *request,
				 char *errmsg)
{
    int i, v, j, addvars = 0;

    /* how many vars are wanted, and new? */
    for (i=1; i<=varlist[0]; i++) {
	if (request->opt[varlist[i]].save && 
	    varindex(pdinfo, tmpinfo->varname[i]) == pdinfo->v) {
	    addvars++;
	}
    }

    if (addvars > 0 && dataset_add_series(addvars, pZ, pdinfo)) {
	strcpy(errmsg, _("Failed to allocate memory for new data"));
	return 1;
    }

    j = pdinfo->v - addvars;

    for (i=1; i<=varlist[0]; i++) {
	if (request->opt[varlist[i]].save) {
	    v = varindex(pdinfo, tmpinfo->varname[i]);
	    if (v < pdinfo->v) {
		copy_variable(*pZ, pdinfo, v, tmpZ, tmpinfo, i);
	    } else {
		copy_variable(*pZ, pdinfo, j++, tmpZ, tmpinfo, i);
	    }
	}
    }

    return 0;
}

#ifdef WIN32

static int helper_spawn (const char *prog, const char *vname,
			 const char *workdir, int code)
{
    char *cmd = NULL;
    int ret;

    if (code == TRAMO_ONLY) {
	cmd = g_strdup_printf("\"%s\" -i %s -k serie", prog, vname);
    } else if (code == TRAMO_SEATS) {
	cmd = g_strdup_printf("\"%s\" -OF %s", prog, vname);
    } else if (code == X12A) {
	cmd = g_strdup_printf("\"%s\" %s -r -p -q", prog, vname);
    } else {
	return 1;
    }

    if (cmd == NULL) {
	ret = E_ALLOC;
    } else {
	ret = winfork(cmd, workdir, SW_SHOWMINIMIZED, 
		      CREATE_NEW_CONSOLE | HIGH_PRIORITY_CLASS);
	g_free(cmd);
    }

    return ret;
}

#else

static int helper_spawn (const char *prog, const char *vname,
			 const char *workdir, int code)
{
    int err;

    if (code == TRAMO_ONLY) {
	err = glib_spawn(workdir, prog, "-i", vname, "-k", "serie", NULL);
    } else if (code == TRAMO_SEATS) {
	err = glib_spawn(workdir, prog, "-OF", vname, NULL);
    } else if (code == X12A) {
	err = glib_spawn(workdir, prog, vname, "-r", "-p", "-q", NULL);
    } else {
	err = 1;
    }

    return err;
}

#endif

int write_tx_data (char *fname, int varnum, 
		   double ***pZ, DATAINFO *pdinfo, int *graph, 
		   const char *prog, const char *workdir,
		   char *errmsg)
{
    int i, doit, err = 0;
    char varname[VNAMELEN];
    int varlist[4];
    FILE *fp = NULL;
    tx_request request;
    double **tmpZ;
    DATAINFO *tmpinfo;

    *errmsg = 0;

    /* sanity check */
    if (var_is_scalar(pdinfo, varnum)) {
	sprintf(errmsg, "%s %s", pdinfo->varname[varnum], 
		_("is a scalar"));
	return 1;
    }

    /* figure out which program we're using */
    if (strstr(prog, "tramo") != NULL) {
	request.code = TRAMO_SEATS;
    } else {
	request.code = X12A;
    }

    if (request.code == TRAMO_SEATS && (pdinfo->t2 - pdinfo->t1) > 599) {
	strcpy(errmsg, _("TRAMO can't handle more than 600 observations.\n"
			 "Please select a smaller sample."));
	return 1;
    } else if (request.code == X12A && (pdinfo->t2 - pdinfo->t1) > 719) {
	strcpy(errmsg, _("X-12-ARIMA can't handle more than 720 observations.\n"
			 "Please select a smaller sample."));
	return 1;
    }	

    request.pd = pdinfo->pd;

    /* show dialog and get option settings */
    doit = tx_dialog(&request); 
    if (!doit) {
	gtk_widget_destroy(request.dialog);
	return 0;
    }
    set_opts(&request);
    gtk_widget_destroy(request.dialog);

#if 0
    if (request.code == TRAMO_SEATS) {
	print_tramo_options(&request, stderr);
	return 1;
    }
#endif

    /* create little temporary dataset */
    tmpinfo = create_new_dataset(&tmpZ, 4, pdinfo->n, 0);
    if (tmpinfo == NULL) {
	return E_ALLOC;
    }

    copy_basic_data_info(tmpinfo, pdinfo);

    if (request.code == X12A) { 
	/* make a default x12a.mdl file if it doesn't already exist */
	sprintf(fname, "%s%cx12a.mdl", workdir, SLASH);
	fp = gretl_fopen(fname, "r");
	if (fp == NULL) {
	    fp = gretl_fopen(fname, "w");
	    if (fp == NULL) return 1;
	    fprintf(fp, "%s", default_mdl);
	    fclose(fp);
	} else {
	    fclose(fp);
	}
    } 

    sprintf(varname, pdinfo->varname[varnum]);
    form_varlist(varlist, &request);

    if (request.code == X12A) { 
	/* write out the .spc file for x12a */
	sprintf(fname, "%s%c%s.spc", workdir, SLASH, varname);
	write_spc_file(fname, *pZ, pdinfo, varnum, varlist);
    } else { 
	/* TRAMO, possibly plus SEATS */
	lower(varname);
	sprintf(fname, "%s%c%s", workdir, SLASH, varname);
	/* next line: this also sets request->code = TRAMO_ONLY if
	   seats is not to be run */
	write_tramo_file(fname, *pZ, pdinfo, varnum, &request);
	if (request.code == TRAMO_ONLY) {
	    cancel_savevars(&request); /* FIXME later */
	    varlist[0] = 0;
	}
    }

    /* now run the program(s) */

    if (request.code == X12A) {
	err = helper_spawn(prog, varname, workdir, X12A);
    } else { 
	char seats[MAXLEN];

	/* ensure any stale files get deleted first, just in case */
	clear_tramo_files(workdir, varname);

	err = helper_spawn(prog, varname, workdir, TRAMO_ONLY);

	if (!err && request.code == TRAMO_SEATS) {
	    get_seats_command(seats, prog);
	    err = helper_spawn(seats, varname, workdir, TRAMO_SEATS);
	}
    }

    if (!err) {
	if (request.code == X12A) {
	    sprintf(fname, "%s%c%s.out", workdir, SLASH, varname); 
	} else {
	    sprintf(fname, "%s%coutput%c%s.out", workdir, SLASH, SLASH, varname);
	} 

	/* save vars locally if needed; graph if wanted */
	if (varlist[0] > 0) {
	    copy_variable(tmpZ, tmpinfo, 0, *pZ, pdinfo, varnum);

	    for (i=1; i<=varlist[0]; i++) {
		err = add_series_from_file((request.code == X12A)? fname : workdir, 
					   varlist[i], tmpZ, tmpinfo, i, 
					   request.code, errmsg);
		if (err) {
		    fprintf(stderr, "add_series_from_file() failed\n");
		}
	    }

	    if (request.opt[TRIGRAPH].save) {
		err = graph_series(tmpZ, tmpinfo, request.code);
		if (err) {
		    fprintf(stderr, "graph_series() failed\n");
		} else {
		    *graph = 1;
		}
	    }
	}

	/* now save the local vars to main dataset, if wanted */
	if (request.savevars > 0) {
	    err = save_vars_to_dataset(pZ, pdinfo, tmpZ, tmpinfo, varlist, 
				       &request, errmsg);
	}
    }

    destroy_dataset(tmpZ, tmpinfo);

    return err;
}



syntax highlighted by Code2HTML, v. 0.9.1