/**
 * gui.c - GUI code for SciteProj
 *
 *  Copyright 2006 Roy Wood, 2009-2020 Andreas Rönnquist
 *
 * This file is part of SciteProj.
 *
 * SciteProj 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 3 of the License, or
 * (at your option) any later version.
 *
 * SciteProj 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 SciteProj.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <string.h>
#include <sys/stat.h>
#include <glib.h>
#include <gtk/gtk.h>

#include <gdk/gdkkeysyms.h>

#include <stdlib.h>
#include <glib/gi18n.h>

#include <locale.h>

#include "clicked_node.h"

#include "gui.h"
#include "tree_manipulation.h"
#include "scite_utils.h"
#include "string_utils.h"
#include "prefs.h"
#include "statusbar.h"
#include "graphics.h"
#include "about.h"
#include "properties_dialog.h"
#include "file_utils.h"

#include "clipboard.h"

#include "remove.h"

#include "recent_files.h"

#include "gui_callbacks.h"
#include "sort.h"

#include "menus.h"



// Forward-declare static functions

static gint window_delete_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data);
static void tree_row_activated_cb(GtkTreeView *treeView, GtkTreePath *path,
                                  GtkTreeViewColumn *column, gpointer userData);
static gboolean mouse_button_pressed_cb(GtkWidget *treeView, GdkEventButton *event, gpointer userData);

//gboolean dialog_response_is_exit(gint test);

void recent_files_switch_visible();

gboolean tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key,
                                     GtkTreeIter *iter, gpointer search_data);

gboolean is_name_valid(gchar *instring);

gboolean key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer userData);

gchar *window_saved_title = NULL;

static GtkWidget *sMainWindow = NULL;
GtkWidget *projectTreeView = NULL;

ClickedNode clicked_node;

GtkCellRenderer *textCellRenderer = NULL;
GtkCellRenderer *pixbuffCellRenderer = NULL;

GtkWidget *scrolledWindow = NULL;

GtkTreeViewColumn *column1 = NULL;

GtkWidget *recentGrid = NULL;

GtkWidget *recentHbox = NULL;


/**
 * Initialize globals (i.e. create the main window and populate it).  This is a long chunk of code.
 *
 * @return TRUE on success, FALSE on failure (further details returned in err)
 *
 * @param err returns any errors
 */
