/*
 * Copyright (C) 2020 ~ 2021 Uniontech Software Technology Co., Ltd.
 *
 * Author:     yanghao<yanghao@uniontech.com>
 *
 * Maintainer: zhengyouge<zhengyouge@uniontech.com>
 *             yanghao<yanghao@uniontech.com>
 *             hujianzhong<hujianzhong@uniontech.com>
 *
 * 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 3 of the License, or
 * 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 "jobcontroller.h"
#include "dfileservices.h"
#include "shutil/fileutils.h"
#include "dgvfsfileinfo.h"
#include "models/desktopfileinfo.h"
#include "shutil/dfmfilelistfile.h"

#include <QtConcurrent/QtConcurrent>
#include <QDir>

#define FILE_DEFAULT_ATTRIBUTES "standard::*,etag::*,id::*,access::*,mountable::*,time::*,unix::*,dos::*,\
owner::*,thumbnail::*,preview::*,filesystem::*,gvfs::*,selinux::*,trash::*,recent::*,metadata::*"

#ifndef LOAD_FILE_INTERVAL
#define LOAD_FILE_INTERVAL 50
#endif

JobController::JobController(const DUrl &fileUrl, const DDirIteratorPointer &iterator, bool silent, QObject *parent)
    : QThread(parent)
    , m_silent(silent)
    , m_iterator(iterator)
    , m_fileUrl(fileUrl)
{

}

JobController::JobController(const DUrl &fileUrl, const QStringList &nameFilters,
                             QDir::Filters filters, bool silent, QObject *parent)
    : QThread(parent)
    , m_silent(silent)
    , m_fileUrl(fileUrl)
    , m_nameFilters(nameFilters)
    , m_filters(filters)
{

}

JobController::JobController(const DUrl &fileUrl, const QStringList &nameFilters, QDir::Filters filters, QObject *parent)
    : QThread(parent)
    , m_fileUrl(fileUrl)
    , m_nameFilters(nameFilters)
    , m_filters(filters)
    , mtpIteration(true)
{
}

JobController::~JobController()
{
    stop();
    wait();

    if (cancell) {
        g_object_ref(cancell);
        cancell = nullptr;
    }
}

JobController::State JobController::state() const
{
    return m_state;
}

int JobController::timeCeiling() const
{
    return m_timeCeiling;
}

int JobController::countCeiling() const
{
    return m_countCeiling;
}

void JobController::start()
{
    if (m_state == Started) {
        return;
    }

    if (m_state == Paused) {
        setState(Started);
        waitCondition.wakeAll();

        return;
    }
    setState(Started);
    QThread::start(TimeCriticalPriority);
}

void JobController::pause()
{
    if (m_state != Started)
        return;

    setState(Paused);
}

void JobController::stop()
{
    if (m_state == Stoped)
        return;

    if (m_iterator)
        m_iterator->close();

    setState(Stoped);

    if (cancell)
        g_cancellable_cancel(cancell);

    waitCondition.wakeAll();
}

void JobController::stopAndDeleteLater()
{
    stop();

    if (!isRunning()) {
        deleteLater();
    } else {
        disconnect(this, &JobController::finished, this, &JobController::deleteLater);
        connect(this, &JobController::finished, this, &JobController::deleteLater);
    }
}

void JobController::setTimeCeiling(int timeCeiling)
{
    if (isRunning())
        return;

    m_timeCeiling = timeCeiling;
}

void JobController::setCountCeiling(int countCeiling)
{
    if (isRunning())
        return;

    m_countCeiling = countCeiling;
}

void JobController::run()
{

    if (mtpIteration)
            return gioIterating();

        return normalIterating();
}
void JobController::normalIterating()
{
    m_updateFinished = false;
    if (!m_iterator) {
        const auto &&list = DFileService::instance()->getChildren(this, m_fileUrl, m_nameFilters, m_filters, QDirIterator::NoIteratorFlags, m_silent);

        m_updateFinished = true;
        emit childrenUpdated(list);
        emit addChildrenList(list);

        setState(Stoped);

        return;
    }

    QQueue<DAbstractFileInfoPointer> fileInfoQueue;

    timer.reset(new QElapsedTimer());

    timer->restart();

    bool update_children = true;

    const DAbstractFileInfoPointer &rootInfo = DFileService::instance()->createFileInfo(this, m_fileUrl);
    if (rootInfo && !rootInfo->hasOrderly() && fileInfoQueue.count() > 0) {
        update_children = false;
        emit childrenUpdated(fileInfoQueue);
        emit addChildrenList(fileInfoQueue);
    }
    while (m_iterator->hasNext()) {
        if (m_state == Paused) {
            mutex.lock();
            waitCondition.wait(&mutex);
            mutex.unlock();
        }

        if (m_state == Stoped) {
            break;
        }
        if (!m_iterator->next().isValid())
            continue;

        DAbstractFileInfoPointer fileinfo;
        fileinfo = m_iterator->fileInfo();
        /*fix bug 49039 解决多次调用m_iterator->hasNext导致有重复结果，这里去重*/
        if (!fileinfo || fileInfoQueue.contains(fileinfo))
            continue;

        if (update_children) {
            fileInfoQueue.enqueue(fileinfo);

            if (timer->elapsed() > m_timeCeiling || fileInfoQueue.count() > m_countCeiling) {
                update_children = false;
                emit childrenUpdated(fileInfoQueue);
                emit addChildrenList(fileInfoQueue);

                fileInfoQueue.clear();
            }
        } else {
            fileInfoQueue.enqueue(fileinfo);

            if (timer->elapsed() > m_timeCeiling || fileInfoQueue.count() > m_countCeiling) {
                timer->restart();

                emit addChildrenList(fileInfoQueue);

                fileInfoQueue.clear();
            }

            emit addChildren(fileinfo);

        }
    }
    //刷新已完成
    m_updateFinished = true;

    if (m_state != Stoped)
        emit addChildren(DAbstractFileInfoPointer(nullptr), true);

    if (update_children) {
        emit childrenUpdated(fileInfoQueue);
        emit addChildrenList(fileInfoQueue);
    }

    setState(Stoped);
}

