/**
 * @file gnome-cmd-con-remote.cc
 * @copyright (C) 2001-2006 Marcus Bjurman\n
 * @copyright (C) 2007-2012 Piotr Eljasiak\n
 * @copyright (C) 2013-2023 Uwe Scholz\n
 *
 * @copyright 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.
 *
 * @copyright 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.
 *
 * @copyright 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <config.h>

#include "gnome-cmd-includes.h"
#include "gnome-cmd-data.h"
#include "gnome-cmd-con-remote.h"
#include "gnome-cmd-main-win.h"
#include "gnome-cmd-plain-path.h"
#include "imageloader.h"
#include "utils.h"

using namespace std;


static GnomeCmdConClass *parent_class = nullptr;


static void set_con_mount_failed(GnomeCmdCon *con)
{
    g_return_if_fail(GNOME_CMD_IS_CON(con));
    con->base_gFileInfo = nullptr;
    con->open_result = GnomeCmdCon::OPEN_FAILED;
    con->state = GnomeCmdCon::STATE_CLOSED;
    con->open_failed_msg = con->open_failed_error->message;
}

static void mount_remote_finish_callback(GObject *gobj, GAsyncResult *result, gpointer user_data)
{
    auto con = GNOME_CMD_CON(user_data);
    g_return_if_fail(GNOME_CMD_IS_CON(user_data));
    g_return_if_fail(gobj != nullptr);
    auto gFile = G_FILE(gobj);
    g_return_if_fail(G_IS_FILE(gFile));

    GError *error = nullptr;

    g_file_mount_enclosing_volume_finish(gFile, result, &error);
    if (error && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_ALREADY_MOUNTED))
    {
        DEBUG('m', "Unable to mount enclosing volume: %s\n", error->message);
        con->open_failed_error = g_error_copy(error);
        g_error_free(error);
        set_con_mount_failed(con);
        g_object_unref(gFile);
        return;
    }
    set_con_base_gfileinfo(con);
    con->state = GnomeCmdCon::STATE_OPEN;
    con->open_result = GnomeCmdCon::OPEN_OK;
    g_object_unref(gFile);
}


static void mount_func (GnomeCmdCon *con)
{
    g_return_if_fail(GNOME_CMD_IS_CON(con));

    auto gFile = gnome_cmd_con_create_gfile(con);

    auto uri = g_file_get_uri(gFile);
    DEBUG('m', "Connecting to %s\n", uri);

    auto gMountOperation = gtk_mount_operation_new ((GtkWindow*) main_win);

    g_file_mount_enclosing_volume (gFile,
                                   G_MOUNT_MOUNT_NONE,
                                   gMountOperation,
                                   nullptr,
                                   mount_remote_finish_callback,
                                   con);
}

static gboolean start_mount_func (GnomeCmdCon *con)
{
    g_thread_new (nullptr, (GThreadFunc) mount_func, con);

    return FALSE;
}


static void remote_open (GnomeCmdCon *con)
{
    DEBUG('m', "Opening remote connection\n");

    g_return_if_fail (con->uri != nullptr);

    con->state = GnomeCmdCon::STATE_OPENING;
    con->open_result = GnomeCmdCon::OPEN_IN_PROGRESS;

    if (!con->base_path)
        con->base_path = new GnomeCmdPlainPath(G_DIR_SEPARATOR_S);

    g_timeout_add (1, (GSourceFunc) start_mount_func, con);
}


static void remote_close_callback(GObject *gobj, GAsyncResult *result, gpointer user_data)
{
    auto gMount = (GMount*) gobj;
    auto con = (GnomeCmdCon*) user_data;
    GError *error = nullptr;

    g_mount_unmount_with_operation_finish(gMount, result, &error);

    if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CLOSED))
    {
        gnome_cmd_error_message(_("Disconnect error"), error);
        g_error_free(error);
        return;
    }
    if (error)
        g_error_free(error);

    con->state = GnomeCmdCon::STATE_CLOSED;
    con->open_result = GnomeCmdCon::OPEN_NOT_STARTED;
}

static gboolean remote_close (GnomeCmdCon *con)
{
    GError *error = nullptr;

    gnome_cmd_con_set_default_dir (con, nullptr);
    delete con->base_path;
    con->base_path = nullptr;

    auto uri = gnome_cmd_con_get_uri(con);
    auto gFileTmp = g_file_new_for_uri(uri);
    DEBUG ('m', "Closing connection to %s\n", uri);

    auto gMount = g_file_find_enclosing_mount (gFileTmp, nullptr, &error);
    if (error)
    {
        g_warning("remote_close - g_file_find_enclosing_mount error: %s - %s", uri, error->message);
        g_error_free(error);
        g_object_unref(gFileTmp);
        return false;
    }

    g_mount_unmount_with_operation (
        gMount,
        G_MOUNT_UNMOUNT_NONE,
        nullptr,
        nullptr,
        remote_close_callback,
        con
    );

    g_object_unref(gFileTmp);

    return TRUE;
}


static void remote_cancel_open (GnomeCmdCon *con)
{
    DEBUG('m', "Setting state CANCELLING\n");
    con->state = GnomeCmdCon::STATE_CANCELLING;
}


static gboolean remote_open_is_needed (GnomeCmdCon *con)
{
    return TRUE;
}


static GFile *create_remote_gfile_with_path(GnomeCmdCon *con, GnomeCmdPath *path)
{
    auto gUri = g_uri_build(G_URI_FLAGS_NONE, con->scheme, nullptr, con->hostname, con->port, path->get_path(), nullptr, nullptr);
    auto gFilePathUriString = g_uri_to_string(gUri);
    auto gFile = g_file_new_for_uri(gFilePathUriString);
    g_free(gFilePathUriString);
    return gFile;
}


static GFile *remote_create_gfile (GnomeCmdCon *con, GnomeCmdPath *path)
{
    g_return_val_if_fail (con->uri != nullptr, nullptr);

    if (path)
    {
        return create_remote_gfile_with_path(con, path);
    }
    else
    {
        auto gFile = g_file_new_for_uri (con->uri);
        return gFile;
    }
}


static GnomeCmdPath *remote_create_path (GnomeCmdCon *con, const gchar *path_str)
{
    return new GnomeCmdPlainPath(path_str);
}


/*******************************
 * Gtk class implementation
 *******************************/