gboolean setup_gui(GError **err)
{
   gboolean resultCode = FALSE;
   GtkTreeSelection *selection = NULL;
   GtkWidget *vpaned = NULL;

   GtkTreeStore *projectTreeStore = NULL;
   //GtkAccelGroup* accelgroup = NULL;
   GError *tempErr = NULL;

   GtkWidget *grid;
   GtkWidget *fullGrid = NULL;

   GtkWidget *recentScrolledWindow = NULL;


   clicked_node.valid = FALSE;
   clicked_node.name = NULL;
   clicked_node.type = -1;

   window_saved_title=g_strdup_printf(_("[UNTITLED]"));

   // Create top-level window, configure it

   if (!(sMainWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL))) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s, gtk_window_new() = NULL",
                  __func__,
                  "Couldn't init main window"
                 );

      goto EXITPOINT;
   }

   // TODO: call these in load_graphics instead

   if (!(load_graphics(sMainWindow, err))) {
      goto EXITPOINT;
   }

   gtk_window_set_icon(GTK_WINDOW(sMainWindow), program_icon_pixbuf);
   gtk_window_set_default_icon(program_icon_pixbuf);

   gtk_window_set_title(GTK_WINDOW(sMainWindow), window_saved_title);

   gtk_container_set_border_width(GTK_CONTAINER(sMainWindow), 0);	//3
   g_signal_connect(G_OBJECT(sMainWindow), "delete_event", G_CALLBACK(window_delete_event_cb), NULL);

   // Main content of the window is a vpaned

   vpaned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);

   // Then we need a grid
   grid = gtk_grid_new();

   // Activate the keyboard accelerators

   accelerator_group = gtk_accel_group_new();
   gtk_window_add_accel_group(GTK_WINDOW(sMainWindow), accelerator_group);

   if (init_menus(sMainWindow) != 0) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s, gtk_scrolled_window_new() = NULL",
                  __func__,
                  "Couldn't init menus"
                 );
      goto EXITPOINT;
   }

   // Add a scrolled window to the main window
   if (!(scrolledWindow = gtk_scrolled_window_new(NULL, NULL))) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s, gtk_scrolled_window_new() = NULL",
                  __func__,
                  "Couldn't init main scrolled window"
                 );

      goto EXITPOINT;
   }

   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledWindow),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);

   gtk_grid_attach(GTK_GRID(grid), scrolledWindow, 0, 1, 1, 1);

   gtk_widget_set_vexpand(scrolledWindow, TRUE);
   gtk_widget_set_hexpand(scrolledWindow, TRUE);

   // Create the tree datastore

   if ((projectTreeStore = create_treestore(&tempErr)) == NULL) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s",
                  tempErr->message,
                  "Couldn't init treestore"
                 );
      goto EXITPOINT;
   }

   // Create the treeview, set it up to render the tree datastore, and add it to the hbox

   if (!(projectTreeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(projectTreeStore)))) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s, gtk_tree_view_new_with_model() = NULL",
                  __func__,
                  "Couldn't init gtk_tree_view"
                 );

      goto EXITPOINT;
   }

   gtk_tree_view_set_enable_search(GTK_TREE_VIEW(projectTreeView), TRUE);

   if (!(textCellRenderer = gtk_cell_renderer_text_new())) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s, gtk_cell_renderer_text_new() = NULL",
                  __func__,
                  "Couldn't init cell renderer"
                 );

      goto EXITPOINT;
   }

   g_object_set(G_OBJECT(textCellRenderer),
                "editable", FALSE,
                "mode", GTK_CELL_RENDERER_MODE_EDITABLE,
                NULL);

   if (!(pixbuffCellRenderer = gtk_cell_renderer_pixbuf_new())) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1,
                  "%s: %s, gtk_cell_renderer_pixbuf_new() = NULL",
                  __func__,
                  "Couldn't init gtk_cell_renderer_pixbuf"
                 );

      goto EXITPOINT;
   }

   if (!(column1 = gtk_tree_view_column_new())) {
      g_set_error(err, APP_SCITEPROJ_ERROR, -1,
                  "%s: %s, gtk_tree_view_column_new() = NULL",
                  __func__,
                  "Couldn't init gtk_tree_view_column"
                 );

      goto EXITPOINT;
   }

   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(projectTreeView), FALSE);

   gtk_tree_view_column_set_resizable(column1, TRUE);
   gtk_tree_view_column_set_min_width(column1, (int)(prefs.width * .75));


   gtk_tree_view_column_pack_start(column1, pixbuffCellRenderer, FALSE);
   gtk_tree_view_column_add_attribute(column1, pixbuffCellRenderer, "pixbuf", COLUMN_ICON);


   gtk_tree_view_column_pack_start(column1, textCellRenderer, TRUE);
   gtk_tree_view_column_add_attribute(column1, textCellRenderer, "text", COLUMN_FILENAME);
   gtk_tree_view_column_add_attribute(column1, textCellRenderer, "weight", COLUMN_FONTWEIGHT);
   gtk_tree_view_column_add_attribute(column1, textCellRenderer, "weight-set", COLUMN_FONTWEIGHTSET);

   gtk_tree_view_append_column(GTK_TREE_VIEW(projectTreeView), column1);

   // Stoopid gtk always expands the last column

   gtk_container_add(GTK_CONTAINER(scrolledWindow), projectTreeView);

   // Get tree events

   selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(projectTreeView));
   gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);

   g_signal_connect(G_OBJECT(projectTreeView), "row-activated",
                    G_CALLBACK(tree_row_activated_cb), NULL);

   gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(projectTreeView),
                                       tree_view_search_equal_func, NULL, NULL);

   g_signal_connect(G_OBJECT(projectTreeView), "row-expanded",
                    G_CALLBACK(row_expand_or_collapse_cb), NULL);

   g_signal_connect(G_OBJECT(projectTreeView), "row-collapsed",
                    G_CALLBACK(row_expand_or_collapse_cb), NULL);


   g_signal_connect(G_OBJECT(projectTreeView), "button-press-event",
                    G_CALLBACK(mouse_button_pressed_cb), projectTreeView);

   g_signal_connect(G_OBJECT(projectTreeView), "key-press-event",
                    G_CALLBACK(key_press_cb), projectTreeView);

   // --------------------------------
   // Recent file stuff:

   if (!(recentGrid = gtk_grid_new())) {
      g_set_error(err, APP_SCITEPROJ_ERROR,-1,
                  "%s: %s, gtk_grid_new() = NULL",
                  "Couldn't init recent grid",
                  __func__);
      goto EXITPOINT;
   }

   if ((recentScrolledWindow = init_recent_files(&tempErr)) == NULL) {

      goto EXITPOINT;
   }


   gtk_widget_set_vexpand(recentScrolledWindow, TRUE);
   gtk_widget_set_hexpand(recentScrolledWindow, TRUE);

   gtk_grid_attach(GTK_GRID(recentGrid), recentScrolledWindow, 0, 0, 1, 1);

   fullGrid = gtk_grid_new();



   gtk_paned_pack1(GTK_PANED(vpaned), grid, TRUE, FALSE);
   gtk_paned_pack2(GTK_PANED(vpaned), recentGrid, TRUE, TRUE);

   gtk_widget_show(vpaned);



   gtk_grid_insert_row(GTK_GRID(fullGrid), 0);
   gtk_grid_attach(GTK_GRID(fullGrid), vpaned, 0, 0, 1, 1);

   gtk_grid_insert_row(GTK_GRID(fullGrid), 0);
   gtk_grid_attach(GTK_GRID(fullGrid), menuBar, 0, 0, 1, 1);

   gtk_widget_show(menuBar);

   gtk_widget_show(GTK_WIDGET(fullGrid));

   if (!prefs.hide_statusbar) {
      if (!init_statusbar(fullGrid, vpaned, &tempErr)) {
         g_set_error(err, APP_SCITEPROJ_ERROR, -1, "%s: %s", tempErr->message, "Error initing statusbar");
         goto EXITPOINT;
      }
   }

   gtk_container_add(GTK_CONTAINER(sMainWindow), fullGrid);


   g_signal_connect(G_OBJECT(recentTreeView), "key-press-event", G_CALLBACK(key_press_cb), recentTreeView);

   gtk_window_resize(GTK_WINDOW(sMainWindow), prefs.width, prefs.height);
   gtk_window_move(GTK_WINDOW(sMainWindow), prefs.xpos, prefs.ypos);

   int window_xsize, window_ysize;
   gtk_window_get_size(GTK_WINDOW(sMainWindow), &window_xsize, &window_ysize);

   gtk_paned_set_position(GTK_PANED(vpaned), (int)(window_ysize*0.75));

   // Show it all....

   gtk_widget_show(recentGrid);

   if (!prefs.show_recent) {
      gtk_widget_hide(recentGrid);
   } else {
      gtk_widget_show(recentGrid);
   }

   gtk_widget_show(projectTreeView);
   gtk_widget_show(scrolledWindow);
   gtk_widget_show(grid);
   gtk_widget_show(sMainWindow);

   resultCode = TRUE;

