/*
 *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "gretl.h"
#include "cmdstack.h"
#include "lib_private.h"
#include "cmd_private.h"
#include "fileselect.h"
#include "session.h"

#define CMD_DEBUG 0

typedef struct {
    int ID;
    int n;
    char **cmds;
} model_stack;

static model_stack *mstacks;
static int n_mstacks;

static char **cmd_stack;
static int n_cmds;

void free_command_stack (void)
{
    int i, j;

    if (cmd_stack != NULL) {
	for (i=0; i<n_cmds; i++) {
	    free(cmd_stack[i]);
	}
	free(cmd_stack);
	cmd_stack = NULL;
    }

    n_cmds = 0;

    if (n_mstacks > 0 && mstacks != NULL) {  
	for (i=0; i<n_mstacks; i++) {
	    for (j=0; j<mstacks[i].n; j++) {
		free(mstacks[i].cmds[j]); 
	    }
	    free(mstacks[i].cmds);
	}
	free(mstacks);
	mstacks = NULL;
    }

    n_mstacks = 0;
}

int add_command_to_stack (const char *str)
{
    int n = strlen(str);
    char **cmds;

    cmds = myrealloc(cmd_stack, (n_cmds + 1) * sizeof *cmds);
    if (cmds == NULL) {
	return 1;
    }

    cmd_stack = cmds;

    if (n > 0 && str[n-1] == '\n') {
	cmd_stack[n_cmds] = gretl_strdup(str);
    } else {
	cmd_stack[n_cmds] = g_strdup_printf("%s\n", str);
    }

    if (cmd_stack[n_cmds] == NULL) {
	return 1;
    }

    if (strlen(str) > 2 && 
	strncmp(str, "help", 4) &&
	strncmp(str, "info", 4) &&
	strncmp(str, "list", 4) &&
	strncmp(str, "open", 4) &&
	strncmp(str, "quit", 4)) {
	mark_session_changed();
    }

#if CMD_DEBUG
    fprintf(stderr, "added to stack as cmd_stack[%d]:\n"
	    " %s\n", n_cmds, cmd_stack[n_cmds]);
#endif
    
    n_cmds++;

    return 0;
}

void delete_last_command (void)
{
    if (n_cmds > 0) {
	free(cmd_stack[n_cmds - 1]);
	n_cmds--;
    }
}

static model_stack *add_model_stack (int model_id)
{
    model_stack *tmp;
    int nm = n_mstacks;

    tmp = myrealloc(mstacks, (nm + 1) * sizeof *tmp);
    if (tmp == NULL) {
	return NULL;
    }

    mstacks = tmp;
    n_mstacks++;

    mstacks[nm].ID = model_id;    
    mstacks[nm].n = 0;
    mstacks[nm].cmds = NULL;

#if CMD_DEBUG
    fprintf(stderr, "add_model_stack:\n"
	    " mstacks[%d]: ID=%d\n", nm, model_id);
#endif

    return &mstacks[nm];
}

static int mstack_delete_last_command (model_stack *mstack)
{
    int nc = mstack->n;
    char **tmp;

    free(mstack->cmds[nc-1]);
    mstack->n -= 1;

    tmp = realloc(mstack->cmds, (nc - 1) * sizeof *tmp);
    if (tmp == NULL) {
	return 1;
    }

    mstack->cmds = tmp;

    return 0;
}

static int add_command_to_mstack (model_stack *mstack, const char *str)
{
    int nc = mstack->n;
    char **tmp;

    tmp = realloc(mstack->cmds, (nc + 1) * sizeof *tmp);
    if (tmp == NULL) {
	return 1;
    }

    mstack->cmds = tmp;

    mstack->cmds[nc] = gretl_strdup(str);
    if (mstack->cmds[nc] == NULL) {
	return 1;
    }

    mstack->n += 1;



#if CMD_DEBUG
    fprintf(stderr, "add_command_to_mstack, with ID=%d:\n"
	    " %s\n", mstack->ID, str);
#endif

    return 0;
}

static model_stack *mstack_from_model_id (int ID)
{
    int i;

    for (i=0; i<n_mstacks; i++) {
	if (mstacks[i].ID == ID) { 
	    return &mstacks[i];
	}
    }

    return NULL;
}

/* make a record of commands associated with a given model, so that
   they may be reconstructed later as part of the session mechanism
*/

