/* nautilus-compress-dialog-controller.h
 *
 * Copyright (C) 2016 the Nautilus developers
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include <glib/gi18n.h>
#include <gnome-autoar/gnome-autoar.h>
#include <libadwaita-1/adwaita.h>

#include <eel/eel-vfs-extensions.h>

#include "nautilus-compress-dialog-controller.h"

#include "nautilus-global-preferences.h"

struct _NautilusCompressDialogController
{
    NautilusFileNameWidgetController parent_instance;

    GtkWidget *compress_dialog;
    GtkWidget *activate_button;
    GtkWidget *error_label;
    GtkWidget *name_entry;
    GtkWidget *extension_dropdown;
    GtkSizeGroup *extension_sizegroup;
    GtkWidget *passphrase_label;
    GtkWidget *passphrase_entry;

    const char *extension;
    gchar *passphrase;

    gulong response_handler_id;
};

G_DEFINE_TYPE (NautilusCompressDialogController, nautilus_compress_dialog_controller, NAUTILUS_TYPE_FILE_NAME_WIDGET_CONTROLLER);

#define NAUTILUS_TYPE_COMPRESS_ITEM (nautilus_compress_item_get_type ())
G_DECLARE_FINAL_TYPE (NautilusCompressItem, nautilus_compress_item, NAUTILUS, COMPRESS_ITEM, GObject)

struct _NautilusCompressItem
{
    GObject parent_instance;
    NautilusCompressionFormat format;
    char *extension;
    char *description;
};

G_DEFINE_TYPE (NautilusCompressItem, nautilus_compress_item, G_TYPE_OBJECT);

static void
nautilus_compress_item_init (NautilusCompressItem *item)
{
}

static void
nautilus_compress_item_finalize (GObject *object)
{
    NautilusCompressItem *item = NAUTILUS_COMPRESS_ITEM (object);

    g_free (item->extension);
    g_free (item->description);

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

static void
nautilus_compress_item_class_init (NautilusCompressItemClass *class)
{
    GObjectClass *object_class = G_OBJECT_CLASS (class);

    object_class->finalize = nautilus_compress_item_finalize;
}

static NautilusCompressItem *
nautilus_compress_item_new (NautilusCompressionFormat  format,
                            const char                *extension,
                            const char                *description)
{
    NautilusCompressItem *item = g_object_new (NAUTILUS_TYPE_COMPRESS_ITEM, NULL);

    item->format = format;
    item->extension = g_strdup (extension);
    item->description = g_strdup (description);

    return item;
}

static gboolean
nautilus_compress_dialog_controller_name_is_valid (NautilusFileNameWidgetController  *self,
                                                   gchar                             *name,
                                                   gchar                            **error_message)
{
    gboolean is_valid;

    is_valid = TRUE;
    if (strlen (name) == 0)
    {
        is_valid = FALSE;
    }
    else if (strstr (name, "/") != NULL)
    {
        is_valid = FALSE;
        *error_message = _("Archive names cannot contain “/”.");
    }
    else if (strcmp (name, ".") == 0)
    {
        is_valid = FALSE;
        *error_message = _("An archive cannot be called “.”.");
    }
    else if (strcmp (name, "..") == 0)
    {
        is_valid = FALSE;
        *error_message = _("An archive cannot be called “..”.");
    }
    else if (nautilus_file_name_widget_controller_is_name_too_long (self, name))
    {
        is_valid = FALSE;
        *error_message = _("Archive name is too long.");
    }

    if (is_valid && g_str_has_prefix (name, "."))
    {
        /* We must warn about the side effect */
        *error_message = _("Archives with “.” at the beginning of their name are hidden.");
    }

    return is_valid;
}

