/* texteditors.c
 * text editors for editing scripts, adding comments etc
 *
 * for Denemo, a gtk+ frontend to GNU Lilypond
 * (c) 2009  Richard Shann
 * 
 */

#include <gtksourceview/gtksourceview.h>
#include <gtksourceview/gtksourcelanguage.h>
#include <gtksourceview/gtksourcelanguagemanager.h>
#include <gtksourceview/gtksourcestyleschememanager.h>
#include <gtksourceview/gtksourceprintcompositor.h>
#if GTK_MAJOR_VERSION==3
#include <gdk/gdkkeysyms-compat.h>      //FIXME Look for something more gtk3 like
#else
#include <gtksourceview/gtksourceiter.h>
#endif
#include <gtksourceview/gtksourcebuffer.h>
#include "texteditors.h"
#include "view.h"

static void find_cb (GtkAction * action, gpointer user_data);

static void replace_cb (GtkAction * action, gpointer user_data);

/* returns newly allocated string containing current Scheme in the ScriptView
 caller must free
*/
gchar *
getSchemeText (void)
{
  GtkTextIter startiter, enditer;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  gtk_text_buffer_get_start_iter (buffer, &startiter);
  gtk_text_buffer_get_end_iter (buffer, &enditer);
  return gtk_text_buffer_get_text (buffer, &startiter, &enditer, FALSE);

}

/* execute the script that is in the Scheme script window */
void
executeScript (void)
{
  gchar *text = getSchemeText ();
  g_debug ("Calling script %s\n", text);
  stage_undo (Denemo.gui->si, ACTION_STAGE_END);        //undo is a queue so this is the end :)
  (void) call_out_to_guile (text);
  stage_undo (Denemo.gui->si, ACTION_STAGE_START);
  g_free (text);
}


/* execute the line of scheme script that is in the Scheme CLI */
void
executeCLI (GtkWidget * button, GtkEntry * entry)
{
  gchar *display = NULL;
  if (entry)
    {
      display = g_strdup_printf ("%s%s%s", "(format #t \"~%=> ~A~%\"", gtk_entry_get_text (entry), ")\n");
      //g_print("displaying %s\n", display);
      (void) call_out_to_guile (display);
      g_free (display);
    }
  else
    g_critical ("entry is NULL!!!!");
}




/* Return number of characters in Scheme script */
gint
getNumCharsSchemeText (void)
{
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  return gtk_text_buffer_get_char_count (buffer);
}

void
deleteSchemeText (void)
{
  GtkTextIter startiter, enditer;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  gtk_text_buffer_get_start_iter (buffer, &startiter);
  gtk_text_buffer_get_end_iter (buffer, &enditer);
  gtk_text_buffer_delete (buffer, &startiter, &enditer);
}

void
appendSchemeText (gchar * text)
{
  GtkTextIter enditer;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer ((GtkTextView *) (Denemo.ScriptView));
  gtk_text_buffer_get_end_iter (buffer, &enditer);
  gtk_text_buffer_insert (buffer, &enditer, text, -1);
}

static gint
hide_scheme (GtkAction * action, GdkEvent * event, GtkWidget * w)
{
  activate_action ("/MainMenu/ViewMenu/ToggleScript");
  return TRUE;
}


static void
save_scheme_text_as (GtkWidget * widget, GtkWidget * textview)
{
  gchar **pfilename = g_object_get_data (G_OBJECT (textview), "pfilename");
  gchar *text = getSchemeText ();
  GtkWidget *label;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  GtkWidget *dialog = gtk_file_chooser_dialog_new ("Save Scheme Text as...",
                                                   NULL /*GTK_WINDOW(gtk_text_view_get_window(GTK_TEXT_VIEW(Denemo.ScriptView), GTK_TEXT_WINDOW_WIDGET)) */ ,
                                                   GTK_FILE_CHOOSER_ACTION_SAVE,
                                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                                   GTK_STOCK_SAVE, GTK_RESPONSE_OK, NULL);

  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
    {
      g_free (*pfilename);
      *pfilename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));

      if (g_file_test (*pfilename, G_FILE_TEST_EXISTS))
        {
          gtk_widget_destroy (dialog);
          dialog = gtk_dialog_new_with_buttons ("File already exists",  //FIXME I think there is a function to do this already.
                                                NULL /*GTK_WINDOW(gtk_text_view_get_window(GTK_TEXT_VIEW(Denemo.ScriptView), GTK_TEXT_WINDOW_WIDGET)) */ ,
                                                GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_OK, GTK_RESPONSE_OK, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);
          gchar *labeltext = g_strconcat ("\nThe file ", *pfilename, " already exists.\n Do you want to overwrite it?\n\n", NULL);
          label = gtk_label_new (labeltext);
          g_free (labeltext);
          GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
          gtk_container_add (GTK_CONTAINER (content_area), label);
          gtk_widget_show_all (dialog);

          if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_CANCEL)
            {
              g_free (text);
              gtk_widget_destroy (dialog);
              return;
            }
        }
      g_file_set_contents (*pfilename, text, -1, NULL);
      gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (buffer), FALSE);
    }
  g_free (text);
  gtk_widget_destroy (dialog);
}


