/*
   Copyright (C) 2008 Norbert Frese <nf2 at scheinwelt at>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License (LGPL) as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later
   version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "giobridge.h"
#include "giobridge-mount.h"
#include "giobridge-mount-operation.h"

#include <QtCore/QByteRef>
#include <QtCore/QDate>
#include <QtCore/QVarLengthArray>
#include <QtCore/QCoreApplication>
#include <QtCore/QRegExp>
#include <QtCore/QFile>

#include <kdebug.h>
#include <kurl.h>
#include <kcomponentdata.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <ktemporaryfile.h>
#include <klocale.h>
#include <limits.h>
#include <kshell.h>
#include <kmountpoint.h>
#include <kstandarddirs.h>

#include <kio/ioslave_defaults.h>
#include <kde_file.h>
#include <kglobal.h>
#include <kmimetype.h>

#include <glib.h>
#include <gio/gio.h>

using namespace KIO;

// =============== mount utils ================

#define AUTHINFO_EXTRAFIELD_DOMAIN "domain"
#define AUTHINFO_EXTRAFIELD_ANONYMOUS "anonymous"
#define AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET "bypass-cache-and-kwallet"

static void
ask_password_cb (GMountOperation *op,
     const char      *message,
     const char      *default_user,
     const char      *default_domain,
     GAskPasswordFlags flags)
{
    GIOBridgeMountOperation * gioBridgeMountOp = GIOBRIDGE_MOUNT_OPERATION(op);

    kDebug(GIOBRIDGE_DEBUG_AREA)
            << "ask_password_cb need-username=" << ((flags & G_ASK_PASSWORD_NEED_USERNAME) != 0)
            << "anonymous supported=" << ((flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED) != 0)
            <<  " message=" << message << endl;

    AuthInfo authInfo;
    authInfo.url = *gioBridgeMountOp->url;
    authInfo.prompt = QString(message);
    authInfo.setExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET, true);

    if (default_user)
    {
        authInfo.username = QString(default_user);
    }

    if (!(flags & G_ASK_PASSWORD_NEED_USERNAME))
    {
        authInfo.readOnly = true;
    }

    if (flags & G_ASK_PASSWORD_NEED_DOMAIN)
    {
        authInfo.setExtraField(AUTHINFO_EXTRAFIELD_DOMAIN, QString(default_domain));
    }

    if (flags & G_ASK_PASSWORD_NEED_PASSWORD)
    {
        // always?
    }

    if (flags & G_ASK_PASSWORD_ANONYMOUS_SUPPORTED)
    {
        //authInfo.setAnonSupported(true);
        authInfo.setExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS, false);
    }

    if (flags & G_ASK_PASSWORD_SAVING_SUPPORTED)
    {
        authInfo.keepPassword = true;
    }

    if (gioBridgeMountOp->gioBridge->openPasswordDialog(authInfo))
    {
        if (flags & G_ASK_PASSWORD_NEED_USERNAME)
        {
            g_mount_operation_set_username (op, authInfo.username.toUtf8().data());
        }
        g_mount_operation_set_password (op, authInfo.password.toUtf8().data());

        if (authInfo.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid ())
        {
            bool anonFlag = authInfo.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).toBool();
            kDebug(GIOBRIDGE_DEBUG_AREA) << "anonymous selected: " << anonFlag;
            g_mount_operation_set_anonymous (op, anonFlag);
        }

        if (authInfo.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid ())
        {
            kDebug(GIOBRIDGE_DEBUG_AREA) << "domain extra field exists";
            g_mount_operation_set_domain (op, (authInfo.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).toString().toUtf8().data()));
        }

        if (authInfo.keepPassword)
        {
            g_mount_operation_set_password_save(op, G_PASSWORD_SAVE_PERMANENTLY);
        }

        g_mount_operation_reply (op, G_MOUNT_OPERATION_HANDLED);
    }
    else
    {
        // cancelled
        kDebug(GIOBRIDGE_DEBUG_AREA) << "Cancel Button Pressed "<< endl;
        g_mount_operation_reply (op, G_MOUNT_OPERATION_ABORTED);
    }
}


static void
mount_done_cb (GObject *object,
         GAsyncResult *res,
         gpointer user_data)
{
    GIOBridgeMountOperation * gioBridgeMountOp = GIOBRIDGE_MOUNT_OPERATION(user_data);

    gboolean succeeded = g_file_mount_enclosing_volume_finish (G_FILE (object), res, gioBridgeMountOp->mountErr);

    if (!succeeded)
        kDebug(GIOBRIDGE_DEBUG_AREA) << "Error mounting location: " << (*(gioBridgeMountOp->mountErr))->message << endl;

    g_main_loop_quit (gioBridgeMountOp->mainLoop);
}


bool GioBridgeMount::mount(GFile * gFile, GError ** err)
{

    kDebug(GIOBRIDGE_DEBUG_AREA) << "mountUrl: " << m_saveUrl << " gfile:" << g_file_get_uri (gFile)<< endl;

    if (gFile == NULL)
    {
        return false;
    }

    GIOBridgeMountOperation * gioBridgeMountOp = giobridge_mount_operation_new (m_saveGioBridge, new KUrl(m_saveUrl), err);

    g_signal_connect (G_MOUNT_OPERATION(gioBridgeMountOp), "ask_password", (GCallback)ask_password_cb, NULL);
    g_file_mount_enclosing_volume (gFile, G_MOUNT_MOUNT_NONE, G_MOUNT_OPERATION(gioBridgeMountOp), NULL, mount_done_cb, gioBridgeMountOp);

    giobridge_mount_operation_run(gioBridgeMountOp);
    g_object_unref(gioBridgeMountOp);

    if (*err)
    {
        return false;
    }
    return true;
}

bool GioBridgeMount::biMount(GioBridge * gioBridge, GFile * src, GFile * target, GError ** error)
{
    char* srcUri = g_file_get_uri(src);
    char* targetUri = g_file_get_uri(target);

    KUrl srcKUrl(QString::fromUtf8(srcUri));
    KUrl targetKUrl(QString::fromUtf8(targetUri));

    GioBridgeMount autoMounterSrc(gioBridge, srcKUrl);

    autoMounterSrc.mount(src, error);
    if (*error)
    {
        if ((*error)->code != G_IO_ERROR_ALREADY_MOUNTED)
        {
            return false;
        }

        g_error_free(*error);
        *error = NULL;
    }

    GioBridgeMount autoMounterTarget(gioBridge, targetKUrl);
    autoMounterTarget.mount(target, error);

    if (*error)
    {
        if ((*error)->code != G_IO_ERROR_ALREADY_MOUNTED)
        {
            return false;
        }

        g_error_free(*error);
        *error = NULL;
    }
    return true;
}



///////

//GioBridgeMount::GioBridgeMount(GioBridge * gioBridge) : m_saveGioBridge(gioBridge)
//{
//}

GioBridgeMount::GioBridgeMount(GioBridge * gioBridge, const KUrl& url)
        : m_saveGioBridge(gioBridge),
        m_saveUrl(url)
{
}

GFileInputStream* GioBridgeMount::op_g_file_read(GFile *file,
                            GCancellable *cancellable,
                            GError **error)
{
    GFileInputStream* inputStream = g_file_read(file,
            cancellable,
            error);

    if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
    {
        g_error_free(*error);
        *error = NULL;
        mount(file, error);
        if (*error) return NULL;

        inputStream = g_file_read(file,
                    cancellable,
                    error);
    }
    return inputStream;

}

GFileInfo* GioBridgeMount::op_g_file_query_info(GFile *file,
                            const char *attributes,
                            GFileQueryInfoFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{
    GFileInfo* fileInfo = g_file_query_info(file,
                                attributes,
                                flags,
                                cancellable,
                                error);

    if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
    {
        g_error_free(*error);
        *error = NULL;
        mount(file, error);
        if (*error) return NULL;

        fileInfo = g_file_query_info(file,
                                        attributes,
                                        flags,
                                        cancellable,
                                        error);
    }
    return fileInfo;
}

gboolean GioBridgeMount::op_g_file_set_attribute(GFile *file,
                            const char *attribute,
                            GFileAttributeType type,
                            gpointer value_p,
                            GFileQueryInfoFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{
    gboolean success = g_file_set_attribute(file,
                                attribute,
                                type,
                                value_p,
                                flags,
                                cancellable,
                                error);

    if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
    {
        g_error_free(*error);
        *error = NULL;
        mount(file, error);
        if (*error) return FALSE;

        success = g_file_set_attribute(file,
                                    attribute,
                                    type,
                                    value_p,
                                    flags,
                                    cancellable,
                                    error);
    }
    return success;
}

gboolean GioBridgeMount::op_g_file_set_attributes_from_info (GFile *file,
                            GFileInfo *info,
                            GFileQueryInfoFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{
    gboolean success = g_file_set_attributes_from_info (file,
            info,
            flags,
            cancellable,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         mount(file, error);
         if (*error) return FALSE;

         success = g_file_set_attributes_from_info (file,
                 info,
                 flags,
                 cancellable,
                 error);

     }
     return success;
}


gboolean GioBridgeMount::op_g_file_make_directory(GFile *file,
                            GCancellable *cancellable,
                            GError **error)
{
    gboolean success = g_file_make_directory(file,
            cancellable,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         mount(file, error);
         if (*error) return FALSE;

         success = g_file_make_directory(file,
                 cancellable,
                 error);
     }
     return success;
}


GFileOutputStream*  GioBridgeMount::op_g_file_create(GFile *file,
                            GFileCreateFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{
    GFileOutputStream* outputStream = g_file_create(file,
            flags,
            cancellable,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         mount(file, error);
         if (*error) return NULL;

         outputStream = g_file_create(file,
                     flags,
                     cancellable,
                     error);
     }
     return outputStream;
}

GFileOutputStream*  GioBridgeMount::op_g_file_replace(GFile *file,
                            const char *etag,
                            gboolean make_backup,
                            GFileCreateFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{
    GFileOutputStream* outputStream = g_file_replace(file,
            etag,
            make_backup,
            flags,
            cancellable,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         mount(file, error);
         if (*error) return NULL;

         outputStream = g_file_replace(file,
                 etag,
                 make_backup,
                 flags,
                 cancellable,
                 error);
     }
     return outputStream;
}

GFileOutputStream*  GioBridgeMount::op_g_file_append_to(GFile *file,
                                GFileCreateFlags flags,
                                GCancellable *cancellable,
                                GError **error)
{
    GFileOutputStream* outputStream = g_file_append_to(file,
            flags,
            cancellable,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         mount(file, error);
         if (*error) return NULL;

         outputStream = g_file_append_to(file,
                 flags,
                 cancellable,
                 error);

     }
     return outputStream;

}

gboolean GioBridgeMount::op_g_file_copy(
                            GioBridge * gioBridge,
                            GFile *source,
                            GFile *destination,
                            GFileCopyFlags flags,
                            GCancellable *cancellable,
                            GFileProgressCallback progress_callback,
                            gpointer progress_callback_data,
                            GError **error)
{
    gboolean success = g_file_copy(source,
            destination,
            flags,
            cancellable,
            progress_callback,
            progress_callback_data,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         GioBridgeMount::biMount(gioBridge, source, destination, error);  // source or destination?

         if (*error) return FALSE;

         success = g_file_copy(source,
                 destination,
                 flags,
                 cancellable,
                 progress_callback,
                 progress_callback_data,
                 error);
     }
     return success;
}

gboolean GioBridgeMount::op_g_file_move(GioBridge * gioBridge,
                            GFile *source,
                            GFile *destination,
                            GFileCopyFlags flags,
                            GCancellable *cancellable,
                            GFileProgressCallback progress_callback,
                            gpointer progress_callback_data,
                            GError **error)
{
    gboolean success = g_file_move(source,
            destination,
            flags,
            cancellable,
            progress_callback,
            progress_callback_data,
            error);

     if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
     {
         g_error_free(*error);
         *error = NULL;
         GioBridgeMount::biMount(gioBridge, source, destination, error);  // source or destination?
         if (*error) return FALSE;

         success = g_file_move(source,
                 destination,
                 flags,
                 cancellable,
                 progress_callback,
                 progress_callback_data,
                 error);
     }
     return success;
}

// unused, because symlinks are usually local
gboolean GioBridgeMount::op_g_file_make_symbolic_link(GFile *file,
                            const char *symlink_value,
                            GCancellable *cancellable,
                            GError **error)
{
    gboolean success = g_file_make_symbolic_link(file,
            symlink_value,
            cancellable,
            error);

    if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
    {
        g_error_free(*error);
        *error = NULL;
        mount(file, error);
        if (*error) return false;

        success = g_file_make_symbolic_link(file,
                symlink_value,
                cancellable,
                error);
    }
    return success;
}

GFileEnumerator* GioBridgeMount::op_g_file_enumerate_children(GFile *file,
                            const char *attributes,
                            GFileQueryInfoFlags flags,
                            GCancellable *cancellable,
                            GError **error)
{

    GFileEnumerator* enumerator = g_file_enumerate_children(file,
                                attributes,
                                flags,
                                cancellable,
                                error);

    if (*error && (*error)->code == G_IO_ERROR_NOT_MOUNTED)
    {
        g_error_free(*error);
        *error = NULL;
        mount(file, error);
        if (*error) return NULL;

        enumerator = g_file_enumerate_children(file,
                                    attributes,
                                    flags,
                                    cancellable,
                                    error);
    }
    return enumerator;

}