static void destroy (GtkObject *object)
{
    auto con_remote = GNOME_CMD_CON_REMOTE (object);

    gnome_cmd_pixmap_free (con_remote->parent.go_pixmap);
    gnome_cmd_pixmap_free (con_remote->parent.open_pixmap);
    gnome_cmd_pixmap_free (con_remote->parent.close_pixmap);

    if (GTK_OBJECT_CLASS (parent_class)->destroy)
        (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}


static void class_init (GnomeCmdConRemoteClass *klass)
{
    GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass);
    GnomeCmdConClass *con_class = GNOME_CMD_CON_CLASS (klass);

    parent_class = static_cast<GnomeCmdConClass*> (gtk_type_class (GNOME_CMD_TYPE_CON));

    object_class->destroy = destroy;

    con_class->open = remote_open;
    con_class->close = remote_close;
    con_class->cancel_open = remote_cancel_open;
    con_class->open_is_needed = remote_open_is_needed;
    con_class->create_gfile = remote_create_gfile;
    con_class->create_path = remote_create_path;
}


static void init (GnomeCmdConRemote *remote_con)
{
    guint dev_icon_size = gnome_cmd_data.dev_icon_size;
    gint icon_size;

    g_assert (gtk_icon_size_lookup (GTK_ICON_SIZE_LARGE_TOOLBAR, &icon_size, nullptr));

    GnomeCmdCon *con = GNOME_CMD_CON (remote_con);

    con->method = CON_FTP;
    con->should_remember_dir = TRUE;
    con->needs_open_visprog = TRUE;
    con->needs_list_visprog = TRUE;
    con->can_show_free_space = FALSE;
    con->is_local = FALSE;
    con->is_closeable = TRUE;
    con->go_pixmap = gnome_cmd_pixmap_new_from_icon (gnome_cmd_con_get_icon_name (con), dev_icon_size);
    con->open_pixmap = gnome_cmd_pixmap_new_from_icon (gnome_cmd_con_get_icon_name (con), dev_icon_size);
    con->close_pixmap = gnome_cmd_pixmap_new_from_icon (gnome_cmd_con_get_icon_name (con), icon_size);

    if (con->close_pixmap)
    {
        GdkPixbuf *overlay = gdk_pixbuf_copy (con->close_pixmap->pixbuf);

        if (overlay)
        {
            GdkPixbuf *umount = IMAGE_get_pixbuf (PIXMAP_OVERLAY_UMOUNT);

            if (umount)
            {
                gdk_pixbuf_copy_area (umount, 0, 0,
                                      MIN (gdk_pixbuf_get_width (umount), icon_size),
                                      MIN (gdk_pixbuf_get_height (umount), icon_size),
                                      overlay, 0, 0);
                // FIXME: free con->close_pixmap here
                con->close_pixmap = gnome_cmd_pixmap_new_from_pixbuf (overlay);
            }

            g_object_unref (overlay);
        }
    }
}



