#include "file-action-controler.h"
#include "global-settings.h"
#include "clipboard-utils.h"
#include "file-enumerator.h"
#include "file-meta-info.h"
#include "thumbnail-manager.h"
#include "file-info.h"
#include "file-info-job.h"
#include "file-info-manager.h"
#include "file-watcher.h"
#include "file-operation-manager.h"
#include "file-move-operation.h"
#include "file-trash-operation.h"
#include "file-copy-operation.h"
#include "file-operation-utils.h"
#include "file-utils.h"
#include "desktop-menu.h"
#include "peony-desktop-application.h"
#include "audio-play-manager.h"
#include "file-launch-manager.h"
#include "file-rename-operation.h"
#include "file-batch-rename-operation.h"
#include "advanced-desktop-item-model.h"
#include "image-mount-manager.h"

#include <QtX11Extras/QX11Info>
#include <kstartupinfo.h>

#include <QStandardPaths>
#include <QApplication>
#include <QDebug>
#include <QMimeData>
#include <QClipboard>
#include <QMessageBox>
#include <QTimer>
#include <QDir>
#include <QProcess>
using namespace Peony;

static bool meetSpecialConditions(const QStringList& selectedUris)
{
    /* The desktop home directory, computer, and trash do not allow operations such as copying, cutting,
     * deleting, renaming, moving, or using shortcut keys for corresponding operations.add by 2021/06/17 */
    static QString homeUri = "file://" +  QStandardPaths::writableLocation(QStandardPaths::HomeLocation);

    if (selectedUris.contains("computer:///")
       ||selectedUris.contains("trash:///")
       ||selectedUris.contains(homeUri)){
        return true;
    }

    return false;
}

static FileActionController *global_instance = nullptr;

FileActionController *FileActionController::getInstance()
{
    if (!global_instance) {
        global_instance = new FileActionController;
    }
    return global_instance;
}

FileActionController::FileActionController(QObject *parent)
{
    m_directryUri = "file://" + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
    auto zoolLevel = getZoomLevel();
    setDefaultZoomLevel(zoolLevel);
}

bool FileActionController::enableShortcutKeys()
{
    return GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool();
}

void FileActionController::fileCopy(const QStringList &selectedUris)
{
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }
    qDebug() << "copy:" << selectedUris;
    if (!selectedUris.isEmpty() && !meetSpecialConditions(selectedUris)){
        ClipboardUtils::setClipboardFiles(selectedUris, false);
    }
}

void FileActionController::fileCut(const QStringList &selectedUris)
{
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }
    Q_EMIT changeOpacity(0.5);
    if (!selectedUris.isEmpty() && !meetSpecialConditions(selectedUris))
    {
        ClipboardUtils::setClipboardFiles(selectedUris, true);
    }
}

void FileActionController::filePaste(const QStringList &selectedUris)
{
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }

    Q_EMIT changeOpacity(1);
    if (qApp->clipboard()->mimeData()->hasFormat ("uos/remote-copy")) {
        auto op = ClipboardUtils::pasteClipboardFiles(m_directryUri);
        if (!op) {
           // viewport()->update();
        }
    } else {
        paste(selectedUris);
    }
}

void FileActionController::paste(const QStringList &selectedUris)
{
    //auto clipUris = ClipboardUtils::getClipboardFilesUris();
    if (ClipboardUtils::getInstance()->isClipboardHasFiles() && !meetSpecialConditions(selectedUris)) {
        auto op = ClipboardUtils::pasteClipboardFiles(m_directryUri);
        if (op) {
            connect(op, &Peony::FileOperation::operationFinished, this, [=](){
                auto opInfo = op->getOperationInfo();
                auto targetUirs = opInfo->dests();
                QTimer::singleShot(300, this, [=](){
                    for (QString uri : targetUirs) {
                        auto origin = FileUtils::getOriginalUri(uri);
                        Q_EMIT setSelectedUris(QStringList()<<origin);
                    }
                });
            }, Qt::BlockingQueuedConnection);
        }
    }
}