EXITPOINT:

   if (tempErr) g_error_free(tempErr);

   return resultCode;
}


/**
 *
 */
void gui_close()
{
   if (window_saved_title) g_free(window_saved_title);

   if (scrolledWindow) gtk_widget_destroy(scrolledWindow);

   unload_graphics();

   done_statusbar();

   if (sMainWindow) gtk_widget_destroy(sMainWindow);
}



/**
 * Determine whether a specified row in the tree is expanded.
 *
 * @return TRUE if the row is expanded; FALSE otherwise
 *
 * @param path is the GtkTreePath referencing the row
 */
gboolean tree_row_is_expanded(GtkTreePath *path)
{
   g_assert(path != NULL);

   return gtk_tree_view_row_expanded(GTK_TREE_VIEW(projectTreeView), path);
}



/**
 * Expand a row in the tree.
 *
 * @param path is the GtkTreePath referencing the row
 * @param expandChildren indicates whether all children should be expanded
 */
void expand_tree_row(GtkTreePath *path, gboolean expandChildren)
{
   if (path != NULL) {
      gtk_tree_view_expand_row(GTK_TREE_VIEW(projectTreeView), path, FALSE);
   }
}



/**
 * Callback for Gtk "delete_event" message for the top-level application window.
 *
 * @param widget is not used
 * @param event is not used
 * @param data is not used
 */
