/******************************* LICENCE **************************************
* Any code in this file may be redistributed or modified under the terms of
* the GNU General Public Licence as published by the Free Software 
* Foundation; version 2 of the licence.
****************************** END LICENCE ***********************************/

/******************************************************************************
* Author:
* Andrew Smith, http://littlesvr.ca/misc/contactandrew.php
*
* Contributors:
* 
******************************************************************************/

#include <gtk/gtk.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdbool.h>
#include <errno.h>
#include <libintl.h>

#include "isomaster.h"

extern AppSettings GBLappSettings;
extern char* GBLuserHomeDir;

extern GtkWidget* GBLmainWindow;

extern GtkWidget* GBLfsCurrentDirField;
extern GtkWidget* GBLfsTreeView;
extern GtkListStore* GBLfsListStore;
extern char* GBLfsCurrentDir;

extern GdkPixbuf* GBLdirPixbuf;
extern GdkPixbuf* GBLfilePixbuf;
//~ extern GdkPixbuf* GBLsymlinkPixbuf;

extern int errno;

/* the column for the filename in the fs pane */
static GtkTreeViewColumn* GBLfilenameFsColumn;

void acceptFsPathCbk(GtkEntry *entry, gpointer user_data)
{
    const char* newPath;
    char* newPathTerminated;
    
    newPath = gtk_entry_get_text(entry);
    
    if(newPath[strlen(newPath) - 1] == '/')
    {
        changeFsDirectory((char*)newPath);
    }
    else
    {
        newPathTerminated = malloc(strlen(newPath) + 2);
        if(newPathTerminated == NULL)
            fatalError("newPathTerminated = malloc(strlen(newPath) + 2) failed");
        
        strcpy(newPathTerminated, newPath);
        strcat(newPathTerminated, "/");
        
        changeFsDirectory(newPathTerminated);
        
        free(newPathTerminated);
    }
}

void buildFsBrowser(GtkWidget* boxToPackInto)
{
    GtkWidget* scrolledWindow;
    GtkTreeSelection *selection;
    GtkCellRenderer* renderer;
    GtkTreeViewColumn* column;
    
    GBLfsListStore = gtk_list_store_new(NUM_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_UINT);
    
    scrolledWindow = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledWindow),
				   GTK_POLICY_AUTOMATIC,
				   GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start(GTK_BOX(boxToPackInto), scrolledWindow, TRUE, TRUE, 0);
    gtk_widget_show(scrolledWindow);
    
    /* view widget */
    GBLfsTreeView = gtk_tree_view_new_with_model(GTK_TREE_MODEL(GBLfsListStore));
    gtk_tree_view_set_search_column(GTK_TREE_VIEW(GBLfsTreeView), COLUMN_FILENAME);
    g_object_unref(GBLfsListStore); /* destroy model automatically with view */
    gtk_container_add(GTK_CONTAINER(scrolledWindow), GBLfsTreeView);
    g_signal_connect(GBLfsTreeView, "row-activated", (GCallback)fsRowDblClickCbk, NULL);
    g_signal_connect(GBLfsTreeView, "select-cursor-parent", (GCallback)fsGoUpDirTreeCbk, NULL);
    gtk_widget_show(GBLfsTreeView);
    
    /* this won't be enabled until gtk allows me to drag a multiple selection */
    //~ GtkTargetEntry targetEntry;
    //~ targetEntry.target = "text/plain";
    //~ targetEntry.flags = 0;
    //~ targetEntry.info = 0;
    //~ gtk_tree_view_enable_model_drag_source(GTK_TREE_VIEW(GBLfsTreeView), GDK_BUTTON1_MASK, 
                                           //~ &targetEntry, 1, GDK_ACTION_COPY);
    
    /* enable multi-line selection */
    selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(GBLfsTreeView));
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
    
    /* filename column */
    GBLfilenameFsColumn = gtk_tree_view_column_new();
    gtk_tree_view_column_set_title(GBLfilenameFsColumn, _("Name"));
    gtk_tree_view_column_set_resizable(GBLfilenameFsColumn, TRUE);
    
    renderer = gtk_cell_renderer_pixbuf_new();
    gtk_tree_view_column_pack_start(GBLfilenameFsColumn, renderer, FALSE);
    gtk_tree_view_column_add_attribute(GBLfilenameFsColumn, renderer, "pixbuf", COLUMN_ICON);
    
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_pack_start(GBLfilenameFsColumn, renderer, TRUE);
    gtk_tree_view_column_add_attribute(GBLfilenameFsColumn, renderer, "text", COLUMN_FILENAME);
    
    gtk_tree_view_column_set_sort_column_id(GBLfilenameFsColumn, COLUMN_FILENAME);
    gtk_tree_view_column_set_expand(GBLfilenameFsColumn, TRUE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(GBLfsTreeView), GBLfilenameFsColumn);
    
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_FILENAME, 
                                    sortByName, NULL, NULL);
    
    /* size column */
    column = gtk_tree_view_column_new();
    renderer = gtk_cell_renderer_text_new();
    gtk_tree_view_column_set_title(column, _("Size"));
    gtk_tree_view_column_pack_start(column, renderer, FALSE);
    gtk_tree_view_column_add_attribute(column, renderer, "text", COLUMN_SIZE);
    gtk_tree_view_column_set_cell_data_func(column, renderer, sizeCellDataFunc64, NULL, NULL);
    gtk_tree_view_column_set_sort_column_id(column, COLUMN_SIZE);
    gtk_tree_view_append_column(GTK_TREE_VIEW(GBLfsTreeView), column);
    
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_SIZE, 
                                    sortBySize, NULL, NULL);
    
    /* set default sort */
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(GBLfsListStore),
                                         GBLappSettings.fsSortColumnId, 
                                         GBLappSettings.fsSortDirection);
    
    GBLdirPixbuf = NULL;
    GBLfilePixbuf = NULL;
    