static gchar *
nautilus_compress_dialog_controller_get_new_name (NautilusFileNameWidgetController *controller)
{
    NautilusCompressDialogController *self;
    g_autofree gchar *basename = NULL;
    gchar *error_message = NULL;
    gboolean valid_name;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (controller);

    basename = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (nautilus_compress_dialog_controller_parent_class)->get_new_name (controller);
    /* Do not check or add the extension if the name is invalid */
    valid_name = nautilus_compress_dialog_controller_name_is_valid (controller,
                                                                    basename,
                                                                    &error_message);

    if (!valid_name)
    {
        return g_strdup (basename);
    }

    if (g_str_has_suffix (basename, self->extension))
    {
        return g_strdup (basename);
    }

    return g_strconcat (basename, self->extension, NULL);
}

static void
compress_dialog_controller_on_response (GtkDialog *dialog,
                                        gint       response_id,
                                        gpointer   user_data)
{
    NautilusCompressDialogController *controller;

    controller = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);

    if (response_id != GTK_RESPONSE_OK)
    {
        g_signal_emit_by_name (controller, "cancelled");
    }
}

static void
update_selected_format (NautilusCompressDialogController *self)
{
    gboolean show_passphrase = FALSE;
    guint selected;
    GListModel *model;
    NautilusCompressItem *item;

    selected = gtk_drop_down_get_selected (GTK_DROP_DOWN (self->extension_dropdown));
    if (selected == GTK_INVALID_LIST_POSITION)
    {
        return;
    }

    model = gtk_drop_down_get_model (GTK_DROP_DOWN (self->extension_dropdown));
    item = g_list_model_get_item (model, selected);
    if (item == NULL)
    {
        return;
    }

    if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP)
    {
        show_passphrase = TRUE;
    }

    self->extension = item->extension;

    gtk_widget_set_visible (self->passphrase_label, show_passphrase);
    gtk_widget_set_visible (self->passphrase_entry, show_passphrase);
    if (!show_passphrase)
    {
        gtk_editable_set_text (GTK_EDITABLE (self->passphrase_entry), "");
        gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), FALSE);
        gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry),
                                           GTK_ENTRY_ICON_SECONDARY,
                                           "view-conceal");
    }

    g_settings_set_enum (nautilus_compression_preferences,
                         NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT,
                         item->format);

    /* Since the extension changes when the button is toggled, force a
     * verification of the new file name by simulating an entry change
     */
    gtk_widget_set_sensitive (self->activate_button, FALSE);
    g_signal_emit_by_name (self->name_entry, "changed");
}

static void
extension_dropdown_setup_item (GtkSignalListItemFactory *factory,
                               GtkListItem              *item,
                               gpointer                  user_data)
{
    GtkWidget *title;

    title = gtk_label_new ("");
    gtk_label_set_xalign (GTK_LABEL (title), 0.0);

    g_object_set_data (G_OBJECT (item), "title", title);
    gtk_list_item_set_child (item, title);
}


static void
extension_dropdown_setup_item_full (GtkSignalListItemFactory *factory,
                                    GtkListItem              *item,
                                    gpointer                  user_data)
{
    GtkWidget *hbox, *vbox, *title, *subtitle, *checkmark;

    title = gtk_label_new ("");
    gtk_label_set_xalign (GTK_LABEL (title), 0.0);
    gtk_widget_set_halign (title, GTK_ALIGN_START);

    subtitle = gtk_label_new ("");
    gtk_label_set_xalign (GTK_LABEL (subtitle), 0.0);
    gtk_widget_add_css_class (subtitle, "dim-label");
    gtk_widget_add_css_class (subtitle, "caption");

    checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");

    hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
    vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 3);
    gtk_widget_set_hexpand (vbox, TRUE);

    gtk_box_append (GTK_BOX (hbox), vbox);
    gtk_box_append (GTK_BOX (vbox), title);
    gtk_box_append (GTK_BOX (vbox), subtitle);
    gtk_box_append (GTK_BOX (hbox), checkmark);

    g_object_set_data (G_OBJECT (item), "title", title);
    g_object_set_data (G_OBJECT (item), "subtitle", subtitle);
    g_object_set_data (G_OBJECT (item), "checkmark", checkmark);

    gtk_list_item_set_child (item, hbox);
}