static gint window_delete_event_cb(GtkWidget *widget, GdkEvent *event, gpointer data)
{
   gboolean eventHandled = TRUE;

   gtk_main_quit();

   return eventHandled;
}


/**
 *
 */
static void switch_folder_icon(GtkTreeView *treeView, GtkTreePath *path)
{
   GtkTreeIter iter;
   gint nodeItemType;

   gchar *relFilePath = NULL;

   GtkTreeModel *treeModel = gtk_tree_view_get_model(treeView);
   GdkPixbuf *pixbuf;

   gtk_tree_model_get_iter(treeModel, &iter, path);
   gtk_tree_model_get(treeModel, &iter, COLUMN_ITEMTYPE, &nodeItemType,
                      COLUMN_FILEPATH, &relFilePath,
                      COLUMN_ICON, &pixbuf,
                      -1);

   gboolean res=gtk_tree_view_row_expanded(treeView, path);

   if (res) {
      gtk_tree_view_collapse_row(treeView, path);
   } else {

      gtk_tree_view_expand_row(treeView, path, FALSE);
   }

   g_free(relFilePath);
}


/**
 * Callback handler for Gtk "row-activated" event.
 *
 * @param treeView is the GtkTreeView
 * @param path is the GtkTreePath of the activated row
 * @param column is not used
 * @param userData is not used
 */
static void tree_row_activated_cb(GtkTreeView *treeView,
                                  GtkTreePath *path,
                                  GtkTreeViewColumn *column,
                                  gpointer userData)
{
   GtkTreeIter iter;
   gchar *relFilePath = NULL;
   gchar *absFilePath = NULL;
   gchar *command = NULL;
   GError *err = NULL;
   GtkWidget *dialog = NULL;
   gint nodeItemType;
   gchar *fixed = NULL;


   // Get the data from the row that was activated

   GtkTreeModel *treeModel = gtk_tree_view_get_model(treeView);
   gtk_tree_model_get_iter(treeModel, &iter, path);
   gtk_tree_model_get(treeModel, &iter, COLUMN_ITEMTYPE, &nodeItemType, COLUMN_FILEPATH, &relFilePath, -1);


   // We can only open files

   if (nodeItemType != ITEMTYPE_FILE) {
      switch_folder_icon(treeView, path);
      goto EXITPOINT;
   }

   absFilePath = fix_path((gchar*)get_project_directory(), relFilePath);

   fixed = fix_path((gchar*)get_project_directory(), relFilePath);

   if ((command = g_strdup_printf("open:%s\n", fixed)) == NULL) {
      g_set_error(&err, APP_SCITEPROJ_ERROR, -1,
                  "%s: %s, g_strdup_printf() = NULL",
                  "Error formatting SciTE command",
                  __func__);
   }
   else {
      if (send_scite_command(command, &err)) {
         // Try to activate SciTE; ignore errors

         activate_scite(NULL);

         if (prefs.give_scite_focus == TRUE) {
            send_scite_command((gchar*)"focus:0", NULL);
         }

         add_file_to_recent(fixed, NULL);

         gchar *statusbar_text = g_strdup_printf(_("Opened %s"),
                                                 remove_newline(get_filename_from_full_path(command)));

         set_statusbar_text(statusbar_text);

         g_free(statusbar_text);
      }
   }

EXITPOINT:

   if (err != NULL) {

      gchar *could_not_open_string;
      could_not_open_string = g_strdup_printf(_("Could not open selected file:"));

      dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                      "%s\n\n%s", could_not_open_string, err->message);

      gtk_dialog_run(GTK_DIALOG (dialog));

      g_free(could_not_open_string);
   }

   if (relFilePath) g_free(relFilePath);
   if (absFilePath) g_free(absFilePath);
   if (command) g_free(command);
   if (err) g_error_free(err);
   if (dialog) gtk_widget_destroy(dialog);
   if (fixed) g_free(fixed);
}



/**
 * Respond to a Gtk "button-press-event" message.
 *
 * @param treeView is the GTKTreeView widget in which the mouse-button event occurred
 * @param event is the GdkEventButton event object
 * @param userData is not currently used
 */
