/*
 *
 * Copyright (C) 2023, KylinSoft Co., Ltd.
 *
 * 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
 * (at your option) 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 <https://www.gnu.org/licenses/>.
 *
 * Authors: iaom <zhangpengfei@kylinos.cn>
 */
#include "dir-watcher.h"

#include <QDebug>
#include <QDir>
#include <QDBusConnection>
#include <QDBusArgument>
#include <QThread>
#include <fstab.h>
#include <QMutexLocker>
#include "config.h"
#include "fileindexserviceadaptor.h"
#include "volume-manager.h"

#define CURRENT_INDEXABLE_DIR_SETTINGS QDir::homePath() + "/.config/org.ukui/ukui-search/ukui-search-current-indexable-dir.conf"
#define INDEXABLE_DIR_VALUE "IndexableDir"
#define DEFAULT_INDEXABLE_DIR QDir::homePath()

static std::once_flag flag;
static DirWatcher *global_intance = nullptr;

DirWatcher::DirWatcher(QObject *parent) : QObject(parent)
{
    //兼容旧版配置
    Config::self()->processCompatibleCache();

    connect(VolumeManager::self(), &VolumeManager::MountAdded, this, &DirWatcher::mountAdded);
    connect(VolumeManager::self(), &VolumeManager::UDiskMounted, this, &DirWatcher::onUdiskMounted);
    new FileindexAdaptor(this);
}

DirWatcher::~DirWatcher()
{

}

void DirWatcher::refreshExtraDirs()
{
    QStringList before = extraDirs();
    handleExtraDirsConfigRefreshed();
    QStringList after = extraDirs();

    auto dirs2BAdded = QSet<QString>(before.begin(), before.end()) - QSet<QString>(before.begin(), before.end());
    auto dirs2BRemoved = QSet<QString>(before.begin(), before.end()) - QSet<QString>(before.begin(), before.end());

    if (dirs2BAdded.isEmpty() && dirs2BRemoved.isEmpty()) {
        return;
    }

    for (const QString& addPath : dirs2BAdded) {
        Q_EMIT this->appendIndexItem(addPath, {});
        qDebug() << "Add extra dir:" << addPath;
    }

    for (const QString& removePath : dirs2BRemoved) {
        Q_EMIT this->removeIndexItem(removePath);
        qDebug() << "Remove extra dir:" << removePath;
    }
}

void DirWatcher::handleExtraDirsConfigRefreshed()
{
    Config::self()->setExtraDirs(extraDirsOfPaths(Config::self()->searchDirs()));
}

void DirWatcher::onUdiskMounted(const QString &mountPoint)
{
    qDebug() << "Udisk mounted" << mountPoint;
    refreshExtraDirs();
}

DirWatcher *DirWatcher::getDirWatcher()
{
    std::call_once(flag, [ & ] {
        global_intance = new DirWatcher();
    });
    return global_intance;
}

QStringList DirWatcher::currentIndexableDir()
{
    return currentSearchDirs();
}


QStringList DirWatcher::currentBlackListOfIndex()
{
    return currentBlackList();
}

/**
 * @brief DirWatcher::blackListOfDir 根据传入目录返回当前目录下的所有不可搜索目录，没有则返回空列表
 * @param dirPath 要搜索的目录
 */
QStringList DirWatcher::blackListOfDir(const QString &dirPath)
{
    return SearchDir::blackListOfDir(dirPath);
}

QStringList DirWatcher::extraDirs()
{
    return Config::self()->extraDirs();
}

