/*
 *  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.
 *
 */

/* console.c for gretl */

#include "gretl.h"
#include "console.h"
#include "menustate.h"

#include "libset.h"
#include "monte_carlo.h"
#include "gretl_func.h"
#include "cmd_private.h"

static GtkWidget *console_view;
static PRN *console_prn;
static gchar *cbuf;
static ExecState cstate;

#define DEFAULT_HLINES 32

static char **cmd_history;
static int hl, hlmax, hlines;

static int gretl_console_init (void)
{
    char *hstr;
    int i;

    hlines = 0;

    hstr = getenv("GRETL_HISTORY_LINES");
    if (hstr != NULL) {
	hlines = atoi(hstr);
    }

    if (hlines <= 2 || hlines > 128) {
	hlines = DEFAULT_HLINES;
    }

    cmd_history = mymalloc(hlines * sizeof *cmd_history);
    if (cmd_history == NULL) {
	return E_ALLOC;
    }

    for (i=0; i<hlines; i++) {
	cmd_history[i] = NULL;
    }

    hlmax = 0;
    hl = -1;

    cbuf = NULL;

    set_gretl_echo(1);

    gretl_exec_state_init(&cstate, CONSOLE_EXEC, NULL, 
			  get_lib_cmd(), models, NULL);

    return 0;
}

static void gretl_console_free (GtkWidget *w, gpointer p)
{
    int i;

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

    hlines = hlmax = 0;
    hl = -1;

    if (console_prn != NULL) {
	infobox(_("Closing redirected output file"));
	gretl_print_destroy(console_prn);
	console_prn = NULL;
    }

    free(cbuf);
    cbuf = NULL;
}

static int push_history_line (const char *line)
{
    int i;

    /* drop last entry */
    free(cmd_history[hlines-1]);

    /* push the other lines down one place */
    for (i=hlines-1; i>0; i--) {
	cmd_history[i] = cmd_history[i-1];
    }

    /* add the new line */
    cmd_history[0] = g_strdup(line);
    
    if (hlmax < hlines) {
	hlmax++;
    }

    hl = -1;

    return 0;
}

static void beep (void)
{
#ifdef G_OS_WIN32
    MessageBeep(MB_ICONEXCLAMATION);
#else
    putchar('\a');
    fflush(stdout);
#endif
}

static char *pop_history_line (int keyval)
{
    static int beeptime;
    char *ret = NULL;

    if (keyval == GDK_Up) {
	if (hl < hlmax) hl++;
	if (hl == hlmax) {
	    hl--;
	    beep();
	} else {
	    ret = cmd_history[hl];
	}
    } else if (keyval == GDK_Down) {
	if (hl >= 0) hl--;
	if (hl < 0) {
	    if (beeptime) {
		beep();
	    }
	    beeptime = 1;
	} else {
	    beeptime = 0;
	    ret = cmd_history[hl];
	}
    }

    return ret;
}

static void console_scroll_to_end (GtkTextBuffer *buf, 
				   GtkTextIter *start)
{
    GtkTextMark *mark;

    gtk_text_buffer_place_cursor(buf, start);
    mark = gtk_text_buffer_create_mark(buf, NULL, start, FALSE);
    gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(console_view),
				       mark);
}

enum {
    SAMPLE_RECORD,
    SAMPLE_CHECK
};

static int console_sample_handler (const DATAINFO *pdinfo, int code)
{
    static int pd, t1, t2, ts;
    static double sd0;

    int ret = 0;

    if (code == SAMPLE_RECORD) {
	pd = pdinfo->pd;
	t1 = pdinfo->t1;
	t2 = pdinfo->t2;
	ts = pdinfo->structure;
	sd0 = pdinfo->sd0;
    } else if (code == SAMPLE_CHECK) {
	if (pdinfo->pd != pd ||
	    pdinfo->t1 != t1 ||
	    pdinfo->t2 != t2 ||
	    pdinfo->structure != ts ||
	    pdinfo->sd0 != sd0) {
	    ret = 1;
	}
    }

    return ret;
}

void console_record_sample (const DATAINFO *pdinfo)
{
    console_sample_handler(pdinfo, SAMPLE_RECORD);
}

int console_sample_changed (const DATAINFO *pdinfo)
{
    return console_sample_handler(pdinfo, SAMPLE_CHECK);
}

static int console_vars_changed (const char *line, int oldv, int err)
{
    int ret = 0;

    if (datainfo->v != oldv) {
	ret = 1;
    } else if (!err) {
	if (!strncmp(line, "rename", 6) ||
	    !strncmp(line, "setinfo", 7) ||
	    !strncmp(line, "genr", 4) ||
	    !strncmp(line, "series", 6) ||
	    !strncmp(line, "scalar", 6)) {
	    ret = 1;
	}
    }

    return ret;
}