void FileActionController::deleteToTrash(const QStringList &selectedUris)
{
    //add CTRL+D for delete operation
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }

    if (!selectedUris.isEmpty() && !meetSpecialConditions(selectedUris)){
       FileOperationUtils::trash(selectedUris, true);
    }
}

void FileActionController::undo()
{
    qApp->setProperty("rename", true);
    FileOperationManager::getInstance()->undo();
}

void FileActionController::redo()
{
    // do not relayout item with redo.
    qApp->setProperty("rename", true);
    FileOperationManager::getInstance()->redo();
}

void FileActionController::zoomIn()
{
    if (Huge == m_zoom_level) {
        return;
    }
    switch (getZoomLevel()) {
    case Small:
        m_zoom_level = Normal;
        break;
    case Normal:
        m_zoom_level = Large;
        break;
    case Large:
        m_zoom_level = Huge;
        break;
    default:
        break;
    }
    setDefaultZoomLevel(m_zoom_level);
}

void FileActionController::zoomOut()
{
    if (Small == m_zoom_level) {
        return;
    }
    switch (getZoomLevel()) {
    case Huge:
        m_zoom_level = Large;
        break;
    case Large:
        m_zoom_level = Normal;
        break;
    case Normal:
        m_zoom_level = Small;
        break;
    default:
        break;
    }
    setDefaultZoomLevel(m_zoom_level);
}

void FileActionController::setDefaultZoomLevel(ZoomLevel level)
{
    qDebug()<<"set default zoom level:"<<level;
    m_zoom_level = level;
    QSize newIconSize;
    int emblemSize = 16;
//    m_margin = QPoint(10, 5);
    switch (level) {
    case Small:{
//        m_margin *= 0.8;
        emblemSize = 8;
        newIconSize = QSize(24, 24);
        break;
    }
    case Large:
//        m_margin *= 1.2;
        emblemSize = 24;
        newIconSize = QSize(64, 64);
        break;
    case Huge:
//        m_margin *= 1.4;
        emblemSize = 32;
        newIconSize = QSize(96, 96);
        break;
    default:
        m_zoom_level = Normal;
        newIconSize = QSize(48, 48);
        break;
    }

    auto metaInfo = FileMetaInfo::fromUri("computer:///");
    if (metaInfo) {
        qDebug()<<"set zoom level"<<m_zoom_level;
        metaInfo->setMetaInfoInt("peony-qt-desktop-zoom-level", int(m_zoom_level));
    }

    QSize newGridSize = QSize(10, 10); // padding: 5
    newGridSize += newIconSize; // icon size
    newGridSize += QSize(0, 5); // text-icon-padding: 5
    QFont font = qApp->font();
    font.setPointSize(15);
    QFontMetrics fm = QFontMetrics(font);
    newGridSize += QSize(0, fm.height()*2); // 2 line text height, todo: fix bo_CN
    newGridSize.setWidth(qMax(qApp->fontMetrics().averageCharWidth()*5 + 10, newGridSize.width() + 31));

    GlobalSettings::getInstance()->setValue(DEFAULT_DESKTOP_ZOOM_LEVEL, m_zoom_level);

    setEmblemSize(emblemSize);
    setIconSize(newIconSize);
    setGridSize(newGridSize);
}

