/* The GIMP -- an image manipulation program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * 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.
 */

#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/stat.h>


#ifndef GTK_ENABLE_BROKEN
#define GTK_ENABLE_BROKEN
#endif
#include "gdk/gdkkeysyms.h"
#include "gtk/gtk.h"

#include "libgimp/gimp.h"
#include "libgimp/gimpui.h"

#include "script-fu-intl.h"

#include "siod.h"
#include "script-fu-console.h"

#include <plug-ins/dbbrowser/dbbrowser_utils.h>

#ifdef G_OS_WIN32
#include <fcntl.h>
#include <io.h>
#endif

#define TEXT_WIDTH  400
#define TEXT_HEIGHT 400
#define ENTRY_WIDTH 400

#define BUFSIZE 256

void
gimp_table_attach_aligned (GtkTable    *table,
                           gint         column,
                           gint         row,
                           const gchar *label_text,
                           gfloat       xalign,
                           gfloat       yalign,
                           GtkWidget   *widget,
                           gint         colspan,
                           gboolean     left_align);


typedef struct
{
  GtkWidget *console;
  GtkWidget *cc;
  GtkAdjustment *vadj;

  GdkFont   *font_strong;
  GdkFont   *font_emphasis;
  GdkFont   *font_weak;
  GdkFont   *font;

  gint32     input_id;
} ConsoleInterface;

/*
 *  Local Functions
 */

static void  script_fu_console_interface (void);
static void  script_fu_close_callback    (GtkWidget        *widget,
					  gpointer          data);
static void  script_fu_browse_callback    (GtkWidget        *widget,
					  gpointer          data);
static gboolean script_fu_siod_read      (GIOChannel  *channel,
					  GIOCondition cond,
					  gpointer     data);
static gint  script_fu_cc_is_empty       (void);
static gint  script_fu_cc_key_function   (GtkWidget         *widget,
					  GdkEventKey       *event,
					  gpointer           data);

static FILE *script_fu_open_siod_console (void);
static void  script_fu_close_siod_console(void);

/*
 *  Local variables
 */

static ConsoleInterface cint =
{
  NULL,  /*  console  */
  NULL,  /*  current command  */
  NULL,  /*  vertical adjustment  */

  NULL,  /*  strong font  */
  NULL,  /*  emphasis font  */
  NULL,  /*  weak font  */
  NULL,  /*  normal font  */

  -1     /*  input id  */
};

static char   read_buffer[BUFSIZE];
static GList *history = NULL;
static int    history_len = 0;
static int    history_cur = 0;
static int    history_max = 50;

static int   siod_output_pipe[2];
extern long  siod_verbose_level;
extern char  siod_err_msg[];
extern FILE *siod_output;


#define message(string) printf("(%s): %d ::: %s\n", __PRETTY_FUNCTION__, __LINE__, string)

/*
 *  Function definitions
 */

void
script_fu_console_run (char     *name,
		       int       nparams,
		       GimpParam   *params,
		       int      *nreturn_vals,
		       GimpParam  **return_vals)
{
  static GimpParam values[1];
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  GimpRunModeType run_mode;

  run_mode = params[0].data.d_int32;

  switch (run_mode)
    {
    case GIMP_RUN_INTERACTIVE:
      /*  Enable SIOD output  */
      script_fu_open_siod_console ();

      /*  Run the interface  */
      script_fu_console_interface ();

      /*  Clean up  */
      script_fu_close_siod_console ();
      break;

    case GIMP_RUN_WITH_LAST_VALS:
    case GIMP_RUN_NONINTERACTIVE:
      status = GIMP_PDB_CALLING_ERROR;
      gimp_message (_("Script-Fu console mode allows only interactive invocation"));
      break;

    default:
      break;
    }

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
}

