/*
 *  Copyright (C) 2002 Jorn Baayen
 *
 *  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, 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.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "js-console.h"
#include "prefs-strings.h"
#include "galeon-embed.h"

#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtktextview.h>
#include <gtk/gtktextbuffer.h>

#define CONF_STATE_JS_CONSOLE_SHOW_ERRORS "/apps/galeon/State/js_console_show_errors"
#define CONF_STATE_JS_CONSOLE_SHOW_WARNINGS "/apps/galeon/State/js_console_show_warnings"
#define CONF_STATE_JS_CONSOLE_SHOW_MESSAGES "/apps/galeon/State/js_console_show_messages"

static void javascript_console_class_init (JavascriptConsoleClass *klass);
static void javascript_console_init (JavascriptConsole *dialog);
static void javascript_console_finalize (GObject *object);

static void
impl_construct (GaleonDialog *dialog,
                const GaleonDialogProperty *properties,
                const char *file,
                const char *name);
static void
impl_destruct (GaleonDialog *dialog);
static void
impl_show (GaleonDialog *dialog);

/* Glade callbacks */
void
js_console_close_button_clicked_cb (GtkWidget *button, 
			            GaleonDialog *dialog); 
void
js_console_clear_button_clicked_cb (GtkWidget *button, 
			            GaleonDialog *dialog);
void
js_console_evaluate_button_clicked_cb (GtkWidget *button, 
			               GaleonDialog *dialog); 
void
js_console_filtering_button_clicked_cb (GtkWidget *button, 
			                GaleonDialog *dialog);


/**
 * Msg: this structure contains information for a JavaScript message
 */
typedef struct
{
	JSConsoleMessageType msg_type;
        gchar *type;     /* error type string, for example Error  */
        gchar *text;     /* the error message itself              */
        guint column;    /* the column that should be highlighted */
} Msg;

static GObjectClass *parent_class = NULL;

struct JavascriptConsolePrivate
{
	gboolean constructed;
	GList *errors;
	GtkTextBuffer *buffer;
	gboolean block_update;
	
	gboolean show_errors;
	gboolean show_messages;
	gboolean show_warnings;
};

enum
{
	COMMAND_PROP,
	WARNINGS_PROP,
	ERRORS_PROP,
	MESSAGES_PROP,
	TEXT_PROP
};
 
static const
GaleonDialogProperty properties [] =
{
	{ COMMAND_PROP, "command_entry", NULL, PT_NORMAL, NULL },
	{ WARNINGS_PROP, "warnings_togglebutton", CONF_STATE_JS_CONSOLE_SHOW_WARNINGS, PT_NORMAL, NULL },
	{ WARNINGS_PROP, "errors_togglebutton", CONF_STATE_JS_CONSOLE_SHOW_ERRORS, PT_NORMAL, NULL },
	{ MESSAGES_PROP, "message_togglebutton", CONF_STATE_JS_CONSOLE_SHOW_MESSAGES, PT_NORMAL, NULL },
	{ TEXT_PROP, "console_textview", NULL, PT_NORMAL, NULL },
	
	{ -1, NULL, NULL }
};

enum
{
	EVALUATE,
	LAST_SIGNAL
};

static guint js_console_signals[LAST_SIGNAL] = { 0 };

GType 
javascript_console_get_type (void)
{
        static GType javascript_console_type = 0;

        if (javascript_console_type == 0)
        {
                static const GTypeInfo our_info =
                {
                        sizeof (JavascriptConsoleClass),
                        NULL, /* base_init */
                        NULL, /* base_finalize */
                        (GClassInitFunc) javascript_console_class_init,
                        NULL,
                        NULL, /* class_data */
                        sizeof (JavascriptConsole),
                        0, /* n_preallocs */
                        (GInstanceInitFunc) javascript_console_init
                };

                javascript_console_type = g_type_register_static (GALEON_EMBED_DIALOG_TYPE,
						           "JavascriptConsole",
						           &our_info, 0);
        }

        return javascript_console_type;

}

GaleonDialog *
js_console_new (void)
{
	JavascriptConsole *dialog;
	
	dialog = JAVASCRIPT_CONSOLE (g_object_new (JAVASCRIPT_CONSOLE_TYPE, 
				     NULL));

	return GALEON_DIALOG(dialog);
}

static void
javascript_console_class_init (JavascriptConsoleClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
	GaleonDialogClass *galeon_dialog_class;
	
        parent_class = g_type_class_peek_parent (klass);
	galeon_dialog_class = GALEON_DIALOG_CLASS (klass);
	
        object_class->finalize = javascript_console_finalize;

	galeon_dialog_class->construct = impl_construct;
	galeon_dialog_class->destruct = impl_destruct;
	galeon_dialog_class->show = impl_show;

	js_console_signals[EVALUATE] =
                g_signal_new ("evaluate",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (JavascriptConsoleClass, evaluate),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__STRING,
                              G_TYPE_NONE,
			      1,
			      G_TYPE_STRING);
}