ZoomLevel FileActionController::getZoomLevel() const
{
    //FIXME:
    if (m_zoom_level != ZoomLevel::Invalid)
        return m_zoom_level;

    auto metaInfo = FileMetaInfo::fromUri("computer:///");
    if (metaInfo) {
        auto i = metaInfo->getMetaInfoInt("peony-qt-desktop-zoom-level");
        return ZoomLevel(i);
    }

    GFile *computer = g_file_new_for_uri("computer:///");
    GFileInfo *info = g_file_query_info(computer,
                                        "metadata::peony-qt-desktop-zoom-level",
                                        G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                        nullptr,
                                        nullptr);
    char* zoom_level = g_file_info_get_attribute_as_string(info, "metadata::peony-qt-desktop-zoom-level");
    if (!zoom_level) {
        //qDebug()<<"======================no zoom level meta info\n\n\n";
        g_object_unref(info);
        g_object_unref(computer);
        return Normal;
    }
    g_object_unref(info);
    g_object_unref(computer);
    QString zoomLevel = zoom_level;
    g_free(zoom_level);
    //qDebug()<<ZoomLevel(QString(zoomLevel).toInt())<<"\n\n\n\n\n\n\n\n";
    return ZoomLevel(zoomLevel.toInt()) == ZoomLevel::Invalid? Normal: ZoomLevel(QString(zoomLevel).toInt());
}

GAsyncReadyCallback FileActionController::queryZoomLevelAsyncCallback(GObject *obj, GAsyncResult *res, FileActionController *p_this)
{
    GError *err = nullptr;
    auto info = g_file_query_info_finish(G_FILE(obj), res, &err);

    if (info) {
        char* zoom_level = g_file_info_get_attribute_as_string(info, "metadata::peony-qt-desktop-zoom-level");
        if (!zoom_level) {
            //qDebug()<<"======================no zoom level meta info\n\n\n";
            g_object_unref(info);
            p_this->setDefaultZoomLevel(Normal);
            return nullptr;
        }
        g_object_unref(info);
        QString zoomLevel = zoom_level;
        g_free(zoom_level);
        int level =(zoomLevel.toInt()) == ZoomLevel::Invalid? Normal: ZoomLevel(QString(zoomLevel).toInt());
        p_this->setDefaultZoomLevel(ZoomLevel(level));
        return nullptr;
    } else {
        if (err) {
            qDebug()<<err->code<<err->message;
            g_error_free(err);
        }
        p_this->setDefaultZoomLevel(Normal);
        return nullptr;
    }
}

void FileActionController::editUri(const QString &selectedUri)
{
    if (selectedUri.isEmpty() || meetSpecialConditions(QStringList(selectedUri))) {
        return;
    }
    auto origin = FileUtils::getOriginalUri(selectedUri);
    QTimer::singleShot(100, this, [=]() {
        Q_EMIT edit(selectedUri);
    });
}

void FileActionController::remove(const QStringList &selectedUris)
{
    //Qt::SHIFT + Qt::Key_Delete
    qDebug() << "=====================" << selectedUris;
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }

    if (!meetSpecialConditions(selectedUris)){
        qDebug() << "delete" << selectedUris;
        FileOperationUtils::executeRemoveActionWithDialog(selectedUris);
    }
}

void FileActionController::help()
{
    PeonyDesktopApplication::showGuide();
}

void FileActionController::showProperties(const QStringList &selectedUris)
{
    //Qt::ALT + Qt::Key_Return ,Qt::ALT + Qt::Key_Enter
    if (!m_menu) {
        delete m_menu;
        m_menu = nullptr;
    }
    m_menu = new DesktopMenu(m_directryUri, selectedUris);
    if (selectedUris.count() > 0) {
        m_menu->showProperties(selectedUris);
    } else {
        QString desktopPath = "file://" +  QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
        m_menu->showProperties(desktopPath);
    }
}

void FileActionController::newFolder()
{
    //QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_N)
    CreateTemplateOperation op(m_directryUri, CreateTemplateOperation::EmptyFolder, tr("New Folder"));
    op.run();
    auto targetUri = op.target();

    QTimer::singleShot(300, this, [=]() {
        QString origin = FileUtils::getOriginalUri(targetUri);
        Q_EMIT edit(origin);
   });
}