static gboolean mouse_button_pressed_cb(GtkWidget *treeView, GdkEventButton *event, gpointer userData)
{
   gboolean eventHandled = FALSE;
   GtkTreePath *path = NULL;
   GtkTreeModel *treeModel = NULL;
   gchar *nodeName = NULL;
   gint nodeItemType;
   GtkTreeIter iter;
   GtkTreeSelection *tree_selection = NULL;

   g_assert(treeView != NULL);
   g_assert(event != NULL);

   // Until we know for sure, assume that the user has not clicked on a node

   clicked_node.valid = FALSE;


   // If it is not a right-click, then ignore it

   if (event->type != GDK_BUTTON_PRESS || event->button != 3) {
      goto EXITPOINT;
   }

   // Find if the user has clicked on a node

   if (!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeView),
                                      (gint) event->x, (gint) event->y,
                                      &path, NULL, NULL, NULL)) {
      // Nope-- user clicked in the GtkTreeView, but not on a node
      goto EXITPOINT;
   }

   // User clicked on a node, so retrieve the particulars

   treeModel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeView));

   if (!gtk_tree_model_get_iter(treeModel, &iter, path)) {
      goto EXITPOINT;
   }

   gtk_tree_model_get(treeModel, &iter, COLUMN_ITEMTYPE, &nodeItemType, COLUMN_FILEPATH, &nodeName, -1);


   // Save the node info for use by the popup menu callbacks

   if (clicked_node.name) g_free(clicked_node.name);

   clicked_node.valid = TRUE;
   clicked_node.iter = iter;
   clicked_node.type = nodeItemType;
   clicked_node.name = nodeName;
   nodeName = NULL;

   // Check if something is selected
   tree_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeView));

   if (tree_selection != NULL) {
      // Check if clicked on something in the selection, otherwise make the clicked one the selection.

      if (gtk_tree_selection_path_is_selected(tree_selection, path) == FALSE) {
         // clear selection and make current line selected

         gtk_tree_selection_unselect_all(tree_selection);

         gtk_tree_selection_select_path(tree_selection, path);
      }
   }

   // Pop up the appropriate menu for the node type

   if (nodeItemType == ITEMTYPE_FILE) {
      if (fileRightClickPopupMenu) {
         gtk_menu_popup_at_pointer(GTK_MENU(fileRightClickPopupMenu), (GdkEvent*)event);
      }
   }
   else if (nodeItemType == ITEMTYPE_GROUP) {
      if (groupRightClickPopupMenu) {
         gtk_menu_popup_at_pointer(GTK_MENU(groupRightClickPopupMenu), (GdkEvent*)event);
      }
   }

   
   eventHandled = TRUE;


EXITPOINT:

   if (path) gtk_tree_path_free(path);
   if (nodeName) g_free(nodeName);

   return eventHandled;
}




/**
 * Set the title of the main window.
 *
 * @param newName is the desired new name of the window.
 */
void set_window_title(const gchar *newName)
{
   g_assert(newName != NULL);

   gchar *temp_string = g_new(gchar, 512);
   g_snprintf(temp_string, 512, "%s", newName);

   gtk_window_set_title(GTK_WINDOW(sMainWindow), temp_string);

   g_free(window_saved_title);

   window_saved_title = g_strdup_printf("%s", newName);

   g_free(temp_string);
}


/**
 *
 */
gboolean dialog_response_is_exit(gint test)
{
   gboolean result = FALSE;

   if ((test == GTK_RESPONSE_REJECT) || (test == GTK_RESPONSE_CANCEL) ||
           (test == GTK_RESPONSE_DELETE_EVENT) || (test == GTK_RESPONSE_NONE)) {
      result = TRUE;
   }

   return result;
}


/**
 *
 */
void recent_files_switch_visible()
{
   gboolean visible = FALSE;

   g_object_get(G_OBJECT(recentGrid), "visible", &visible, NULL);

   if (visible) {
      gtk_widget_hide(recentGrid);
      gtk_widget_grab_focus(projectTreeView);
   } else {
      gtk_widget_show(recentGrid);
   }

}


/**
 *
 */
void set_dialog_transient(GtkWidget *dialog)
{
   gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(sMainWindow));
}


/**
 *
 */
GtkWindow *get_main_window()
{
   return GTK_WINDOW(sMainWindow);
}