static void
save_scheme_text (GtkWidget * widget, GtkWidget * textview)
{
  gchar **pfilename = g_object_get_data (G_OBJECT (textview), "pfilename");
  GtkTextBuffer *buffer;
  if (*pfilename == NULL)
    save_scheme_text_as (NULL, textview);
  else
    {
      gchar *text = getSchemeText ();
      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
      g_file_set_contents (*pfilename, text, -1, NULL);
      gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (buffer), FALSE);
      g_free (text);
    }
}

gboolean
save_scheme_dialog (GtkTextBuffer * buffer, GtkWidget * textview)
{
  gchar **pfilename = g_object_get_data (G_OBJECT (textview), "pfilename");
  GtkWidget *dialog;
  GtkWidget *label;
  gint response;
  GtkWidget *content_area;

  if (gtk_text_buffer_get_modified (buffer))
    {
      dialog = gtk_dialog_new_with_buttons ("Scheme text changed", NULL /*GTK_WINDOW(gtk_text_view_get_window(GTK_TEXT_VIEW(Denemo.ScriptView), GTK_TEXT_WINDOW_WIDGET)) */ ,
                                            GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_YES, GTK_RESPONSE_YES, GTK_STOCK_NO, GTK_RESPONSE_NO, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, NULL);


      if (*pfilename == NULL)
        label = gtk_label_new ("\nDo you want to save the changes in a new file?\n\n");
      else
        {
          gchar *labeltext = g_strconcat ("\nDo you want to save the changes in ", *pfilename, "?\n\n", NULL);
          label = gtk_label_new (labeltext);
          g_free (labeltext);
        }

      content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
      gtk_container_add (GTK_CONTAINER (content_area), label);

      gtk_widget_show_all (dialog);

      response = gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);
      if (response == GTK_RESPONSE_YES)
        save_scheme_text (NULL, textview);
      else if (response == GTK_RESPONSE_CANCEL)
        return FALSE;

    }
  return TRUE;
}



static void
load_scheme_from_file (GtkWidget * widget, GtkWidget * textview)
{
  gchar **pfilename = g_object_get_data (G_OBJECT (textview), "pfilename");
  gchar *text = NULL;
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  GtkWidget *dialog = gtk_file_chooser_dialog_new ("Open File",
                                                   NULL /*GTK_WINDOW(gtk_text_view_get_window(GTK_TEXT_VIEW(Denemo.ScriptView),GTK_TEXT_WINDOW_WIDGET)) */ ,
                                                   GTK_FILE_CHOOSER_ACTION_OPEN,
                                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                                   GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL);

  if (!save_scheme_dialog (buffer, textview))
    {
      return;
    }


  if (gtk_dialog_run ((GTK_DIALOG (dialog))) == GTK_RESPONSE_OK)
    {
      if (*pfilename)
        g_free (*pfilename);
      *pfilename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
      g_file_get_contents (*pfilename, &text, NULL, NULL);
      gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), text, -1);
      gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (buffer), FALSE);
    }

  g_free (text);
  gtk_widget_destroy (dialog);

}

void
clear_scheme_window (GtkWidget * widget, GtkWidget * textview)
{
  gchar **pfilename = g_object_get_data (G_OBJECT (textview), "pfilename");
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (Denemo.ScriptView));
  if (!save_scheme_dialog (buffer, textview))
    return;

  g_free (*pfilename);
  *pfilename = NULL;
  gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), "", 0);
  gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (buffer), FALSE);
}


static gboolean
keypress (GtkEntry * w, GdkEventKey * event)
{
  if (event->keyval == GDK_Return)
    executeCLI (NULL, w);
  return FALSE;                 //let the normal handler have the keypress
}

/*
 create_editor_window()
 create a text window for editing

*/