void FileActionController::refresh()
{
    //QKeySequence(Qt::CTRL + Qt::Key_R) | Qt::Key_F5
    //fix refresh clear copy files issue, link to bug#109247
    if (Peony::ClipboardUtils::isDesktopFilesBeCut())
        Peony::ClipboardUtils::clearClipboard();/* Refresh clear cut status */

    if (!m_freshing) {
        QGuiApplication::setOverrideCursor(Qt::WaitCursor);
        m_freshing = true;
        PeonyDesktopApplication::getModel()->refresh();
    }
}

void FileActionController::finishRefresh()
{
    QGuiApplication::setOverrideCursor(Qt::ArrowCursor);
    m_freshing = false;
}

void FileActionController::invertSelections()
{
    //QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_L)
    invertSelections();
}

void FileActionController::recoverNormalLevel()
{
    //QKeySequence(Qt::CTRL + Qt::Key_0)
    if (m_zoom_level == Normal)
        return;
    this->setDefaultZoomLevel(Normal);
}

void FileActionController::editUris(const QStringList &selectedUris)
{
    //QList<QKeySequence>()<<QKeySequence(Qt::ALT + Qt::Key_E)<<Qt::Key_F2)
    if (selectedUris.isEmpty() || meetSpecialConditions(selectedUris)) {
        return;
    }
    auto origin = FileUtils::getOriginalUri(selectedUris.first());
    QTimer::singleShot(100, this, [=]() {
        Q_EMIT edit(selectedUris.first());
    });
}

void FileActionController::setShowHidden()
{
    //QKeySequence(Qt::CTRL + Qt::Key_H)
    GlobalSettings *settings = GlobalSettings::getInstance();
    if (settings->isExist(SHOW_HIDDEN_PREFERENCE)) {
        bool showHidden = settings->getValue(SHOW_HIDDEN_PREFERENCE).toBool();
        settings->setValue(SHOW_HIDDEN_PREFERENCE, !showHidden);
    }
}

void FileActionController::clearClipboard()
{
    //Qt::Key_Escape
    Peony::ClipboardUtils::clearClipboard();
//    Q_EMIT update();
}

void FileActionController::setSelectAll()
{
    //QKeySequence::SelectAll
    if (! GlobalSettings::getInstance()->getValue(ENABLE_SHORTCUT_KEYS).toBool()) {
        return ;
    }

    Q_EMIT selectAll();
}

void FileActionController::showMenu(const QStringList &selectedUris, const QPoint &globalPos)
{
    if (m_menu) {
        delete m_menu;
        m_menu = nullptr;
    }
    m_menu = new DesktopMenu(m_directryUri, selectedUris);
    connect(m_menu, &DesktopMenu::editUri, this, &FileActionController::editUri);
    connect(m_menu, &DesktopMenu::editUris, this, &FileActionController::editUris);
    connect(m_menu, &DesktopMenu::invertSelections, this, &FileActionController::selectAll);
    connect(m_menu, &DesktopMenu::setDefaultZoomLevel, this, &FileActionController::setDefaultZoomLevel);
    connect(m_menu, &DesktopMenu::refresh, this, &FileActionController::refresh);
    connect(m_menu, &DesktopMenu::changeOpacity, this, &FileActionController::changeOpacity);
    connect(m_menu, &DesktopMenu::paste, this, &FileActionController::paste);
    m_menu->exec(globalPos);
    m_urisToEdit = m_menu->urisToEdit();
}