static void
script_fu_console_interface (void)
{
  GtkWidget *dlg;
  GtkWidget *button;
  GtkWidget *label;
  GtkWidget *vsb;
  GtkWidget *table;
  GtkWidget *hbox;
  GIOChannel *input_channel;

  INIT_I18N_UI();
  textdomain("cinepaint-script-fu");

  gimp_ui_init ("script-fu", FALSE);

  dlg = gtk_dialog_new ();
  gtk_window_set_title (GTK_WINDOW (dlg), _("Script-Fu Console"));
  gimp_help_connect_help_accel (dlg, gimp_standard_help_func, 
				"filters/script-fu.html");
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      (GtkSignalFunc) script_fu_close_callback,
		      NULL);
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
		      GTK_SIGNAL_FUNC (gtk_widget_destroyed),
		      &dlg);
  gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->vbox), 2);
  gtk_container_set_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area), 0);

  /*  Action area  */
  button = gtk_button_new_with_label (_("Close"));
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     (gpointer)dlg);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button, TRUE, TRUE, 0);
  gtk_widget_show (button);

  /*  The info vbox  */
  label = gtk_label_new (_("SIOD Output"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), label, FALSE, TRUE, 0);
  gtk_widget_show (label);

  /*  The output text widget  */
  cint.vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
  vsb = gtk_vscrollbar_new (cint.vadj);
  cint.console = gtk_text_new (NULL, cint.vadj);
  gtk_text_set_editable (GTK_TEXT (cint.console), FALSE);
  gtk_widget_set_usize (cint.console, TEXT_WIDTH, TEXT_HEIGHT);

  table  = gtk_table_new (1, 2, FALSE);
  gtk_table_set_col_spacing (GTK_TABLE (table), 0, 2);

  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), table, TRUE, TRUE, 0);

  gtk_table_attach (GTK_TABLE (table), vsb, 1, 2, 0, 1,
		    0, GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);
  gtk_table_attach (GTK_TABLE (table), cint.console, 0, 1, 0, 1,
		    GTK_EXPAND | GTK_SHRINK | GTK_FILL,
		    GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0, 0);

  gtk_container_set_border_width (GTK_CONTAINER (table), 2);

  cint.font_strong = gdk_font_load ("-*-helvetica-bold-r-normal-*-*-120-*-*-*-*-*-*");
  cint.font_emphasis = gdk_font_load ("-*-helvetica-medium-o-normal-*-*-100-*-*-*-*-*-*");
  cint.font_weak = gdk_font_load ("-*-helvetica-medium-r-normal-*-*-100-*-*-*-*-*-*");
  cint.font = gdk_fontset_load ("-*-*-medium-r-normal-*-*-100-*-*-c-*-iso8859-1,*");

  /*  Realize the widget before allowing new text to be inserted  */
  gtk_widget_realize (cint.console);

  gtk_text_insert (GTK_TEXT (cint.console), cint.font_strong, NULL, NULL,
		   "The GIMP - GNU Image Manipulation Program\n\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_emphasis, NULL, NULL,
		   "Copyright (C) 1995-2001\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_emphasis, NULL, NULL,
		   "Spencer Kimball, Peter Mattis and the GIMP Development Team\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "This program is free software; you can redistribute it and/or modify\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "it under the terms of the GNU General Public License as published by\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "the Free Software Foundation; either version 2 of the License, or\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "(at your option) any later version.\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "This program is distributed in the hope that it will be useful,\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "but WITHOUT ANY WARRANTY; without even the implied warranty of\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "See the GNU General Public License for more details.\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "You should have received a copy of the GNU General Public License\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "along with this program; if not, write to the Free Software\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.\n\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_strong, NULL, NULL,
		   "Welcome to SIOD, Scheme In One Defun\n", -1); 
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "(C) Copyright 1988-1994 Paradigm Associates Inc.\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		   "\n\n", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_strong, NULL, NULL,
		   "Script-Fu Console - ", -1);
  gtk_text_insert (GTK_TEXT (cint.console), cint.font_emphasis, NULL, NULL,
		   "Interactive Scheme Development\n\n", -1);

  gtk_widget_show (vsb);
  gtk_widget_show (cint.console);
  gtk_widget_show (table);

  /*  The current command  */
  label = gtk_label_new (_("Current Command"));
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), label, FALSE, TRUE, 0);
  gtk_widget_show (label);

  hbox = gtk_hbox_new ( FALSE, 0 );
  gtk_widget_set_usize (hbox, ENTRY_WIDTH, 0);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), hbox, FALSE, TRUE, 0);
  gtk_widget_show (hbox);
    
  cint.cc = gtk_entry_new ();
  
  gtk_box_pack_start (GTK_BOX (hbox), cint.cc, 
		      TRUE, TRUE, 0);
/*    gtk_widget_set_usize (cint.cc, (ENTRY_WIDTH*5)/6, 0);  */
  GTK_WIDGET_SET_FLAGS (cint.cc, GTK_CAN_DEFAULT);
  gtk_widget_grab_default (cint.cc);
  gtk_signal_connect (GTK_OBJECT (cint.cc), "key_press_event",
		      (GtkSignalFunc) script_fu_cc_key_function,
		      NULL);

  button = gtk_button_new_with_label (_("Browse..."));