/***********************************
 * Public functions
 ***********************************/

GtkType gnome_cmd_con_remote_get_type ()
{
    static GtkType type = 0;

    if (type == 0)
    {
        GtkTypeInfo info =
        {
            (gchar*) "GnomeCmdConRemote",
            sizeof (GnomeCmdConRemote),
            sizeof (GnomeCmdConRemoteClass),
            (GtkClassInitFunc) class_init,
            (GtkObjectInitFunc) init,
            /* reserved_1 */ nullptr,
            /* reserved_2 */ nullptr,
            (GtkClassInitFunc) nullptr
        };

        type = gtk_type_unique (GNOME_CMD_TYPE_CON, &info);
    }
    return type;
}

/**
 * Logic for setting up a new remote connection accordingly to the given uri_str.
 */
GnomeCmdConRemote *gnome_cmd_con_remote_new (const gchar *alias, const string &uri_str)
{
    auto gnomeCmdConRemote = static_cast<GnomeCmdConRemote*> (g_object_new (GNOME_CMD_TYPE_CON_REMOTE, nullptr));

    g_return_val_if_fail (gnomeCmdConRemote != nullptr, nullptr);

    GError *error = nullptr;

    gchar *scheme = nullptr;
    gchar *user = nullptr;
    gchar *host = nullptr;
    gint port = -1;
    gchar *path = nullptr;

    g_uri_split_with_user (
        uri_str.c_str(),
        G_URI_FLAGS_HAS_PASSWORD,
        &scheme,
        &user,
        nullptr, //password
        nullptr, //auth_params
        &host, //host
        &port, //port
        &path, //path
        nullptr, //query
        nullptr, //fragment
        &error
    );
    if (error)
    {
        g_warning("gnome_cmd_con_remote_new - g_uri_split error: %s", error->message);
        g_error_free(error);
        return nullptr;
    }

    GnomeCmdCon *con = GNOME_CMD_CON (gnomeCmdConRemote);

    gnome_cmd_con_set_alias (con, alias);
    gnome_cmd_con_set_uri (con, uri_str.c_str());
    gnome_cmd_con_set_scheme(con, scheme);
    gnome_cmd_con_set_user_name (con, user);
    gnome_cmd_con_set_host_name (con, host);
    gnome_cmd_con_set_port (con, port);
    gnome_cmd_con_set_root_path (con, path);

    gnome_cmd_con_remote_set_tooltips (gnomeCmdConRemote, host);

    con->method = gnome_cmd_con_get_scheme (uri_str.c_str());

    g_free (scheme);
    g_free(host);
    g_free (path);
    g_free (user);

    return gnomeCmdConRemote;
}


void gnome_cmd_con_remote_set_tooltips (GnomeCmdConRemote *con, const gchar *host_name)
{
    g_return_if_fail (con != nullptr);
    if (!host_name)
    {
        return;
    }

    GNOME_CMD_CON (con)->open_tooltip = g_strdup_printf (_("Opens remote connection to %s"), host_name);
    GNOME_CMD_CON (con)->close_tooltip = g_strdup_printf (_("Closes remote connection to %s"), host_name);
}