static void console_exec (void)
{
    GtkTextBuffer *buf;
    GtkTextIter start, end;
    static int redirected;
    int oldv = datainfo->v;
    char execline[MAXLINE];
    int coding = 0;
    int err = 0;

    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(console_view));
    gtk_text_buffer_get_end_iter(buf, &end);
    start = end;
    gtk_text_iter_set_line_offset(&start, 2);

    top_n_tail(cbuf);

    if (strcmp(cbuf, "quit") == 0 || 
	strcmp(cbuf, "q") == 0 ||
	strcmp(cbuf, "exit") == 0) {
	gtk_widget_destroy(console_view->parent->parent->parent);
	g_free(cbuf);
	cbuf = NULL;
	return;
    }

    if (console_prn == NULL && bufopen(&console_prn)) {
	g_free(cbuf);
	cbuf = NULL;
	return;
    }

    *execline = 0;
    strncat(execline, cbuf, MAXLINE - 1);
    g_free(cbuf);
    cbuf = NULL;

    console_record_sample(datainfo);

    push_history_line(execline);

    cstate.line = execline;
    cstate.flags = CONSOLE_EXEC;
    cstate.prn = console_prn;

    /* actually execute the command line */
    err = gui_exec_line(&cstate, &Z, &datainfo);

    while (!err && gretl_execute_loop()) {
	err = gretl_loop_exec(&cstate, &Z, &datainfo);
    }

    redirected = printing_is_redirected(console_prn);

    gtk_text_buffer_get_end_iter(buf, &start);
    gtk_text_buffer_insert(buf, &start, "\n", 1);

    if (!redirected) {
	const char *cbuf = gretl_print_get_buffer(console_prn);

        /* put results into console window */
	if (!g_utf8_validate(cbuf, -1, NULL)) {
	    gchar *trbuf = my_locale_to_utf8(cbuf);

	    fprintf(stderr, "console text did not validate as utf8\n");
	    if (trbuf != NULL) {
		gtk_text_buffer_insert(buf, &start, trbuf, strlen(trbuf));
		g_free(trbuf);
	    }
	} else {
	    gtk_text_buffer_insert(buf, &start, cbuf, strlen(cbuf));
	}
	gretl_print_destroy(console_prn);
	console_prn = NULL;
    } else {
	gretl_print_reset_buffer(console_prn);
    }

    coding = gretl_compiling_loop() || gretl_compiling_function();

    gtk_text_buffer_insert_with_tags_by_name(buf, &start, 
					     (coding)? "> " : "? ", 
					     2, "redtext", NULL);

    /* scroll to end of buffer */
    console_scroll_to_end(buf, &start);

    /* update variable listing in main window if needed */
    if (console_vars_changed(execline, oldv, err)) {
	mark_dataset_as_modified();
	populate_varlist();
    }

    /* update sample info and options if needed */
    if (console_sample_changed(datainfo)) {
	set_sample_label(datainfo);
    }
}

void show_gretl_console (void)
{
    PRN *prn;
    char fname[MAXLEN];
    windata_t *vwin;
    GtkTextBuffer *buf;
    GtkTextIter end;
    const char *intro = 
	N_("gretl console: type 'help' for a list of commands\n? ");

    if (console_view != NULL) {
	gdk_window_show(console_view->parent->window);
	gdk_window_raise(console_view->parent->window);
	return;
    }

    if (user_fopen("console_tmp", fname, &prn)) {
	return;
    }

    if (gretl_console_init()) {
	return;
    }

    pputs(prn, _(intro));
    gretl_print_destroy(prn);

    vwin = view_file(fname, 1, 1, 78, 400, CONSOLE);
    console_view = vwin->w;

    g_signal_connect(G_OBJECT(console_view), "destroy",
		     G_CALLBACK(gretl_console_free),
		     NULL);
    g_signal_connect(G_OBJECT(console_view), "destroy",
		     G_CALLBACK(gtk_widget_destroyed),
		     &console_view);

    /* go to end of last line of text */
    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(console_view));
    gtk_text_buffer_get_end_iter(buf, &end);
    gtk_text_buffer_place_cursor(buf, &end);

    gtk_widget_grab_focus(console_view);
}

#define IS_BACKKEY(k) (k == GDK_BackSpace || k == GDK_Left)