void FileActionController::openFileByUri(QString uri)
{
    auto info = FileInfo::fromUri(uri);
    auto job = new FileInfoJob(info);
    job->setAutoDelete();
    job->connect(job, &FileInfoJob::queryAsyncFinished, [=]() {
        if ((info->isDir() || info->isVolume() || info->isVirtual())) {
            QDir dir(info->filePath());
            if (! dir.exists())
            {
                Peony::AudioPlayManager::getInstance()->playWarningAudio();
                auto result = QMessageBox::question(nullptr, tr("Open Link failed"),
                                      tr("File not exist, do you want to delete the link file?"),
                                                    QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
                if (result == QMessageBox::Yes) {
                    qDebug() << "Delete unused symbollink in desktop.";
                    QStringList selections;
                    selections.push_back(uri);
                    FileOperationUtils::trash(selections, true);
                }
                return;
            }

            if (! info->uri().startsWith("trash://")
                    && ! info->uri().startsWith("computer://")
                    &&  ! info->canExecute())
            {
                Peony::AudioPlayManager::getInstance()->playWarningAudio();
                QMessageBox::critical(nullptr, tr("Open failed"),
                                      tr("Open directory failed, you have no permission!"));
                return;
            }

#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
            QProcess p;
            QUrl url = uri;
            p.setProgram("peony");
            p.setArguments(QStringList() << url.toEncoded() <<"%U&");
            qint64 pid;
            p.startDetached(&pid);

            // send startinfo to kwindowsystem
            quint32 timeStamp = QX11Info::isPlatformX11() ? QX11Info::appUserTime() : 0;
            KStartupInfoId startInfoId;
            startInfoId.initId(KStartupInfo::createNewStartupIdForTimestamp(timeStamp));
            startInfoId.setupStartupEnv();
            KStartupInfoData data;
            data.setHostname();
            data.addPid(pid);
            QRect rect = info.get()->property("iconGeometry").toRect();
#ifdef KSTARTUPINFO_HAS_SET_ICON_GEOMETRY
            if (rect.isValid())
                data.setIconGeometry(rect);
#endif
            data.setLaunchedBy(getpid());
            KStartupInfo::sendStartup(startInfoId, data);
            KStartupInfo::resetStartupEnv();
#else
            QProcess p;
            QString strq;
            for (int i = 0;i < uri.length();++i) {
                if(uri[i] == ' '){
                    strq += "%20";
                }else{
                    strq += uri[i];
                }
            }

            p.startDetached("/usr/bin/peony", QStringList()<<strq<<"%U&");
#endif
        } else {
            if (IMAGE_MOUNT_MANAGER->isImageFile(uri)) {
                IMAGE_MOUNT_MANAGER->mountImage(uri);
                return;
            }
            if (!(info->isDesktopFile() && execSharedFileLink(uri))) {
                FileLaunchManager::openAsync(uri, false, false);
            }
        }
        //this->clearSelection();
    });
    job->queryAsync();
}

void FileActionController::openFileByUri(const QStringList &uris)
{
    QStringList dirs;
    QStringList files;
    QStringList isoFiles;
    for (auto uri : uris) {
        auto info = FileInfo::fromUri(uri);
        if (info->isDir() || info->isVolume()) {
            dirs<<uri;
        } else if (IMAGE_MOUNT_MANAGER->isImageFile(uri)) {
            isoFiles << uri;
        } else {
            files<<uri;
        }
    }
    if (!dirs.isEmpty())
        this->openWindow(dirs);

    if (!files.isEmpty()) {
        Peony::FileLaunchManager::openFilesByDefaultApplications(files);
    }

    for (auto iso_uri: isoFiles) {
        IMAGE_MOUNT_MANAGER->mountImage(iso_uri);
    }
}

void FileActionController::openWindow(const QStringList &uris)
{
    QStringList args;
    for (auto arg : uris) {
        QUrl url = arg;
        args<<QString(url.toEncoded());
    }
    QProcess p;
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
    p.setProgram("/usr/bin/peony");
    p.setArguments(QStringList()<<"--show-folders"<<args);
    p.startDetached();
#else
    p.startDetached("/usr/bin/peony", QStringList()<<"--show-folders"<<args);
#endif
}

bool FileActionController::execSharedFileLink(const QString uri)
{
    auto info = FileInfo::fromUri(uri);
    if (info->isEmptyInfo()) {
        FileInfoJob j(info);
        j.querySync();
    }
    if (uri.endsWith(".desktop")) {
        GKeyFile* key_file = g_key_file_new();
        QUrl url = uri;
        QString desktopfp = url.path();
        g_key_file_load_from_file(key_file, desktopfp.toUtf8().constData(), G_KEY_FILE_KEEP_COMMENTS, nullptr);
        GError* error = NULL;
        if (g_key_file_has_key(key_file, G_KEY_FILE_DESKTOP_GROUP, "X-Peony-CMD", nullptr)) {
            if (g_key_file_has_key(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, nullptr)) {
                g_autofree char* val = g_key_file_get_value(key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, &error);
                if (error) {
                    qWarning() << "get desktop file:" << uri << " name error:" << error->code << " -- " << error->message;
                    g_error_free(error);
                    error = nullptr;
                } else {
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
                    QProcess p;
                    p.setProgram("peony");
                    QString str = val;
                    str = str.replace("peony ","");
                    p.setArguments(QStringList() << str);
                    qint64 pid;
                    p.startDetached(&pid);
                    // send startinfo to kwindowsystem
                    quint32 timeStamp = QX11Info::isPlatformX11() ? QX11Info::appUserTime() : 0;
                    KStartupInfoId startInfoId;
                    startInfoId.initId(KStartupInfo::createNewStartupIdForTimestamp(timeStamp));
                    startInfoId.setupStartupEnv();
                    KStartupInfoData data;
                    data.setHostname();
                    data.addPid(pid);
#ifdef KSTARTUPINFO_HAS_SET_ICON_GEOMETRY
                    QRect rect = info.get()->property("iconGeometry").toRect();
                    if (rect.isValid()) {
                        data.setIconGeometry(rect);
                    }
#endif
                    data.setLaunchedBy(getpid());
                    KStartupInfo::sendStartup(startInfoId, data);
#else
                    QProcess p;
                    QString strq;
                    for (int i = 0;i < uri.length();++i) {
                        if(uri[i] == ' '){
                            strq += "%20";
                        }else{
                            strq += uri[i];
                        }
                    }
                    p.startDetached("/usr/bin/peony", QStringList()<<strq<<"%U&");
#endif
                    return true;
                }
            }
        }
    }
    return false;
}

void FileActionController::rename(const QStringList& selections, const QString newName)
{
    qDebug() << "rename operation "<<selections<<newName;
    if (selections.count() > 1) {
        auto fileOpMgr = FileOperationManager::getInstance();
        QStringList lists = selections;
        auto renameOp = new FileBatchRenameOperation(lists, newName);
        qApp->setProperty("rename", true);
        //select file when rename finished
        connect(renameOp, &FileBatchRenameOperation::operationFinished, this, [=](){
            auto info = renameOp->getOperationInfo().get();
            auto uri = info->target();
            QTimer::singleShot(100, this, [=](){
                Q_EMIT setSelectedUris(QStringList()<<uri);
            });
        }, Qt::BlockingQueuedConnection);

        fileOpMgr->startOperation(renameOp, true);
    } else if (selections.count() == 1) {
        QString uri = selections.first();
        auto fileOpMgr = FileOperationManager::getInstance();
        auto renameOp = new FileRenameOperation(uri, newName);
        qApp->setProperty("rename", true);

        //select file when rename finished
        connect(renameOp, &FileRenameOperation::operationFinished, this, [=](){
            auto info = renameOp->getOperationInfo().get();
            auto uri = info->target();
            QTimer::singleShot(100, this, [=](){
                Q_EMIT setSelectedUris(QStringList()<<uri);
            });
        }, Qt::BlockingQueuedConnection);

        fileOpMgr->startOperation(renameOp, true);
    }
}

QIcon FileActionController::icon(const QString &uri)
{
    FileInfo* info = FileInfo::fromUri(uri).get();
    return info->getIcon();
}