static GtkWidget *
create_editor_window (void)
{
  GtkWidget *TextView;
  GtkSourceBuffer *buffer;
  GtkSourceLanguageManager *LanguageManager = gtk_source_language_manager_get_default ();
  //GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  GtkWidget *menu, *menuBar, *fileMenu, *item;
  gchar **filename = g_malloc0 (sizeof (gchar *));


  buffer = gtk_source_buffer_new (NULL);
  gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (buffer), TRUE);
  gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), gtk_source_language_manager_get_language (LanguageManager, "scheme"));
  gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (buffer), TRUE);

  TextView = GTK_WIDGET (gtk_source_view_new_with_buffer (GTK_SOURCE_BUFFER (buffer)));

  g_object_set_data (G_OBJECT (TextView), "pfilename", filename);
  gtk_source_view_set_show_line_numbers (GTK_SOURCE_VIEW (TextView), TRUE);
  gtk_source_view_set_auto_indent (GTK_SOURCE_VIEW (TextView), TRUE);
  gtk_source_view_set_indent_on_tab (GTK_SOURCE_VIEW (TextView), TRUE);
  gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (TextView), GTK_WRAP_CHAR);

  GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  GtkWidget *w = window;
  gtk_window_set_title (GTK_WINDOW (w), "Denemo Scheme Script");
  //gtk_window_set_resizable (GTK_WINDOW (w), TRUE);
  g_signal_connect (G_OBJECT (w), "delete-event", G_CALLBACK (hide_scheme /*gtk_widget_hide_on_delete */ ), w);
  GtkWidget *main_vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_add (GTK_CONTAINER (w), main_vbox);

  GtkWidget *hbox = gtk_hbox_new (FALSE, 1);
  w = gtk_button_new_with_label ("CLI: ");
  GtkWidget *button = w;
  //gtk_widget_set_can_default(w, TRUE);
  //GTK_WIDGET_SET_FLAGS(window, GTK_CAN_DEFAULT);
  //gtk_window_set_default (window, w);  
  gtk_box_pack_start (GTK_BOX (hbox), w, FALSE, TRUE, 0);
  w = gtk_entry_new ();
  GtkWidget *entry = w;
  //gtk_entry_set_activates_default (w,TRUE);
  gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, TRUE, 0);
  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (executeCLI), entry);
  g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK (keypress), NULL);
  w = gtk_button_new_with_label ("Execute Script");
  g_signal_connect (G_OBJECT (w), "clicked", G_CALLBACK (executeScript), NULL);
  gtk_box_pack_start (GTK_BOX (main_vbox), w, FALSE, FALSE, 0);





  menu = gtk_menu_new ();

  item = gtk_menu_item_new_with_label ("New");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (clear_scheme_window), (gpointer) TextView);

  item = gtk_menu_item_new_with_label ("Open");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (load_scheme_from_file), (gpointer) TextView);

  item = gtk_menu_item_new_with_label ("Save");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (save_scheme_text), (gpointer) TextView);

  item = gtk_menu_item_new_with_label ("Save as...");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (save_scheme_text_as), (gpointer) TextView);

  item = gtk_menu_item_new_with_label ("Find");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (find_cb), (gpointer) TextView);
  item = gtk_menu_item_new_with_label ("Replace");
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
  g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (replace_cb), (gpointer) TextView);



  fileMenu = gtk_menu_item_new_with_label ("File");
  gtk_widget_show (fileMenu);

  gtk_menu_item_set_submenu (GTK_MENU_ITEM (fileMenu), menu);
  gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (buffer), FALSE);

  menuBar = gtk_menu_bar_new ();

  gtk_box_pack_start (GTK_BOX (main_vbox), menuBar, FALSE, FALSE, 0);
  gtk_widget_show (menuBar);
  gtk_menu_shell_append (GTK_MENU_SHELL (menuBar), fileMenu);


  GtkWidget *wid = gtk_check_button_new ();
  gtk_activatable_set_related_action (GTK_ACTIVATABLE (wid), gtk_ui_manager_get_action (Denemo.ui_manager, "/MainMenu/ModeMenu/RecordScript"));
  gtk_box_pack_start (GTK_BOX (main_vbox), wid, FALSE, FALSE, 0);
  GtkWidget *sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  gtk_container_add (GTK_CONTAINER (main_vbox), sw);
  gtk_container_add (GTK_CONTAINER (sw), TextView);
  return TextView;
}


void
create_scheme_window (void)
{
  Denemo.ScriptView = create_editor_window ();
}