static int bslash_cont (const gchar *line)
{
    int bslash = ends_with_backslash(line);

    if (cbuf == NULL) {
	cbuf = g_strdup(line);
    } else {
	cbuf = g_realloc(cbuf, strlen(cbuf) + 1 + strlen(line));
	if (cbuf != NULL) {
	    strcat(cbuf, line);
	}
    }

    if (cbuf == NULL) {
	return 0;
    }

    if (bslash) {
	char *p = strrchr(cbuf, '\\');

	if (p - cbuf > 0 && !isspace(*(p - 1))) {
	    *p = ' ';
	} else {
	    *p = 0;
	}
    }

    return bslash;
}

gint console_key_handler (GtkWidget *w, GdkEventKey *key, gpointer d)
{
    gint last_line, curr_line, line_pos;
    GdkModifierType mods;
    GtkTextBuffer *buf;
    GtkTextIter iter;

 start_again:

    /* where are we? */
    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(console_view));
    gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_insert(buf));
    curr_line = gtk_text_iter_get_line(&iter);    
    line_pos = gtk_text_iter_get_line_index(&iter);
    last_line = gtk_text_buffer_get_line_count(buf) - 1;

    /* if at start of command line, backspacing does nothing */
    if (IS_BACKKEY(key->keyval) && line_pos < 3) {
	return TRUE;
    }

    /* if not on prompt line, return to (the end of) it */
    if (curr_line < last_line) {
	GtkTextIter end;

	gtk_text_buffer_get_end_iter(buf, &end);
	gtk_text_buffer_place_cursor(buf, &end);
	gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(console_view), TRUE);	
	goto start_again;
    }

    /* make return key execute the command, unless backslash-
       continuation is happening */
    if (key->keyval == GDK_Return) {
	GtkTextIter start, end;	
	gchar *bit;
	int cont = 0;

	start = end = iter;
	gtk_text_iter_set_line_index(&start, 2);
	gtk_text_iter_forward_to_line_end(&end);
	bit = gtk_text_buffer_get_text(buf, &start, &end, FALSE);

	if (bit != NULL) {
	    cont = bslash_cont(bit);
	    g_free(bit);
	}

	if (cont) {
	    gtk_text_buffer_insert_with_tags_by_name(buf, &end, 
						     "\n> ", 3,
						     "redtext", NULL);
	    console_scroll_to_end(buf, &end);
	} else {
	    console_exec();
	}

	key->keyval = GDK_End;
	return FALSE;
    }

    /* up and down arrows: command history */
    if (key->keyval == GDK_Up || key->keyval == GDK_Down) {
	char *histline;

	histline = pop_history_line(key->keyval);

	if (histline != NULL || key->keyval == GDK_Down) {
	    GtkTextIter start, end;

	    start = end = iter;
	    gtk_text_iter_set_line_index(&start, 2);
	    gtk_text_iter_forward_to_line_end(&end);
	    gtk_text_buffer_delete(buf, &start, &end);
	    if (histline != NULL) {
		gtk_text_buffer_insert(buf, &start, histline, strlen(histline));
	    }
	}
	
	return TRUE;
    }

    /* Ctrl-A: go to start of typing area */
    gdk_window_get_pointer(console_view->window, NULL, NULL, &mods);
    if (mods & GDK_CONTROL_MASK && 
	gdk_keyval_to_upper(key->keyval) == GDK_A) {
	GtkTextIter where = iter;

	gtk_text_iter_set_line_index(&where, 2);	
	gtk_text_buffer_place_cursor(buf, &where);
	return TRUE;
    }

    /* tab completion for gretl commands */
    if (key->keyval == GDK_Tab) {
	GtkTextIter start, end;	
	gchar *bit;

	start = end = iter;
	gtk_text_iter_set_line_index(&start, 2);
	gtk_text_iter_forward_to_line_end(&end);
	bit = gtk_text_buffer_get_text(buf, &start, &end, FALSE);

	if (bit != NULL) {
	    if (*bit != 0) {
		const char *complete = gretl_command_complete(bit);

		if (complete != NULL) {
		    gtk_text_buffer_delete(buf, &start, &end);
		    gtk_text_buffer_insert(buf, &start, complete, 
				       strlen(complete));
		}
	    }
	    g_free(bit);
	}
	return TRUE;
    }

    return FALSE;
}

static gint on_last_line (void)
{
    GtkTextBuffer *buf;
    GtkTextIter iter;
    
    buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(console_view));
    gtk_text_buffer_get_iter_at_mark(buf, &iter, gtk_text_buffer_get_insert(buf));

    return (gtk_text_iter_get_line(&iter) == 
	    gtk_text_buffer_get_line_count(buf) - 1);
}

gint console_mouse_handler (GtkWidget *w, GdkEventButton *event,
			    gpointer p)
{
    int lline = on_last_line();

    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(w), lline);

    return FALSE;
}




syntax highlighted by Code2HTML, v. 0.9.1