/*
 * Peony-Qt's Library
 *
 * Copyright (C) 2020, KylinSoft Co., Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Authors: Yue Lan <lanyue@kylinos.cn>
 *
 */

#include "volume-manager.h"
#include "file-utils.h"
#include "gobject-template.h"
#include <QMessageBox>
#include <QFileInfo>
#include <QDebug>

#include "file-info.h"

#include <gio/gunixmounts.h>

using namespace Peony;

static VolumeManager *m_global_manager = nullptr;

VolumeManager::VolumeManager(QObject *parent) : QObject(parent)
{
    m_volume_monitor = g_volume_monitor_get();

    m_drive_connected_handle = g_signal_connect(m_volume_monitor,
                               "drive-connected",
                               G_CALLBACK(drive_connected_callback),
                               this);

    m_drive_disconnected_handle = g_signal_connect(m_volume_monitor,
                                  "drive-disconnected",
                                  G_CALLBACK(drive_disconnected_callback),
                                  this);

    m_volume_added_handle = g_signal_connect(m_volume_monitor,
                            "volume-added",
                            G_CALLBACK(volume_added_callback),
                            this);

    m_volume_removed_handle = g_signal_connect(m_volume_monitor,
                              "volume-removed",
                              G_CALLBACK(volume_removed_callback),
                              this);

    m_mount_added_handle = g_signal_connect(m_volume_monitor,
                                            "mount-added",
                                            G_CALLBACK(mount_added_callback),
                                            this);

    m_mount_removed_handle = g_signal_connect(m_volume_monitor,
                             "mount-removed",
                             G_CALLBACK(mount_removed_callback),
                             this);
}

VolumeManager::~VolumeManager()
{
    disconnect();

    g_signal_handler_disconnect(m_volume_monitor, m_drive_connected_handle);
    g_signal_handler_disconnect(m_volume_monitor, m_drive_disconnected_handle);
    g_signal_handler_disconnect(m_volume_monitor, m_volume_added_handle);
    g_signal_handler_disconnect(m_volume_monitor, m_volume_removed_handle);
    g_signal_handler_disconnect(m_volume_monitor, m_mount_added_handle);
    g_signal_handler_disconnect(m_volume_monitor, m_mount_removed_handle);

    g_object_unref(m_volume_monitor);
}

VolumeManager *VolumeManager::getInstance()
{
    if (!m_global_manager) {
        m_global_manager = new VolumeManager;
    }
    return m_global_manager;
}

void VolumeManager::drive_connected_callback(GVolumeMonitor *monitor,
        GDrive *drive,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    auto data = std::make_shared<Drive>(drive);
    Q_EMIT p_this->driveConnected(data);
}

void VolumeManager::drive_disconnected_callback(GVolumeMonitor *monitor,
        GDrive *drive,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    Q_EMIT p_this->driveDisconnected(std::make_shared<Drive>(drive));
}

void VolumeManager::volume_added_callback(GVolumeMonitor *monitor,
        GVolume *volume,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    Q_EMIT p_this->volumeAdded(std::make_shared<Volume>(volume));
}

void VolumeManager::volume_removed_callback(GVolumeMonitor *monitor,
        GVolume *volume,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    Q_EMIT p_this->volumeRemoved(std::make_shared<Volume>(volume));
}

void VolumeManager::mount_added_callback(GVolumeMonitor *monitor,
        GMount *mount,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    Q_EMIT p_this->mountAdded(std::make_shared<Mount>(mount));
}

void VolumeManager::mount_removed_callback(GVolumeMonitor *monitor,
        GMount *mount,
        VolumeManager *p_this)
{
    Q_UNUSED(monitor);
    Q_EMIT p_this->mountRemoved(std::make_shared<Mount>(mount));
}

void VolumeManager::unmount_cb(GFile *file, GAsyncResult *result, GError **error, QString *targetUri)
{
    bool successed = g_file_unmount_mountable_with_operation_finish(file, result, error);
    if (!successed) {
        if (error) {
            auto err = GErrorWrapper::wrapFrom(g_error_copy(*error));
            QMessageBox::warning(nullptr, tr("Error"), err.get()->message());
        }
    } else {
        VolumeManager::getInstance()->fileUnmounted(*targetUri);
    }
    delete targetUri;
}