int model_command_init (int model_ID)
{
    char *line;
    CMD *libcmd;
    model_stack *mstack;
    PRN *echo;
    int err = 0;

    line = get_lib_cmdline();
    libcmd = get_lib_cmd();

    /* pre-process the line */
    if (check_specific_command(line)) {
	return 1;
    }

    mstack = mstack_from_model_id(model_ID);

    if (mstack == NULL) {
	mstack = add_model_stack(model_ID);
	if (mstack == NULL) {
	    return 1;
	}
    }

    if (bufopen(&echo)) {
	return 1;
    }

    echo_cmd(libcmd, datainfo, line, CMD_RECORDING, echo);

    if (add_command_to_mstack(mstack, gretl_print_get_buffer(echo))) {
	err = 1;
    }
    
    gretl_print_destroy(echo);

    return err;
}

void model_command_delete (int model_ID) 
{
    model_stack *mstack;

    mstack = mstack_from_model_id(model_ID);
    if (mstack != NULL && mstack->n > 0) {
	mstack_delete_last_command(mstack);
    }
}

static void dump_model_cmds (const model_stack *mstack, FILE *fp)
{
    int i;

    fprintf(fp, "/* commands pertaining to model %d */\n", mstack->ID);

    for (i=0; i<mstack->n; i++) {
	fprintf(fp, "%s", mstack->cmds[i]);
    }
}

/* get the ID number of a variable operated upon by 
   "genr" or "setinfo" 
*/

static int vnum_from_data_command (const char *s)
{
    
    char vname[VNAMELEN];
    char format[8];
    int offset = 6;
    int v = 0;

    sprintf(format, "%%%ds", VNAMELEN - 1);

    if (!strncmp(s, "genr", 4)) {
	offset = 5;
    } 

    if (sscanf(s + offset, format, vname)) {
	v = varindex(datainfo, vname);
	if (v > datainfo->v - 1) {
	    v = 0;
	}
    }

    return v;
}

static int parse_store_cmd (const char *sline, CMD *scmd)
{
    char *linecpy;
    int err = 0;

    linecpy = gretl_strdup(sline);
    if (linecpy == NULL) {
	return 1;
    }

    err = parse_command_line(linecpy, scmd, &Z, datainfo); 

    free(linecpy);

    return err;
}

/* So far as we can tell, has a given modified variable been
   saved to the current datafile?
*/

static int var_is_stored (int v, int pos)
{
    int i, stored = 0;

    for (i=pos+1; i<n_cmds && !stored; i++) {
	if (!strncmp(cmd_stack[i], "# store", 7)) {
	    CMD scmd;
	    int err;

	    err = gretl_cmd_init(&scmd);
	    if (!err) {
		err = parse_store_cmd(cmd_stack[i] + 2, &scmd);
	    }
	    if (!err && scmd.list != NULL) {
		if (!strcmp(paths.datfile, scmd.param)) {
		    if (in_gretl_list(scmd.list, v)) {
			stored = 1;
		    }
		}
	    }
	    gretl_cmd_free(&scmd);
	}
    }

    return stored;
}

static char *mark_redundant_commands (void)
{
    const char *s;
    char *drop;
    int i;

    drop = calloc(n_cmds, 1);
    if (drop == NULL) {
	return NULL;
    }

    for (i=0; i<n_cmds; i++) {
	s = cmd_stack[i];
	if (!strncmp(s, "genr", 4) || !strncmp(s, "setinfo", 7)) {
	    int v = vnum_from_data_command(s);

	    if (v > 0 && var_is_stored(v, i)) {
		drop[i] = 1;
	    }
	}
    }

    return drop;
}

/* Check: Did we open any datafile in this session?  If not, the
   session may have involved importing data from a database, in which
   case the data may or may not have been saved as a gretl datafile.
   If we're really saving the session for future use, we'd better
   insert an "open" command.
*/