#if GTK_MINOR_VERSION >= 6
    GtkIconSet* iconSet;
    GtkIconSize* iconSizes = NULL;
    int numIconSizes;
    GtkIconSize iconSize;
    
    /* CREATE pixbuf for directory */
    iconSet = gtk_icon_factory_lookup_default(GTK_STOCK_DIRECTORY);
    if(iconSet != NULL)
    {
        gtk_icon_set_get_sizes(iconSet, &iconSizes, &numIconSizes);
        iconSize = iconSizes[0];
        g_free(iconSizes);
        //!! figure out proper size and resisze if necessary, see gtk-demo->stock->create_model()
        GBLdirPixbuf = gtk_widget_render_icon(GBLfsTreeView, GTK_STOCK_DIRECTORY, iconSize, NULL);
    }
    /* END CREATE pixbuf for directory */
    
    /* CREATE pixbuf for file */
    iconSet = gtk_icon_factory_lookup_default(GTK_STOCK_FILE);
    if(iconSet != NULL)
    {
        gtk_icon_set_get_sizes(iconSet, &iconSizes, &numIconSizes);
        iconSize = iconSizes[0];
        g_free(iconSizes);
        //!! figure out proper size and resisze if necessary, see gtk-demo->stock->create_model()
        GBLfilePixbuf = gtk_widget_render_icon(GBLfsTreeView, GTK_STOCK_FILE, iconSize, NULL);
    }
    /* END CREATE pixbuf for file */
#endif
    
    if(GBLappSettings.fsCurrentDir != NULL)
    {
        bool rc;
        
        rc = changeFsDirectory(GBLappSettings.fsCurrentDir);
        if(rc == false)
            /* GBLuserHomeDir has just been set and tested a moment ago in findHomeDir() */
            changeFsDirectory(GBLuserHomeDir);
    }
    else
        changeFsDirectory(GBLuserHomeDir);
}