void VolumeManager::unmount(const QString &uri)
{
    auto targetUri = new QString;
    *targetUri = FileInfo::fromUri(uri).get()->targetUri();
    auto file = wrapGFile(g_file_new_for_uri(uri.toUtf8().constData()));
    g_file_unmount_mountable_with_operation(file.get()->get(),
                                            G_MOUNT_UNMOUNT_NONE,
                                            nullptr,
                                            nullptr,
                                            GAsyncReadyCallback(unmount_cb),
                                            targetUri);
}

//FIXME: should i add async support?
std::shared_ptr<Mount> VolumeManager::getMountFromUri(const QString &uri)
{
    GFile *file = g_file_new_for_uri(uri.toUtf8().constData());
    if (!file)
        return nullptr;

    std::shared_ptr<Mount> tmp = nullptr;
    GError *err = nullptr;
    GMount *mount = g_file_find_enclosing_mount(file, nullptr, &err);
    if (err) {
        qDebug()<<err->message;
        g_error_free(err);
    }
    g_object_unref(file);
    if (mount) {
        tmp = std::make_shared<Mount>(mount, true);
    }
    return tmp;
}

std::shared_ptr<Drive> VolumeManager::getDriveFromUri(const QString &uri)
{
    GFile *file = g_file_new_for_uri(uri.toUtf8().constData());
    if (!file)
        return nullptr;

    std::shared_ptr<Drive> tmp;
    GMount *mount = g_file_find_enclosing_mount(file, nullptr, nullptr);
    g_object_unref(file);
    if (mount) {
        GDrive *drive = g_mount_get_drive(mount);
        if (drive) {
            tmp = std::make_shared<Drive>(drive, true);
        }

        g_object_unref(mount);
    }
    return tmp;
}

std::shared_ptr<Volume> VolumeManager::getVolumeFromUri(const QString &uri)
{
    GFile *file = g_file_new_for_uri(uri.toUtf8().constData());
    if (!file)
        return nullptr;

    std::shared_ptr<Volume> tmp;
    GMount *mount = g_file_find_enclosing_mount(file, nullptr, nullptr);
    g_object_unref(file);
    if (mount) {
        GVolume *volume = g_mount_get_volume(mount);
        if (volume) {
            tmp = std::make_shared<Volume>(volume, true);
        }

        g_object_unref(mount);
    }
    return tmp;
}

std::shared_ptr<Drive> VolumeManager::getDriveFromMount(const std::shared_ptr<Mount> &mount)
{
    GMount *m = mount->getGMount();
    if (!m) {
        return nullptr;
    }
    GDrive *drive = g_mount_get_drive(m);
    std::shared_ptr<Drive> tmp;
    if (!drive) {
        return nullptr;
    }
    tmp = std::make_shared<Drive>(drive, true);
    return tmp;
}

std::shared_ptr<Volume> VolumeManager::getVolumeFromMount(const std::shared_ptr<Mount> &mount)
{
    GMount *m = mount->getGMount();
    if (!m) {
        return nullptr;
    }
    GVolume *volume = g_mount_get_volume(m);
    std::shared_ptr<Volume> tmp;
    if (!volume) {
        return nullptr;
    }
    tmp = std::make_shared<Volume>(volume, true);
    return tmp;
}

/* Lookup GDrive* from system volume monitor for @unixPath.
 * @unixPath: eg: "/dev/sdb1"
 */