static void
js_console_append_msg (Msg *msg,
		       JavascriptConsole *dialog)
{
	GtkTextIter iter;
	GtkTextIter iter2;
	const char *style = NULL;
	int offset;
	GtkTextBuffer *buffer = dialog->priv->buffer;

	/* filter */
	if ((msg->msg_type == JS_CONSOLE_ERROR) && !dialog->priv->show_errors)
		return;
	if ((msg->msg_type == JS_CONSOLE_WARNING) && !dialog->priv->show_warnings)
		return;
	if ((msg->msg_type == JS_CONSOLE_MESSAGE) && !dialog->priv->show_messages)
		return;
	
	gtk_text_buffer_get_end_iter (buffer, &iter);
	offset = gtk_text_iter_get_offset (&iter);
	gtk_text_buffer_insert (buffer, &iter, msg->text, -1);

	gtk_text_buffer_get_iter_at_offset (buffer, &iter,
					    offset);
	gtk_text_buffer_get_end_iter (buffer, &iter2);

	switch (msg->msg_type)
        {
        case JS_CONSOLE_ERROR:
		style = "errors";
                break;
        case JS_CONSOLE_WARNING:
		style = "warnings";
                break;
        case JS_CONSOLE_MESSAGE:
		style = "message";
                break;
        }

	gtk_text_buffer_apply_tag_by_name (buffer, style, &iter, &iter2);

	iter2 = iter;
	gtk_text_iter_forward_chars (&iter2, 1);
	gtk_text_buffer_apply_tag_by_name (buffer, "hilighted", &iter, &iter2);
}

/**
 * js_console_append: append a message to the js console
 */
void
javascript_console_append (JavascriptConsole *jc, 
			   const gchar *text, 
			   JSConsoleMessageType type, 
			   guint column)
{
        Msg *msg;

        /* allocate new Msg */
        msg = g_new0 (Msg, 1);

        /* set the column error point */
        msg->column = column;

        /* set the error text */
        if (text[strlen (text) - 1] != '\n')
        {
                /* no newline at the end of the text, append one */
                msg->text = g_strconcat (text, "\n", NULL);
        }
        else
        {
                msg->text = g_strdup (text);
        }
        
	msg->msg_type = type;
	
        /* set the type text & color */
        switch (type)
        {
        case JS_CONSOLE_ERROR:
                /* this is an error, set string, color, and append
                 * to the list of errors */
                msg->type = g_strdup (_("Error: "));
                break;
        case JS_CONSOLE_WARNING:
                /* this is an warning, set string, color, and append
                 * to the list of warnings */
                msg->type = g_strdup (_("Warning: "));
                break;
        case JS_CONSOLE_MESSAGE:
                /* this is an message, set string, color, and append
                 * to the list of messages */
                msg->type = g_strdup ("");
                break;
        default:
                /* shouldn't get here */
                g_warning ("unknown js message type\n");
                break;
        }
	
	jc->priv->errors = g_list_append (jc->priv->errors, msg);
		
	if (jc->priv->constructed)
	{
		js_console_append_msg (msg, jc);
	}
}

static void
update_filtering_info (JavascriptConsole *dialog)
{
	GValue warnings = {0, };
	GValue errors = {0, };
	GValue messages = {0, };

	galeon_dialog_get_value (GALEON_DIALOG(dialog), WARNINGS_PROP, &warnings);
	galeon_dialog_get_value (GALEON_DIALOG(dialog), ERRORS_PROP, &errors);
	galeon_dialog_get_value (GALEON_DIALOG(dialog), MESSAGES_PROP, &messages);

	dialog->priv->show_warnings = g_value_get_boolean (&warnings);
	dialog->priv->show_errors = g_value_get_boolean (&errors);
	dialog->priv->show_messages = g_value_get_boolean (&messages);
}

static void
clear_textview (JavascriptConsole *dialog)
{
	GtkTextIter iter1, iter2;
	gtk_text_buffer_get_bounds (dialog->priv->buffer, 
				    &iter1, &iter2);
	gtk_text_buffer_delete (dialog->priv->buffer, 
				&iter1, &iter2);
}

static void
fill_textview (JavascriptConsole *dialog)
{
	g_list_foreach (dialog->priv->errors, 
			(GFunc)js_console_append_msg, 
			dialog);
}