static void
extension_dropdown_on_selected_item_notify (GtkDropDown *dropdown,
                                            GParamSpec  *pspec,
                                            GtkListItem *item)
{
    GtkWidget *checkmark;

    checkmark = g_object_get_data (G_OBJECT (item), "checkmark");

    if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item))
    {
        gtk_widget_set_opacity (checkmark, 1.0);
    }
    else
    {
        gtk_widget_set_opacity (checkmark, 0.0);
    }
}

static void
extension_dropdown_bind (GtkSignalListItemFactory *factory,
                         GtkListItem              *list_item,
                         gpointer                  user_data)
{
    NautilusCompressDialogController *self;
    GtkWidget *title, *subtitle, *checkmark;
    NautilusCompressItem *item;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
    item = gtk_list_item_get_item (list_item);

    title = g_object_get_data (G_OBJECT (list_item), "title");
    subtitle = g_object_get_data (G_OBJECT (list_item), "subtitle");
    checkmark = g_object_get_data (G_OBJECT (list_item), "checkmark");

    gtk_label_set_label (GTK_LABEL (title), item->extension);
    gtk_size_group_add_widget (self->extension_sizegroup, title);

    if (item->format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP)
    {
        gtk_widget_add_css_class (title, "encrypted_zip");
    }

    if (subtitle)
    {
        gtk_label_set_label (GTK_LABEL (subtitle), item->description);
    }

    if (checkmark)
    {
        g_signal_connect (self->extension_dropdown,
                          "notify::selected-item",
                          G_CALLBACK (extension_dropdown_on_selected_item_notify),
                          list_item);
        extension_dropdown_on_selected_item_notify (GTK_DROP_DOWN (self->extension_dropdown),
                                                    NULL,
                                                    list_item);
    }
}

static void
extension_dropdown_unbind (GtkSignalListItemFactory *factory,
                           GtkListItem              *item,
                           gpointer                  user_data)
{
    NautilusCompressDialogController *self;
    GtkWidget *title;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
    g_signal_handlers_disconnect_by_func (self->extension_dropdown,
                                          extension_dropdown_on_selected_item_notify,
                                          item);

    title = g_object_get_data (G_OBJECT (item), "title");
    if (title)
    {
        gtk_widget_remove_css_class (title, "encrypted_zip");
    }
}

static void
passphrase_entry_on_changed (GtkEditable *editable,
                             gpointer     user_data)
{
    NautilusCompressDialogController *self;
    const gchar *error_message;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);

    g_free (self->passphrase);
    self->passphrase = g_strdup (gtk_editable_get_text (GTK_EDITABLE (self->passphrase_entry)));

    /* Simulate a change of the name_entry to ensure the correct sensitivity of
     * the activate_button, but only if the name_entry is valid in order to
     * avoid changes of the error_revealer.
     */
    error_message = gtk_label_get_text (GTK_LABEL (self->error_label));
    if (error_message[0] == '\0')
    {
        gtk_widget_set_sensitive (self->activate_button, FALSE);
        g_signal_emit_by_name (self->name_entry, "changed");
    }
}

static void
passphrase_entry_on_icon_press (GtkEntry             *entry,
                                GtkEntryIconPosition  icon_pos,
                                gpointer              user_data)
{
    NautilusCompressDialogController *self;
    gboolean visibility;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
    visibility = gtk_entry_get_visibility (GTK_ENTRY (self->passphrase_entry));

    gtk_entry_set_icon_from_icon_name (GTK_ENTRY (self->passphrase_entry),
                                       GTK_ENTRY_ICON_SECONDARY,
                                       visibility ? "view-conceal" : "view-reveal");
    gtk_entry_set_visibility (GTK_ENTRY (self->passphrase_entry), !visibility);
}