static int maybe_prepend_open_data (FILE *fp)
{
    int i, cancel = 0;

    for (i=0; i<n_cmds; i++) {
	if (!strncmp(cmd_stack[i], "open ", 5)) {
	    return 0;
	}
    }

    if (*paths.datfile == '\0') {
	/* current data not saved yet */
	infobox(_("Please give the current dataset a name"));
	file_selector(_("Save data file"), SAVE_DATA, FSEL_DATA_NONE, NULL);
    }

    if (*paths.datfile != '\0') {
	/* prepend an "open" command for the current data file */
	fprintf(fp, "open %s\n", paths.datfile);
    } else {
	/* the user canceled the saving of the data */
	cancel = 1;
    }

    return cancel;
}

static int creates_model (const char *cmdword)
{
    if (is_model_cmd(cmdword) ||
	!strcmp(cmdword, "addto") ||
	!strcmp(cmdword, "omitfrom")) {
	return 1;
    }

    return 0;
}

/* ship out the stack of commands entered in the current session */

int dump_command_stack (const char *fname, int insert_open_data)
{
    model_stack *mstack;
    char cmdword[9];
    char *cmd_drop;
    FILE *fp;
    int debugging = 0;
    int i, modnum;

    if (fname == NULL || *fname == '\0') {
	return 0;
    }

    if (!strcmp(fname, "stderr")) {
	debugging = 1;
	fp = stderr;
	fputs("dumping command stack:\n", stderr);
    } else {
	fp = gretl_fopen(fname, "w"); 
	if (fp == NULL) {
	    errbox(_("Couldn't open command file for writing"));
	    return 1;
	}
    }

    if (insert_open_data) {
	int cancel = maybe_prepend_open_data(fp);

	if (cancel) {
	    if (!debugging) {
		fclose(fp);
		remove(fname);
	    }
	    return 1;
	}
    }

    cmd_drop = mark_redundant_commands();

    modnum = 0;
    for (i=0; i<n_cmds; i++) {
	if (cmd_drop != NULL && cmd_drop[i]) {
	    continue;
	}

	fprintf(fp, "%s", cmd_stack[i]);

	if (!strncmp(cmd_stack[i], "end nls", 7)) {
	    strcpy(cmdword, "endnls");
	} else {
	    *cmdword = 0;
	    sscanf(cmd_stack[i], "%8s", cmdword);
	}
	
	if (creates_model(cmdword)) {
#if CMD_DEBUG
	    fprintf(stderr, "cmd_stack[%d]: looking for model commands\n", i);
#endif
	    mstack = mstack_from_model_id(++modnum);
	    if (mstack != NULL) {
		dump_model_cmds(mstack, fp);
	    }
	}
    }

    if (!debugging) { 
	fclose(fp);
    }

    if (cmd_drop != NULL) {
	free(cmd_drop);
    }

    return 0;
}

void view_command_log (void)
{
    char logfile[MAXLEN];

    if (n_cmds == 0) {
	errbox(_("The command log is empty"));
	return;
    }

    strcpy(logfile, paths.userdir);
    strcat(logfile, "session.inp");

    if (dump_command_stack(logfile, 0)) {
	return;
    }

    view_file(logfile, 0, 0, 78, 370, VIEW_LOG);
}

/* See whether user has done any work, to determine whether or not to
   offer the option of saving commands/output.  Merely running a
   script, or opening a data file, or a few other trivial actions, do
   not count as "work done".
*/

int work_done (void)
{
    const char *s;
    int i, work = 0;

    for (i=0; i<n_cmds; i++) {
	s = cmd_stack[i];
	if (strlen(s) > 2 && 
	    strncmp(s, "run ", 4) &&
	    strncmp(s, "open", 4) &&
	    strncmp(s, "help", 4) &&
	    strncmp(s, "impo", 4) &&
	    strncmp(s, "info", 4) &&
	    strncmp(s, "labe", 4) &&
	    strncmp(s, "list", 4) &&
	    strncmp(s, "quit", 4)) {
	    work = 1;
	    break;
	}
    }

    return work;
}


syntax highlighted by Code2HTML, v. 0.9.1