/*    gtk_widget_set_usize (button, (ENTRY_WIDTH)/6, 0); */
  gtk_box_pack_start (GTK_BOX (hbox), button, 
		      FALSE, TRUE, 0);
  gtk_signal_connect (GTK_OBJECT (button), "clicked",
		      (GtkSignalFunc) script_fu_browse_callback,
		      NULL);
  gtk_widget_show (button);
  gtk_widget_show (cint.cc);

  input_channel = g_io_channel_unix_new (siod_output_pipe[0]);
  cint.input_id = g_io_add_watch (input_channel,
				  G_IO_IN,
				  script_fu_siod_read,
				  NULL);

  /*  Initialize the history  */
  history = g_list_append (history, NULL);
  history_len = 1;

  gtk_widget_show (dlg);

  gtk_main ();

  g_source_remove (cint.input_id);
  if (dlg)
    gtk_widget_destroy (dlg);
  gdk_flush ();
}

static void
script_fu_close_callback (GtkWidget *widget,
			  gpointer   data)
{
  gtk_main_quit ();
}

void 
apply_callback (gchar           *proc_name,
		gchar           *scheme_proc_name,
		gchar           *proc_blurb,
		gchar           *proc_help,
		gchar           *proc_author,
		gchar           *proc_copyright,
		gchar           *proc_date,
		GimpPDBProcType  proc_type,
		gint             nparams,
		gint             nreturn_vals,
		GimpParamDef    *params,
		GimpParamDef    *return_vals)
{
  gint i;
  GString *text;

  if (proc_name == NULL) 
    return;
  
  text = g_string_new ("(");
  text = g_string_append (text, scheme_proc_name);
  for (i=0; i<nparams; i++) 
    {
      text = g_string_append_c (text, ' ');
      text = g_string_append (text, params[i].name);
    }
  text = g_string_append_c (text, ')');

  gtk_entry_set_text (GTK_ENTRY (cint.cc), text->str);
  g_string_free (text, TRUE);
}

static void
script_fu_browse_callback (GtkWidget *widget,
			   gpointer   data)
{
  gtk_quit_add_destroy (1, (GtkObject*) gimp_db_browser (apply_callback));
}

static gint
script_fu_console_scroll_end (gpointer data)
{
  /* The Text widget in 1.0.1 doesn't like being scrolled before
   * it is size-allocated, so we wait for it
   */
  if ((cint.console->allocation.width > 1) && 
      (cint.console->allocation.height > 1))
    {
      cint.vadj->value = cint.vadj->upper - cint.vadj->page_size;
      gtk_signal_emit_by_name (GTK_OBJECT (cint.vadj), "changed");
    }
  else
    gtk_idle_add (script_fu_console_scroll_end, NULL);
  
  return FALSE;
}

static gboolean
script_fu_siod_read (GIOChannel  *channel,
		     GIOCondition cond,
		     gpointer     data)
{
  int count;
  static int hack = 0;
  GIOError error;

  count = 0;
  error = g_io_channel_read (channel, read_buffer, BUFSIZE - 1, &count);

  if (error == G_IO_ERROR_NONE)
    {
#ifndef G_OS_WIN32
      /* Is this needed any longer on Unix? */
      if (!hack) /* this is a stupid hack, but as of 10/27/98
		 * the script-fu-console will hang on my system without it.
		 * the real cause of this needs to be tracked down.
		 * posibly a problem with the text widget, or the
		 * signal handlers not getting fully initialized or...
		 * no reports of hangs on other platforms, could just be a
		 * problem with my system.
		 */
      {
	hack = 1;
	return TRUE;
      }
#endif
      read_buffer[count] = '\0';
      gtk_text_freeze (GTK_TEXT (cint.console));
      gtk_text_insert (GTK_TEXT (cint.console), cint.font_weak, NULL, NULL,
		       read_buffer, -1);
      gtk_text_thaw (GTK_TEXT (cint.console));

      script_fu_console_scroll_end (NULL);
    }
  return TRUE;
}

static gint
script_fu_cc_is_empty (void)
{
  char *str;

  if ((str = gtk_entry_get_text (GTK_ENTRY (cint.cc))) == NULL)
    return TRUE;

  while (*str)
    {
      if (*str != ' ' && *str != '\t' && *str != '\n')
	return FALSE;

      str ++;
    }

  return TRUE;
}