static void
activate_button_on_sensitive_notify (GObject    *gobject,
                                     GParamSpec *pspec,
                                     gpointer    user_data)
{
    NautilusCompressDialogController *self;
    NautilusCompressionFormat format;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (user_data);
    format = g_settings_get_enum (nautilus_compression_preferences,
                                  NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);
    if (format == NAUTILUS_COMPRESSION_ENCRYPTED_ZIP &&
        (self->passphrase == NULL || self->passphrase[0] == '\0'))
    {
        /* Reset sensitivity of the activate_button if password is not set. */
        gtk_widget_set_sensitive (self->activate_button, FALSE);
    }
}

static void
extension_dropdown_setup (NautilusCompressDialogController *self)
{
    GtkListItemFactory *factory, *list_factory;
    GListStore *store;
    NautilusCompressItem *item;
    NautilusCompressionFormat format;
    gint i;

    store = g_list_store_new (NAUTILUS_TYPE_COMPRESS_ITEM);
    item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ZIP,
                                       ".zip",
                                       _("Compatible with all operating systems."));
    g_list_store_append (store, item);
    g_object_unref (item);
    item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_ENCRYPTED_ZIP,
                                       ".zip",
                                       _("Password protected .zip, must be installed on Windows and Mac."));
    g_list_store_append (store, item);
    g_object_unref (item);
    item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_TAR_XZ,
                                       ".tar.xz",
                                       _("Smaller archives but Linux and Mac only."));
    g_list_store_append (store, item);
    g_object_unref (item);
    item = nautilus_compress_item_new (NAUTILUS_COMPRESSION_7ZIP,
                                       ".7z",
                                       _("Smaller archives but must be installed on Windows and Mac."));
    g_list_store_append (store, item);
    g_object_unref (item);

    factory = gtk_signal_list_item_factory_new ();
    g_signal_connect_object (factory, "setup",
                             G_CALLBACK (extension_dropdown_setup_item), self, 0);
    g_signal_connect_object (factory, "bind",
                             G_CALLBACK (extension_dropdown_bind), self, 0);
    g_signal_connect_object (factory, "unbind",
                             G_CALLBACK (extension_dropdown_unbind), self, 0);

    list_factory = gtk_signal_list_item_factory_new ();
    g_signal_connect_object (list_factory, "setup",
                             G_CALLBACK (extension_dropdown_setup_item_full), self, 0);
    g_signal_connect_object (list_factory, "bind",
                             G_CALLBACK (extension_dropdown_bind), self, 0);
    g_signal_connect_object (list_factory, "unbind",
                             G_CALLBACK (extension_dropdown_unbind), self, 0);

    gtk_drop_down_set_factory (GTK_DROP_DOWN (self->extension_dropdown), factory);
    gtk_drop_down_set_list_factory (GTK_DROP_DOWN (self->extension_dropdown), list_factory);
    gtk_drop_down_set_model (GTK_DROP_DOWN (self->extension_dropdown), G_LIST_MODEL (store));

    format = g_settings_get_enum (nautilus_compression_preferences,
                                  NAUTILUS_PREFERENCES_DEFAULT_COMPRESSION_FORMAT);
    for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
    {
        item = g_list_model_get_item (G_LIST_MODEL (store), i);
        if (item->format == format)
        {
            gtk_drop_down_set_selected (GTK_DROP_DOWN (self->extension_dropdown), i);
            update_selected_format (self);
            g_object_unref (item);
            break;
        }

        g_object_unref (item);
    }

    g_object_unref (store);
    g_object_unref (factory);
    g_object_unref (list_factory);
}