QStringList DirWatcher::extraDirsOfPaths(const QStringList &paths)
{
    QStringList extraDirs;
    for (const auto& uDisk : VolumeManager::self()->uDiskVolumes()) {
        for (const auto& mountPoint : uDisk.mountPoints()) {
            bool needCheck(false);
            for (const auto &indexDir: paths) {
                if (mountPoint.startsWith(indexDir + "/") || indexDir == "/") {
                    needCheck = true;
                    break;
                }
            }

            if (!needCheck) {
                continue;
            }

            bool underGlobalBlacklist(false);
            QStringList blackDirs;
            if (paths == Config::self()->searchDirs()) {
                blackDirs = Config::self()->blackDirs();
            } else {
                for (const auto& path : paths) {
                    blackDirs.append(blackListOfDir(path));
                }
            }
            for (const auto &blackPath: blackDirs) {
                if (mountPoint.startsWith(blackPath + "/") || blackPath == mountPoint) {
                    if (Config::self()->globalBlackList().contains(blackPath)) {
                        underGlobalBlacklist = true;
                    } else {
                        underGlobalBlacklist = false;
                    }
                }
            }

            if (underGlobalBlacklist) {
                extraDirs << mountPoint;
                break;
            }
        }
    }
    return extraDirs;
}

void DirWatcher::initDbusService()
{
    QDBusConnection sessionBus = QDBusConnection::sessionBus();
    if (!sessionBus.registerObject("/org/ukui/search/fileindex", this, QDBusConnection::ExportAdaptors)){
        qWarning() << "ukui-search-fileindex dbus register object failed reason:" << sessionBus.lastError();
    }
}

QStringList DirWatcher::currentSearchableDir()
{
    return currentSearchDirs();
}

QStringList DirWatcher::searchableDirForSearchApplication()
{
    return currentSearchDirs();
}

int DirWatcher::appendIndexableListItem(const QString &path)
{
    return appendSearchDir(path);
    /* code:
     * 0: successful
     * 1: path or its parent dir has been added
     * 2: path is or under blacklist
     * 3: path is in repeat mounted devices and another path which is in the same device has been indexed
     * 4: another path which is in the same device has been indexed
     * 5: path is not exists
     */
}

bool DirWatcher::removeIndexableListItem(const QString &path)
{
    removeSearchDir(path);
    return true;
}

int DirWatcher::appendSearchDir(const QString &path)
{
    SearchDir dir(path);
    if (dir.error() == SearchDir::Successful) {
        Q_EMIT this->appendIndexItem(dir.getPath(), dir.getBlackList());
        qDebug() << "Add search dir:" << dir.getPath() << "blacklist:" << dir.getBlackList();

        //要添加已索引目录的父目录，先添加索引，再同步配置文件，从而使得下次读取配置文件时不会将子目录排除掉
        for (const QString &searchDir : Config::self()->searchDirs()) {
            if (searchDir.startsWith(dir.getPath() + "/") || dir.getPath() == "/") {
                SearchDir subDir(searchDir);
                Config::self()->removeDir(subDir);
            }
        }

        dir.generateBlackList();
        Config::self()->addDir(dir);
        refreshExtraDirs();
    }
    qWarning() << dir.getPath() << dir.errorString();
    return dir.error();
}

void DirWatcher::removeSearchDir(const QString &path)
{
    SearchDir dir(path, false);
    Config::self()->removeDir(dir);
    Q_EMIT this->removeIndexItem(path);
    refreshExtraDirs();
    return;
}

QStringList DirWatcher::currentSearchDirs()
{
    return Config::self()->searchDirs();
}

QStringList DirWatcher::currentBlackList()
{
    return Config::self()->blackDirs();
}

QStringList DirWatcher::blockDirsForUser()
{
    return Config::self()->blockDirsForUser();
}

int DirWatcher::addBlockDirOfUser(const QString &dir)
{
    int res = Config::self()->addBlockDirOfUser(dir);
    if (res == SearchDir::Successful) {
        Q_EMIT this->blockItemAdded(dir);
    }
    return res;
}

void DirWatcher::removeBlockDirOfUser(const QString &dir)
{
    Config::self()->removeBlockDirOfUser(dir);
    Q_EMIT this->blockItemRemoved(dir);
}

bool DirWatcher::showInUkcc(const QString &dir) {
    return Config::self()->showInUkcc(dir);
}