/* Buffer action callbacks ------------------------------------------------------------ */
#if  GTK_MAJOR_VERSION==3
static struct
{
  char *what;
  char *replacement;
  GtkTextSearchFlags flags;
} search_data =
{
NULL, NULL, GTK_TEXT_SEARCH_TEXT_ONLY};
#else
static struct
{
  char *what;
  char *replacement;
  GtkSourceSearchFlags flags;
} search_data =
{
NULL, NULL, GTK_SOURCE_SEARCH_CASE_INSENSITIVE};
#endif
#if GTK_MAJOR_VERSION==3
static gboolean
search_dialog (GtkWidget * widget, gboolean replace, char **what_p, char **replacement_p, GtkTextSearchFlags * flags_p)
#else
static gboolean
search_dialog (GtkWidget * widget, gboolean replace, char **what_p, char **replacement_p, GtkSourceSearchFlags * flags_p)
#endif
{
  GtkWidget *dialog;
  GtkEntry *entry1, *entry2;

  dialog = gtk_dialog_new_with_buttons (replace ? "Replace" : "Find", GTK_WINDOW (gtk_widget_get_toplevel (widget)), GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);

  entry1 = g_object_new (GTK_TYPE_ENTRY, "visible", TRUE, "text", search_data.what ? search_data.what : "", "activates-default", TRUE, NULL);
  GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
  GtkWidget *vbox = gtk_vbox_new (FALSE, 1);
  gtk_container_add (GTK_CONTAINER (content_area), vbox);
  gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (entry1), TRUE, TRUE, 0);
  entry2 = g_object_new (GTK_TYPE_ENTRY, "visible", replace, "text", search_data.replacement ? search_data.replacement : "", "activates-default", TRUE, NULL);
  gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (entry2), TRUE, TRUE, 0);

#if GTK_MAJOR_VERSION==2
  GtkToggleButton *case_sensitive;
  case_sensitive = g_object_new (GTK_TYPE_CHECK_BUTTON, "visible", TRUE, "label", "Case sensitive", "active", !(search_data.flags & GTK_SOURCE_SEARCH_CASE_INSENSITIVE), NULL);

  gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (case_sensitive), FALSE, FALSE, 0);
#endif
  gtk_widget_show_all (dialog);

  while (TRUE)
    {
      if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
        {
          gtk_widget_destroy (dialog);
          return FALSE;
        }

      if (*gtk_entry_get_text (entry1))
        break;
    }

  g_free (search_data.what);
  *what_p = search_data.what = g_strdup (gtk_entry_get_text (entry1));
  g_free (search_data.replacement);
  *replacement_p = search_data.replacement = g_strdup (gtk_entry_get_text (entry2));
#if GTK_MAJOR_VERSION==2
  *flags_p = search_data.flags = gtk_toggle_button_get_active (case_sensitive) ? 0 : GTK_SOURCE_SEARCH_CASE_INSENSITIVE;
#endif
  gtk_widget_destroy (dialog);
  return TRUE;
}

static void
do_search_replace (GtkTextView * view, gboolean replace)
{
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
  GtkTextIter iter;
  char *what, *replacement;
#if GTK_MAJOR_VERSION==3
  GtkTextSearchFlags flags;
#else
  GtkSourceSearchFlags flags;
#endif

  if (!search_dialog (GTK_WIDGET (view), replace, &what, &replacement, &flags))
    return;

  if (replace)
    {
      gtk_text_buffer_get_iter_at_offset (buffer, &iter, 0);

      while (TRUE)
        {
          GtkTextIter match_start, match_end;
#if GTK_MAJOR_VERSION==3
          if (!gtk_text_iter_forward_search (&iter, what, flags, &match_start, &match_end, NULL))
#else
          if (!gtk_source_iter_forward_search (&iter, what, flags, &match_start, &match_end, NULL))
#endif
            {
              break;
            }

          gtk_text_buffer_delete (buffer, &match_start, &match_end);
          gtk_text_buffer_insert (buffer, &match_start, replacement, -1);
          iter = match_start;
        }
    }
  else
    {
      GtkTextIter match_start, match_end;

      gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
#if GTK_MAJOR_VERSION==3
      if (gtk_text_iter_forward_search (&iter, what, flags, &match_start, &match_end, NULL))
#else
      if (gtk_source_iter_forward_search (&iter, what, flags, &match_start, &match_end, NULL))
#endif
        {
          gtk_text_buffer_select_range (buffer, &match_start, &match_end);
        }
      else
        {
          GtkTextIter insert = iter;
          gtk_text_buffer_get_start_iter (buffer, &iter);
#if GTK_MAJOR_VERSION==3
          if (gtk_text_iter_forward_search (&iter, what, flags, &match_start, &match_end, &insert))
#else
          if (gtk_source_iter_forward_search (&iter, what, flags, &match_start, &match_end, &insert))
#endif
            gtk_text_buffer_select_range (buffer, &match_start, &match_end);
        }
    }
}

static void
find_cb (GtkAction * action, gpointer user_data)
{
  do_search_replace (user_data, FALSE);
}

static void
replace_cb (GtkAction * action, gpointer user_data)
{
  do_search_replace (user_data, TRUE);
}