void buildFsLocator(GtkWidget* boxToPackInto)
{
    GBLfsCurrentDirField = gtk_entry_new();
    g_signal_connect(GBLfsCurrentDirField, "activate", (GCallback)acceptFsPathCbk, NULL);
    gtk_box_pack_start(GTK_BOX(boxToPackInto), GBLfsCurrentDirField, FALSE, FALSE, 0);
    gtk_widget_show(GBLfsCurrentDirField);
}

bool changeFsDirectory(char* newDirStr)
{
    DIR* newDir;
    struct dirent* nextItem; /* for contents of the directory */
    char* nextItemPathAndName; /* for use with stat() */
    struct stat nextItemInfo;
    GtkTreeIter listIterator;
    int rc;
    GtkTreeModel* model;
    GtkWidget* warningDialog;
    
    newDir = opendir(newDirStr);
    if(newDir == NULL)
    {
        warningDialog = gtk_message_dialog_new(GTK_WINDOW(GBLmainWindow),
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE,
                                               _("Failed to open directory '%s', error %d"),
                                               newDirStr,
                                               errno);
        gtk_window_set_modal(GTK_WINDOW(warningDialog), TRUE);
        gtk_dialog_run(GTK_DIALOG(warningDialog));
        gtk_widget_destroy(warningDialog);
        
        return false;
    }
    
    /* for improved performance disconnect the model from tree view before udating it */
    model = gtk_tree_view_get_model(GTK_TREE_VIEW(GBLfsTreeView));
    g_object_ref(model);
    gtk_tree_view_set_model(GTK_TREE_VIEW(GBLfsTreeView), NULL);
    
    /* this is the only way to disable sorting (for a huge performance boost) */
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_FILENAME, 
                                    sortVoid, NULL, NULL);
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_SIZE, 
                                    sortVoid, NULL, NULL);
    
    gtk_list_store_clear(GBLfsListStore);
    
#if GTK_MINOR_VERSION >= 8
    /* to make sure width of filename column isn't bigger than needed (need gtk 2.8) */
    gtk_tree_view_column_queue_resize(GBLfilenameFsColumn);