static void
init_textview (JavascriptConsole *dialog)
{
	GtkWidget *textview;
	GdkColor color;
	GtkTextBuffer *buffer;
	
	textview = galeon_dialog_get_control 
		(GALEON_DIALOG(dialog), TEXT_PROP);
	buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));

	dialog->priv->buffer = buffer;

  	color.red = 0xffff;
  	color.blue = 0;
  	color.green = 0;
	
  	gtk_text_buffer_create_tag (buffer, "errors",
        	                    "foreground_gdk", &color,
                             	     NULL);
	
	color.red = 0xffBD;
  	color.blue = 0xffAE;
  	color.green = 0xff7B;
	
  	gtk_text_buffer_create_tag (buffer, "warnings",
        	                    "foreground_gdk", &color,
                             	     NULL);

	color.red = 0;
  	color.blue = 0xff64;
  	color.green = 0;
	
  	gtk_text_buffer_create_tag (buffer, "message",
        	                    "foreground_gdk", &color,
                             	     NULL);

	color.red = 0xffff;
  	color.blue = 0xffa0;
  	color.green = 0xffa0;
	
  	gtk_text_buffer_create_tag (buffer, "hilighted",
        	                    "background_gdk", &color,
                             	     NULL);

	update_filtering_info (dialog);
	fill_textview (dialog);
	
	
}

static void
ensure_constructed (JavascriptConsole *dialog)
{

	if (!dialog->priv->constructed)
	{
		dialog->priv->block_update = TRUE;
		
		galeon_dialog_construct (GALEON_DIALOG(dialog),
					 properties,
					 "galeon.glade", 
					 "js_console");

		dialog->priv->block_update = FALSE;
		
		init_textview (dialog);
	}
}

static void
javascript_console_init (JavascriptConsole *dialog)
{		
	dialog->priv = g_new0 (JavascriptConsolePrivate, 1);

	dialog->priv->constructed = FALSE;
	dialog->priv->block_update = TRUE;
}

static void
impl_construct (GaleonDialog *dialog,
                const GaleonDialogProperty *properties,
                const char *file,
                const char *name)
{
	JAVASCRIPT_CONSOLE(dialog)->priv->constructed = TRUE;

	GALEON_DIALOG_CLASS (parent_class)->construct (dialog, properties, file, name);
}

static void
impl_destruct (GaleonDialog *dialog)
{
	JAVASCRIPT_CONSOLE(dialog)->priv->constructed = FALSE;

	GALEON_DIALOG_CLASS (parent_class)->destruct (dialog);
}

static void
impl_show (GaleonDialog *dialog)
{
	JavascriptConsole *javascript_console = JAVASCRIPT_CONSOLE(dialog);
	ensure_constructed (javascript_console);

	GALEON_DIALOG_CLASS (parent_class)->show (dialog);
}


static void
free_js_console_msg (Msg *msg)
{
	g_free (msg->type);
	g_free (msg->text);
	g_free (msg);
}

static void
javascript_console_finalize (GObject *object)
{
	JavascriptConsole *dialog;

        g_return_if_fail (object != NULL);
        g_return_if_fail (IS_JAVASCRIPT_CONSOLE (object));

	dialog = JAVASCRIPT_CONSOLE (object);

        g_return_if_fail (dialog->priv != NULL);

	g_list_foreach (dialog->priv->errors, (GFunc)free_js_console_msg, NULL);
	g_list_free (dialog->priv->errors);

        g_free (dialog->priv);

        G_OBJECT_CLASS (parent_class)->finalize (object);
}

void
js_console_close_button_clicked_cb (GtkWidget *button, 
			            GaleonDialog *dialog) 
{
	galeon_dialog_destruct (dialog);
}

void
js_console_clear_button_clicked_cb (GtkWidget *button, 
			            GaleonDialog *dialog) 
{
	JavascriptConsole *js_console = JAVASCRIPT_CONSOLE(dialog);

	clear_textview (js_console);
	g_list_foreach (js_console->priv->errors, (GFunc)free_js_console_msg, NULL);
	g_list_free (js_console->priv->errors);
	js_console->priv->errors = NULL;
}

void
js_console_evaluate_button_clicked_cb (GtkWidget *button, 
			               GaleonDialog *dialog) 
{
	GValue command = {0, };
	const char *cmd;
	
	galeon_dialog_get_value (GALEON_DIALOG(dialog), COMMAND_PROP, &command);

	cmd = g_value_get_string (&command);
	g_signal_emit (G_OBJECT (dialog), js_console_signals[EVALUATE], 0, cmd);
	g_value_unset (&command);
}

void
js_console_filtering_button_clicked_cb (GtkWidget *button, 
			                GaleonDialog *dialog) 
{
	if (JAVASCRIPT_CONSOLE(dialog)->priv->block_update) return;
	
	clear_textview (JAVASCRIPT_CONSOLE(dialog));
	update_filtering_info (JAVASCRIPT_CONSOLE(dialog));
	fill_textview (JAVASCRIPT_CONSOLE(dialog));
}