void JobController::setState(JobController::State state)
{
    if (m_state == state)
        return;

    m_state = state;
    emit stateChanged(state);
}

void JobController::gioIterating()
{
    qInfo() << "gioIterating start          m_fileUrl = " << m_fileUrl;
    if (m_state == Stoped) {
        return;
    }

    QQueue<DAbstractFileInfoPointer> fileInfoQueue;

    timer.reset(new QElapsedTimer());

    timer->restart();

    bool update_children = true;

    if (m_state == Stoped) {
        return;
    }

    const DAbstractFileInfoPointer &rootInfo = DFileService::instance()->createFileInfo(this, m_fileUrl);
    if (rootInfo && !rootInfo->hasOrderly() && fileInfoQueue.count() > 0) {
        update_children = false;
        emit childrenUpdated(fileInfoQueue);
        emit addChildrenList(fileInfoQueue);
    }

    g_autoptr(GFileEnumerator) enumerator = nullptr;
    g_autoptr(GError) gerror = nullptr;

    g_autoptr(GFile) gfile = g_file_new_for_uri(m_fileUrl.toString().toStdString().c_str());

    if (m_state != Stoped) {
        cancell = g_cancellable_new();
        enumerator = g_file_enumerate_children(gfile,
                                               FILE_DEFAULT_ATTRIBUTES,
                                               G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                                               cancell,
                                               &gerror);
    }


    if (nullptr == enumerator) {
        if (gerror)
            qCritical() << "gio g_file_enumerate_children = " << m_fileUrl << gerror->message << " fialed !!!!!!!!";
        return;
    }

    GFile *gfileIn = nullptr;
    GFileInfo *gfileInfoIn = nullptr;
    while (m_state != Stoped && g_file_enumerator_iterate(enumerator, &gfileInfoIn, &gfileIn, cancell, &gerror)) {
        QTime tt1;
        tt1.start();
        if (!gfileInfoIn)
            break;

        if (m_state == Paused) {
            mutex.lock();
            waitCondition.wait(&mutex);
            mutex.unlock();
        }

        if (m_state == Stoped) {
            break;
        }

        g_autofree gchar *gpath = g_file_get_path(gfileIn);
        const QString &path = QString::fromLocal8Bit(gpath);

        QFileInfo info(path);

        if (!checkFilter(info))
            continue;

        DAbstractFileInfoPointer fileinfo;

        bool isnotsyslink = info.isSymLink();
        //父目录是gvfs目录，那么子目录和子文件必须是gvfs文件
        bool currentisgvfs =  true;

        if (m_state == Stoped) {
            break;
        }
        QMimeType mimetype;
        bool isdesktop = currentisgvfs ? FileUtils::isDesktopFile(info, mimetype) :
                         FileUtils::isDesktopFile(info);

        if (m_state == Stoped) {
            break;
        }

        if (isnotsyslink && isdesktop) {
            fileinfo = DAbstractFileInfoPointer(new DesktopFileInfo(info));
        } else if (currentisgvfs) {
            fileinfo = DAbstractFileInfoPointer(new DGvfsFileInfo(info, mimetype, true));
        } else {
            fileinfo = DAbstractFileInfoPointer(new DFileInfo(info));
        }

        if (m_state == Stoped) {
            break;
        }

        /*fix bug 49039 解决多次调用m_iterator->hasNext导致有重复结果，这里去重*/
        if (!fileinfo || fileInfoQueue.contains(fileinfo))
            continue;

        if (update_children) {
            fileInfoQueue.enqueue(fileinfo);

            if (timer->elapsed() > m_timeCeiling || fileInfoQueue.count() > m_countCeiling) {
                update_children = false;
                emit childrenUpdated(fileInfoQueue);
                emit addChildrenList(fileInfoQueue);

                fileInfoQueue.clear();
            }
        } else {
            fileInfoQueue.enqueue(fileinfo);

            if (timer->elapsed() > m_timeCeiling || fileInfoQueue.count() > m_countCeiling) {
                timer->restart();

                emit addChildrenList(fileInfoQueue);

                fileInfoQueue.clear();
            }

            emit addChildren(fileinfo);

        }

    }

    if (gerror)
        qCritical() << "gio g_file_enumerator_iterate = " << m_fileUrl << gerror->message << " fialed !!!!!!!!";

    //刷新已完成
    m_updateFinished = true;

    if (m_state != Stoped)
        emit addChildren(DAbstractFileInfoPointer(nullptr), true);

    if (update_children) {
        emit childrenUpdated(fileInfoQueue);
        emit addChildrenList(fileInfoQueue);
    }

    setState(Stoped);
}