#endif
    
    nextItemPathAndName = (char*)malloc(strlen(newDirStr) + 257);
    if(nextItemPathAndName == NULL)
        fatalError("changeFsDirectory(): malloc(strlen(newDirStr) + 257) failed");
    
    /* it may be possible but in any case very unlikely that readdir() will fail
    * if it does, it returns NULL (same as end of dir) */
    while( (nextItem = readdir(newDir)) != NULL )
    {
        /* skip current and parent directory */
        if(strcmp(nextItem->d_name, ".") == 0 || strcmp(nextItem->d_name, "..") == 0)
            continue;
        
        if(nextItem->d_name[0] == '.' && !GBLappSettings.showHiddenFilesFs)
        /* skip hidden files/dirs */
            continue;
        
        /* mind 256 is assumed in the malloc below */
        if(strlen(nextItem->d_name) > 256)
        {
            warningDialog = gtk_message_dialog_new(GTK_WINDOW(GBLmainWindow),
                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
                                                   GTK_MESSAGE_ERROR,
                                                   GTK_BUTTONS_CLOSE,
                                                   _("Skiping directory entry because "
                                                   "cannot handle filename longer than 256 chars"));
            gtk_window_set_modal(GTK_WINDOW(warningDialog), TRUE);
            gtk_dialog_run(GTK_DIALOG(warningDialog));
            gtk_widget_destroy(warningDialog);
            continue;
        }
        
        strcpy(nextItemPathAndName, newDirStr);
        strcat(nextItemPathAndName, nextItem->d_name);
        
        rc = lstat(nextItemPathAndName, &nextItemInfo);
        if(rc == -1)
        {
            warningDialog = gtk_message_dialog_new(GTK_WINDOW(GBLmainWindow),
                                                   GTK_DIALOG_DESTROY_WITH_PARENT,
                                                   GTK_MESSAGE_ERROR,
                                                   GTK_BUTTONS_CLOSE,
                                                   _("Skiping directory entry because "
                                                   "stat(%s) failed with %d"),
                                                   nextItemPathAndName,
                                                   errno);
            gtk_window_set_modal(GTK_WINDOW(warningDialog), TRUE);
            gtk_dialog_run(GTK_DIALOG(warningDialog));
            gtk_widget_destroy(warningDialog);
            continue;
        }
        
        if(IS_DIR(nextItemInfo.st_mode))
        /* directory */
        {
            gtk_list_store_append(GBLfsListStore, &listIterator);
            gtk_list_store_set(GBLfsListStore, &listIterator, 
                               COLUMN_ICON, GBLdirPixbuf,
                               COLUMN_FILENAME, nextItem->d_name,
                               //COLUMN_FILENAME, g_filename_to_utf8(nextItem->d_name, -1, 0, 0, 0),
                               //COLUMN_FILENAME, g_locale_to_utf8(nextItem->d_name, -1, 0, 0, 0),
                               COLUMN_SIZE, 0LL,
                               COLUMN_HIDDEN_TYPE, FILE_TYPE_DIRECTORY,
                               -1);
        }
        else if(IS_REG_FILE(nextItemInfo.st_mode))
        /* regular file */
        {
            gtk_list_store_append(GBLfsListStore, &listIterator);
            gtk_list_store_set(GBLfsListStore, &listIterator, 
                               COLUMN_ICON, GBLfilePixbuf,
                               COLUMN_FILENAME, nextItem->d_name,
                               //COLUMN_FILENAME, g_filename_to_utf8(nextItem->d_name, -1, 0, 0, 0),
                               //COLUMN_FILENAME, g_locale_to_utf8(nextItem->d_name, -1, 0, 0, 0), 
                               COLUMN_SIZE, nextItemInfo.st_size,
                               COLUMN_HIDDEN_TYPE, FILE_TYPE_REGULAR,
                               -1);
        }
        else if(IS_SYMLINK(nextItemInfo.st_mode))
        /* regular file */
        {
            gtk_list_store_append(GBLfsListStore, &listIterator);
            gtk_list_store_set(GBLfsListStore, &listIterator, 
                               COLUMN_ICON, GBLfilePixbuf,
                               COLUMN_FILENAME, nextItem->d_name,
                               COLUMN_SIZE, 0LL,
                               COLUMN_HIDDEN_TYPE, FILE_TYPE_SYMLINK,
                               -1);
        }
        /* else fancy file, ignore it */
        
    } /* while (dir contents) */
    
    free(nextItemPathAndName);
    
    closedir(newDir);
    
    /* reconnect the model and view now */
    gtk_tree_view_set_model(GTK_TREE_VIEW(GBLfsTreeView), model);
    g_object_unref(model);
    
    /* reenable sorting */
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_FILENAME, 
                                    sortByName, NULL, NULL);
    gtk_tree_sortable_set_sort_func(GTK_TREE_SORTABLE(GBLfsListStore), COLUMN_SIZE, 
                                    sortBySize, NULL, NULL);
    
    /* set current directory string */
    if(GBLfsCurrentDir != NULL)
        free(GBLfsCurrentDir);
    GBLfsCurrentDir = (char*)malloc(strlen(newDirStr) + 1);
    if(GBLfsCurrentDir == NULL)
        fatalError("changeFsDirectory(): malloc(strlen(newDirStr) + 1) failed");
    strcpy(GBLfsCurrentDir, newDirStr);
    
    /* update the location field with the path and name */
    gtk_entry_set_text(GTK_ENTRY(GBLfsCurrentDirField), GBLfsCurrentDir);
    
    return true;
}