NautilusCompressDialogController *
nautilus_compress_dialog_controller_new (GtkWindow         *parent_window,
                                         NautilusDirectory *destination_directory,
                                         gchar             *initial_name)
{
    NautilusCompressDialogController *self;
    g_autoptr (GtkBuilder) builder = NULL;
    GtkWidget *compress_dialog;
    GtkWidget *error_revealer;
    GtkWidget *error_label;
    GtkWidget *name_entry;
    GtkWidget *activate_button;
    GtkWidget *extension_dropdown;
    GtkSizeGroup *extension_sizegroup;
    GtkWidget *passphrase_label;
    GtkWidget *passphrase_entry;

    builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-compress-dialog.ui");
    compress_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "compress_dialog"));
    error_revealer = GTK_WIDGET (gtk_builder_get_object (builder, "error_revealer"));
    error_label = GTK_WIDGET (gtk_builder_get_object (builder, "error_label"));
    name_entry = GTK_WIDGET (gtk_builder_get_object (builder, "name_entry"));
    activate_button = GTK_WIDGET (gtk_builder_get_object (builder, "activate_button"));
    extension_dropdown = GTK_WIDGET (gtk_builder_get_object (builder, "extension_dropdown"));
    extension_sizegroup = GTK_SIZE_GROUP (gtk_builder_get_object (builder, "extension_sizegroup"));
    passphrase_label = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_label"));
    passphrase_entry = GTK_WIDGET (gtk_builder_get_object (builder, "passphrase_entry"));

    gtk_window_set_transient_for (GTK_WINDOW (compress_dialog),
                                  parent_window);

    self = g_object_new (NAUTILUS_TYPE_COMPRESS_DIALOG_CONTROLLER,
                         "error-revealer", error_revealer,
                         "error-label", error_label,
                         "name-entry", name_entry,
                         "activate-button", activate_button,
                         "containing-directory", destination_directory, NULL);

    self->compress_dialog = compress_dialog;
    self->activate_button = activate_button;
    self->error_label = error_label;
    self->name_entry = name_entry;
    self->extension_dropdown = extension_dropdown;
    self->extension_sizegroup = extension_sizegroup;
    self->passphrase_label = passphrase_label;
    self->passphrase_entry = passphrase_entry;

    extension_dropdown_setup (self);

    self->response_handler_id = g_signal_connect (compress_dialog,
                                                  "response",
                                                  (GCallback) compress_dialog_controller_on_response,
                                                  self);

    g_signal_connect (self->passphrase_entry, "changed",
                      G_CALLBACK (passphrase_entry_on_changed), self);
    g_signal_connect (self->passphrase_entry, "icon-press",
                      G_CALLBACK (passphrase_entry_on_icon_press), self);
    g_signal_connect (self->activate_button, "notify::sensitive",
                      G_CALLBACK (activate_button_on_sensitive_notify), self);
    g_signal_connect_swapped (self->extension_dropdown, "notify::selected-item",
                              G_CALLBACK (update_selected_format), self);

    if (initial_name != NULL)
    {
        gtk_editable_set_text (GTK_EDITABLE (name_entry), initial_name);
    }

    gtk_widget_show (compress_dialog);

    return self;
}

static void
nautilus_compress_dialog_controller_init (NautilusCompressDialogController *self)
{
}

static void
nautilus_compress_dialog_controller_finalize (GObject *object)
{
    NautilusCompressDialogController *self;

    self = NAUTILUS_COMPRESS_DIALOG_CONTROLLER (object);

    if (self->compress_dialog != NULL)
    {
        g_clear_signal_handler (&self->response_handler_id, self->compress_dialog);
        gtk_window_destroy (GTK_WINDOW (self->compress_dialog));
        self->compress_dialog = NULL;
    }

    g_free (self->passphrase);

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

static void
nautilus_compress_dialog_controller_class_init (NautilusCompressDialogControllerClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    NautilusFileNameWidgetControllerClass *parent_class = NAUTILUS_FILE_NAME_WIDGET_CONTROLLER_CLASS (klass);

    object_class->finalize = nautilus_compress_dialog_controller_finalize;

    parent_class->get_new_name = nautilus_compress_dialog_controller_get_new_name;
    parent_class->name_is_valid = nautilus_compress_dialog_controller_name_is_valid;
}

const gchar *
nautilus_compress_dialog_controller_get_passphrase (NautilusCompressDialogController *self)
{
    return self->passphrase;
}
