/*
 *
 * 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>
 */

//ukui-search
#include "file-content-search-task.h"
#include "index-status-recorder.h"
#include "chinese-segmentation.h"
#include "result-item.h"
#include "common.h"
#include "file-utils.h"
#include "file-indexer-config.h"

//Qt
#include <QIcon>
#include <QDebug>
#include <QDateTime>

using namespace UkuiSearch;

FileContentSearchTask::FileContentSearchTask(QObject *parent)
{
    this->setParent(parent);
    qRegisterMetaType<size_t>("size_t");
    m_pool = new QThreadPool(this);
    m_pool->setMaxThreadCount(1);
}

FileContentSearchTask::~FileContentSearchTask()
{
    m_pool->clear();
    m_pool->waitForDone();
}

void FileContentSearchTask::setController(const SearchController &searchController)
{
    m_searchController = searchController;
}

PluginInterface::PluginType FileContentSearchTask::pluginType()
{
    return PluginType::SearchTaskPlugin;
}

const QString FileContentSearchTask::name()
{
    return tr("File Content");
}

const QString FileContentSearchTask::description()
{
    return tr("File Content Search");
}

const QIcon FileContentSearchTask::icon()
{
    return QIcon::fromTheme("folder", QIcon(":/unknown.svg"));
}

void FileContentSearchTask::setEnable(bool enable)
{
    m_enable = enable;
}

bool FileContentSearchTask::isEnable()
{
    int state = IndexStatusRecorder::getInstance()->getStatus(CONTENT_INDEX_DATABASE_STATE_KEY).toInt();
    return m_enable && state == IndexStatusRecorder::State::Ready || state == IndexStatusRecorder::State::Updating || state == IndexStatusRecorder::State::RealTimeUpdating;
}

QString FileContentSearchTask::getCustomSearchType()
{
    return tr("File Content");
}

SearchProperty::SearchType FileContentSearchTask::getSearchType()
{
    return SearchProperty::SearchType::FileContent;
}

void FileContentSearchTask::startSearch()
{
    FileContentSearchWorker *worker = new FileContentSearchWorker(this, &m_searchController);
    m_pool->start(worker);
}

void FileContentSearchTask::stop()
{

}

bool FileContentSearchTask::isSearching()
{
    return m_pool->activeThreadCount() > 0;
}

FileContentSearchWorker::FileContentSearchWorker(FileContentSearchTask *fileContentSearchTask, SearchController *searchController)
{
    m_fileContentSearchTask = fileContentSearchTask;
    m_searchController = searchController;
    m_currentSearchId = m_searchController->getCurrentSearchId();
}

void FileContentSearchWorker::run()
{
    QStringList searchDirs = m_searchController->getSearchDir();
    searchDirs.removeDuplicates();

    for (QString &dir : searchDirs) {
        if (dir.endsWith("/") && dir != "/") {
            dir = dir.mid(0, dir.length() - 1);
        }

        if (underBlocked(dir)) {
            m_skipCheckDirs.append(dir);
        }

        m_validDirectories.append(dir);
    }

    bool finished = true;
    if (m_fileContentSearchTask->isEnable()) {
        qDebug() << "content index enable";
        finished = execSearch();

    } else {
        qWarning() << "content index incomplete";
        sendErrorMsg(QObject::tr("Content index incomplete."));

        finished = false;
    }

    if (finished) QMetaObject::invokeMethod(m_fileContentSearchTask, "searchFinished", Q_ARG(size_t, m_currentSearchId));
}

bool FileContentSearchWorker::underBlocked(const QString &path) const
{
    for (const QString& skipDir : m_skipCheckDirs) {
        if (FileUtils::isOrUnder(path, skipDir)) {
            return false;
        }
    }

    QStringList blockList = DirWatcher::getDirWatcher()->getBlockDirsOfUser();
    for(const QString& i : blockList) {
        if(FileUtils::isOrUnder(path, i))
            return true;
    }
    return false;
}

void FileContentSearchWorker::sendErrorMsg(const QString &msg)
{
    QMetaObject::invokeMethod(m_fileContentSearchTask, "searchError",
                              Q_ARG(size_t, m_currentSearchId),
                              Q_ARG(QString, msg));
}