/* this is called from a button and via a treeview event so don't use the parameters */
void fsGoUpDirTreeCbk(GtkButton *button, gpointer data)
{
    int count;
    bool done;
    char* newCurrentDir;
    
    /* do nothing if already at root */
    if(GBLfsCurrentDir[0] == '/' && GBLfsCurrentDir[1] == '\0')
        return;
    
    /* need to allocate a new string because changeFsDirectory() uses it 
    * to copy from after freeing GBLfsCurrentDir */
    newCurrentDir = (char*)malloc(strlen(GBLfsCurrentDir) + 1);
    if(newCurrentDir == NULL)
        fatalError("fsGoUpDirTree(): malloc(strlen(GBLfsCurrentDir) + 1) failed");
    strcpy(newCurrentDir, GBLfsCurrentDir);
    
    /* look for the second last slash */
    done = false;
    for(count = strlen(newCurrentDir) - 1; !done; count--)
    {
        if(newCurrentDir[count - 1] == '/')
        /* truncate the string */
        {
            newCurrentDir[count] = '\0';
            changeFsDirectory(newCurrentDir);
            done = true;
        }
    }
    
    free(newCurrentDir);
}

void fsRowDblClickCbk(GtkTreeView* treeview, GtkTreePath* path,
                      GtkTreeViewColumn* col, gpointer data)
{
    GtkTreeModel* model;
    GtkTreeIter iterator;
    char* name;
    char* newCurrentDir;
    int fileType;
    GtkWidget* warningDialog;
    
    model = gtk_tree_view_get_model(treeview);
    
    if(gtk_tree_model_get_iter(model, &iterator, path) == FALSE)
    {
        warningDialog = gtk_message_dialog_new(GTK_WINDOW(GBLmainWindow),
                                               GTK_DIALOG_DESTROY_WITH_PARENT,
                                               GTK_MESSAGE_ERROR,
                                               GTK_BUTTONS_CLOSE,
                                               "GUI Error: 'fsRowDblClicked(): "
                                               "gtk_tree_model_get_iter() failed'");
        gtk_window_set_modal(GTK_WINDOW(warningDialog), TRUE);
        gtk_dialog_run(GTK_DIALOG(warningDialog));
        gtk_widget_destroy(warningDialog);
        return;
    }
    
    gtk_tree_model_get(model, &iterator, COLUMN_HIDDEN_TYPE, &fileType, -1);
    if(fileType == FILE_TYPE_DIRECTORY)
    {
        gtk_tree_model_get(model, &iterator, COLUMN_FILENAME, &name, -1);
        
        newCurrentDir = (char*)malloc(strlen(GBLfsCurrentDir) + strlen(name) + 2);
        if(newCurrentDir == NULL)
            fatalError("fsRowDblClicked(): malloc(strlen(GBLfsCurrentDir) + strlen(name) + 2) failed");
        
        strcpy(newCurrentDir, GBLfsCurrentDir);
        strcat(newCurrentDir, name);
        strcat(newCurrentDir, "/");
        
        changeFsDirectory(newCurrentDir);
        
        free(newCurrentDir);
        g_free(name);
    }
}

void refreshFsView(void)
{
    char* fsCurrentDir; /* for changeFsDirectory() */
    
    fsCurrentDir = malloc(strlen(GBLfsCurrentDir) + 1);
    if(fsCurrentDir == NULL)
        fatalError("refreshFsView(): malloc(strlen(GBLfsCurrentDir) + 1) failed");
    strcpy(fsCurrentDir, GBLfsCurrentDir);
    
    /* remember scroll position */
    GdkRectangle visibleRect;
    gtk_tree_view_get_visible_rect(GTK_TREE_VIEW(GBLfsTreeView), &visibleRect);
    
    changeFsDirectory(fsCurrentDir);
    
    /* need the -1 because if i call this function with the same coordinates that 
    * the view already has, the position is set to 0. think it's a gtk bug. */
    gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(GBLfsTreeView), visibleRect.x - 1, visibleRect.y - 1);
    
    free(fsCurrentDir);
}

void showHiddenCbk(GtkButton *button, gpointer data)
{
    GBLappSettings.showHiddenFilesFs = !GBLappSettings.showHiddenFilesFs;
    
    /* refresh fs view */
    refreshFsView();
}


syntax highlighted by Code2HTML, v. 0.9.1