static gint
script_fu_cc_key_function (GtkWidget   *widget,
			   GdkEventKey *event,
			   gpointer     data)
{
  GList *list;
  int direction = 0;

  switch (event->keyval)
    {
    case GDK_Return:
      gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");

      if (script_fu_cc_is_empty ())
	return TRUE;

      list = g_list_nth (history, (g_list_length (history) - 1));
      if (list->data)
	g_free (list->data);
      list->data = g_strdup (gtk_entry_get_text (GTK_ENTRY (cint.cc)));

      gtk_text_freeze (GTK_TEXT (cint.console));
      gtk_text_insert (GTK_TEXT (cint.console), cint.font_strong, NULL, NULL, "=> ", -1);
      gtk_text_insert (GTK_TEXT (cint.console), cint.font, NULL, NULL,
		       gtk_entry_get_text (GTK_ENTRY (cint.cc)), -1);
      gtk_text_insert (GTK_TEXT (cint.console), cint.font, NULL, NULL, "\n\n", -1);
      gtk_text_thaw (GTK_TEXT (cint.console));

      cint.vadj->value = cint.vadj->upper - cint.vadj->page_size;
      gtk_signal_emit_by_name (GTK_OBJECT (cint.vadj), "changed");

      gtk_entry_set_text (GTK_ENTRY (cint.cc), "");
      gdk_flush ();

      repl_c_string ((char *) list->data, 0, 0, 1);
      gimp_displays_flush ();

      history = g_list_append (history, NULL);
      if (history_len == history_max)
	{
	  history = g_list_remove (history, history->data);
	  if (history->data)
	    g_free (history->data);
	}
      else
	history_len++;
      history_cur = g_list_length (history) - 1;

      return TRUE;
      break;

    case GDK_KP_Up:
    case GDK_Up:
      gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
      direction = -1;
      break;

    case GDK_KP_Down:
    case GDK_Down:
      gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
      direction = 1;
      break;

    case GDK_P:
    case GDK_p:
      if (event->state & GDK_CONTROL_MASK)
	{
	  gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
	  direction = -1;
	}
      break;

    case GDK_N:
    case GDK_n:
      if (event->state & GDK_CONTROL_MASK)
	{
	  gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
	  direction = 1;
	}
      break;

    default:
      break;
    }

  if (direction)
    {
      /*  Make sure we keep track of the current one  */
      if (history_cur == g_list_length (history) - 1)
	{
	  list = g_list_nth (history, history_cur);
	  if (list->data)
	    g_free (list->data);
	  list->data = g_strdup (gtk_entry_get_text (GTK_ENTRY (cint.cc)));
	}

      history_cur += direction;
      if (history_cur < 0)
	history_cur = 0;
      if (history_cur >= history_len)
	history_cur = history_len - 1;

      gtk_entry_set_text (GTK_ENTRY (cint.cc), (char *) (g_list_nth (history, history_cur))->data);

      return TRUE;
    }

  return FALSE;
}


static FILE *
script_fu_open_siod_console (void)
{
  if (siod_output == stdout)
    {
      if (pipe (siod_output_pipe))
	{
	  gimp_message (_("Unable to open SIOD output pipe"));
	}
      else if ((siod_output = fdopen (siod_output_pipe [1], "w")) == NULL)
	{
	  gimp_message (_("Unable to open a stream on the SIOD output pipe"));
	  siod_output = stdout;
	}
      else
	{
	  siod_verbose_level = 2;
	  print_welcome ();
	}
    }

  return siod_output;
}

static void
script_fu_close_siod_console (void)
{
  if (siod_output != stdout)
    fclose (siod_output);
  close (siod_output_pipe[0]);
  close (siod_output_pipe[1]);
}

void
script_fu_eval_run (char     *name,
		    int       nparams,
		    GimpParam   *params,
		    int      *nreturn_vals,
		    GimpParam  **return_vals)
{
  static GimpParam values[1];
  GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  GimpRunModeType run_mode;

  run_mode = params[0].data.d_int32;

  switch (run_mode)
    {
    case GIMP_RUN_NONINTERACTIVE:
      if (repl_c_string (params[1].data.d_string, 0, 0, 1) != 0)
	status = GIMP_PDB_EXECUTION_ERROR;
      break;

    case GIMP_RUN_INTERACTIVE:
    case GIMP_RUN_WITH_LAST_VALS:
      status = GIMP_PDB_CALLING_ERROR;
      gimp_message (_("Script-Fu evaluate mode allows only noninteractive invocation"));
      break;

    default:
      break;
    }

  *nreturn_vals = 1;
  *return_vals = values;

  values[0].type = GIMP_PDB_STATUS;
  values[0].data.d_status = status;
}