bool FileContentSearchWorker::execSearch()
{
    try {
        Xapian::Database db(CONTENT_INDEX_PATH.toStdString());
        if(FileIndexerConfig::getInstance()->isOCREnable()) {
            db.add_database(Xapian::Database(OCR_CONTENT_INDEX_PATH.toStdString()));
        }

        Xapian::Enquire enquire(db);

        enquire.set_query(createQuery());

        FileContentSearchFilter filter(this);

        Xapian::MSet result = enquire.get_mset(0, m_searchController->maxResults(), nullptr, &filter);

        for (auto it = result.begin(); it != result.end(); ++it) {
            if (m_searchController->beginSearchIdCheck(m_currentSearchId)) {
                QString path = QString::fromStdString(it.get_document().get_value(CONTENT_DATABASE_PATH_SLOT));

                SearchResultProperties properties = m_searchController->getResultProperties(SearchProperty::SearchType::FileContent);
                ResultItem resultItem(m_currentSearchId);
                resultItem.setItemKey(path);

                if(properties.contains(SearchProperty::SearchResultProperty::FilePath)) {
                    resultItem.setValue(SearchProperty::SearchResultProperty::FilePath, path);
                }
                if(properties.contains(SearchProperty::SearchResultProperty::FileIconName)) {
                    resultItem.setValue(SearchProperty::SearchResultProperty::FileIconName, FileUtils::getFileIcon(QUrl::fromLocalFile(path).toString()).name());
                }
                if(properties.contains(SearchProperty::SearchResultProperty::FileName)) {
                    resultItem.setValue(SearchProperty::SearchResultProperty::FileName, path.section("/", -1));
                }
                if(properties.contains(SearchProperty::SearchResultProperty::ModifiedTime)) {
                    resultItem.setValue(SearchProperty::SearchResultProperty::ModifiedTime,
                                        QDateTime::fromString(QString::fromStdString(it.get_document().get_value(3)), "yyyyMMddHHmmsszzz"));
                }
                m_searchController->getDataQueue()->enqueue(resultItem);
                if(++m_resultNum == m_searchController->informNum()) {
                    QMetaObject::invokeMethod(m_fileContentSearchTask, "reachInformNum");
                    m_resultNum = 0;
                }
                m_searchController->finishSearchIdCheck();

            } else {
                qDebug() << "Search id changed!";
                m_searchController->finishSearchIdCheck();
                return false;
            }
        }

        return true;

    } catch (const Xapian::Error &e) {
        qWarning() << QString::fromStdString(e.get_description());
        sendErrorMsg("Xapian Error: " + QString::fromStdString(e.get_description()));
        return false;
    }
}

inline Xapian::Query FileContentSearchWorker::createQuery()
{
    std::vector<Xapian::Query> v;

    for (auto &keyword : m_searchController->getKeyword()) {
        std::vector<KeyWord> sKeyWord = ChineseSegmentation::getInstance()->callSegment(keyword);

        for(const auto & c : sKeyWord) {
            v.emplace_back(c.word);
        }
    }
    Xapian::Query query;
    if(FileIndexerConfig::getInstance()->isFuzzySearchEnable()) {
        query = Xapian::Query(Xapian::Query::OP_OR, v.begin(), v.end());
    } else {
        query = Xapian::Query(Xapian::Query::OP_AND, v.begin(), v.end());
    }

    return query;
}


FileContentSearchFilter::FileContentSearchFilter(FileContentSearchWorker *worker) : m_worker(worker)
{

}

bool FileContentSearchFilter::operator()(const Xapian::Document &doc) const
{
    //在此处对搜索结果进行过滤
    QString path = QString::fromStdString(doc.get_value(CONTENT_DATABASE_PATH_SLOT));
    if(!QFileInfo::exists(path)) {
        return false;
    }

    if (m_worker->underBlocked(path)) {
        return false;
    }

    //如果不指定搜索目录，那么搜索整个数据库
    if (m_worker && !m_worker->m_validDirectories.empty()) {
        for(const QString &dir : m_worker->m_validDirectories) {
            return FileUtils::isOrUnder(path, dir);
        }
    }

    return true;
}