bool JobController::checkFilter(const QFileInfo &info)
{
    if (m_filters == QDir::NoFilter)
        return true;

    const bool isDir = info.isDir();
    if ((m_filters & QDir::AllDirs) == QDir::AllDirs) {   // all dir, no apply filters rules
        if (isDir)
            return true;
    }

    // dir filter
    bool ret = true;

    const bool readable = info.isReadable();
    const bool writable = info.isWritable();
    const bool executable = info.isExecutable();

    auto checkRWE = [&]() -> bool {
        if (m_filters & QDir::Readable){
            if (!readable)
                return false;
        }
        if (m_filters & QDir::Writable) {
            if (!writable)
                return false;
        }
        if (m_filters & QDir::Executable) {
            if (!executable)
                return false;
        }
        return true;
    };

    if ((m_filters & QDir::AllEntries)
        || ((m_filters & QDir::Dirs) && (m_filters & QDir::Files))) {
        // 判断读写执行
        if (!checkRWE())
            ret = false;
    } else if (m_filters & QDir::Dirs)  {
        if (!isDir) {
            ret = false;
        } else {
            // 判断读写执行
            if (!checkRWE())
                ret = false;
        }
    } else if (m_filters & QDir::Files) {
        const bool isFile = info.isFile();
        if (!isFile) {
            ret = false;
        } else {
            // 判断读写执行
            if (!checkRWE())
                ret = false;
        }
    }

    if (m_filters & QDir::NoSymLinks) {
        const bool isSymlinks = info.isSymLink();
        if (isSymlinks)
            ret = false;
    }

    const QString &fileInfoName = info.fileName();
    const bool showHidden = (m_filters & QDir::Hidden);
    if (!showHidden && (info.isHidden())) {
        return false;
    }

    // filter name
    const bool caseSensitive = (m_filters & QDir::CaseSensitive);
    if (m_nameFilters.contains(fileInfoName, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive))
        ret = false;

    const bool showDot = !((m_filters & QDir::NoDotAndDotDot)) && !((m_filters & QDir::NoDot));
    const bool showDotDot = !((m_filters & QDir::NoDotAndDotDot)) && !((m_filters & QDir::NoDotDot));
    if (!showDot && fileInfoName == ".")
        ret = false;
    if (!showDotDot && fileInfoName == "..")
        ret = false;

    return ret;
}