std::shared_ptr<Drive> VolumeManager::getDriveFromSystemByPath(const QString &unixPath)
{
    std::shared_ptr<Drive> tmp = nullptr;
    QFileInfo deviceFile;
    GList *allVolumes,*l;
    GDrive *gdrive = NULL;
    GVolume *gvolume = NULL;
    char *tmpPath = NULL;

    deviceFile.setFile(unixPath);
    if(!deviceFile.exists())
        return nullptr;

    allVolumes = g_volume_monitor_get_volumes(m_volume_monitor);
    for(l = allVolumes; l != NULL; l = l->next){
       gvolume = (GVolume*)l->data;
       tmpPath = g_volume_get_identifier(gvolume,G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
       if(tmpPath){
           if(!unixPath.compare(tmpPath)){
               gdrive = g_volume_get_drive(gvolume);
               g_free(tmpPath);
               break;
           }

           g_free(tmpPath);
       }
   }

   g_list_foreach(allVolumes,(GFunc)g_object_unref,NULL);
   g_list_free(allVolumes);

   if(gdrive)
      tmp = std::make_shared<Drive>(gdrive,true);

   return tmp;
}

/* Find the corresponding /dev device based on the mount point.
 * @mountPoint: it should not start with "file:///". for example /media/user/aaa is correct.
 * @return: return /dev device path or NULL
 */
const char* VolumeManager::getUnixDeviceFileFromMountPoint(const char* mountPoint)
{
    if(!mountPoint)
        return NULL;

    const char *deviceFilePath = NULL;
    GUnixMountEntry* mountEntry = g_unix_mount_for(mountPoint,NULL);
    if(mountEntry)
        deviceFilePath = g_unix_mount_get_device_path(mountEntry);

    return deviceFilePath;
}

QString Drive::name()
{
    if (!m_drive)
        return nullptr;
    char *name = g_drive_get_name(m_drive);
    QString value = name;
    g_free(name);
    return value;
}

QString Drive::iconName()
{
    if (!m_drive)
        return nullptr;
    GIcon *g_icon = g_drive_get_icon(m_drive);
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    QString iconName;
    if(icon_names) {
        iconName= *icon_names;
    } else {
        g_autofree gchar *icon_name = g_icon_to_string(g_icon);
        iconName = icon_name;
    }
    g_object_unref(g_icon);
    if (iconName.isEmpty())
        return "drive-harddisk";
    return iconName;
}

QString Drive::symbolicIconName()
{
    if (!m_drive)
        return nullptr;
    GThemedIcon *g_icon = G_THEMED_ICON(g_drive_get_symbolic_icon(m_drive));
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    g_object_unref(g_icon);
    if (! icon_names)
        return "drive-harddisk";
    return *icon_names;
}

QString Volume::name()
{
    char *name = g_volume_get_name(m_volume);
    QString value = name;
    g_free(name);
    return value;
}

QString Volume::iconName()
{
    GIcon *g_icon = g_volume_get_icon(m_volume);
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    QString iconName;
    if(icon_names) {
        iconName= *icon_names;
    } else {
        g_autofree gchar *icon_name = g_icon_to_string(g_icon);
        iconName = icon_name;
    }
    g_object_unref(g_icon);
    if (iconName.isEmpty())
        return "drive-harddisk";
    return iconName;
}

QString Volume::symbolicIconName()
{
    GThemedIcon *g_icon = G_THEMED_ICON(g_volume_get_symbolic_icon(m_volume));
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    g_object_unref(g_icon);
    if (! icon_names)
        return "drive-harddisk";
    return *icon_names;
}

QString Mount::name()
{
    char *name = g_mount_get_name(m_mount);
    QString value = name;
    g_free(name);
    return value;
}

QString Mount::iconName()
{
    GIcon *g_icon = g_mount_get_icon(m_mount);
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    QString iconName;
    if(icon_names) {
        iconName= *icon_names;
    } else {
        g_autofree gchar *icon_name = g_icon_to_string(g_icon);
        iconName = icon_name;
    }
    g_object_unref(g_icon);
    if (iconName.isEmpty())
        return "drive-harddisk";
    return iconName;
}

QString Mount::symbolicIconName()
{
    GThemedIcon *g_icon = G_THEMED_ICON(g_mount_get_symbolic_icon(m_mount));
    const gchar* const* icon_names = g_themed_icon_get_names(G_THEMED_ICON (g_icon));
    g_object_unref(g_icon);
    if (! icon_names)
        return "drive-harddisk";
    return *icon_names;
}
