/*******************************************************************************
**
** SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru>
** SPDX-License-Identifier: BSD-3-Clause
** This file is part of the OfficeViewer project.
**
** Redistribution and use in source and binary forms,
** with or without modification, are permitted provided
** that the following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice,
**   this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice,
**   this list of conditions and the following disclaimer
**   in the documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the names of its contributors
**   may be used to endorse or promote products derived from this software
**   without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
** FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
** OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS;
** OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
** EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*******************************************************************************/


#include "exportedcache.h"

#include <QtConcurrent/QtConcurrent>
#include <QFileInfo>
#include <QDir>
#include <QUrl>
#include <QImage>

uint qHash(const ExportedCache::CacheKey &key)
{
    return qHash(QString("%1_%2").arg(key.fileId).arg(key.format));
}

namespace {
const qint64 LIMIT_CACHE = 5.243e+7; // 50 Mb
}

class LoadWorker : public QRunnable
{
public:
    LoadWorker(ExportedCache *exportedCache) : exportedCache(exportedCache) {}

private:
    void run() override
    {
        exportedCache->loadCache();
    }

    ExportedCache *exportedCache;
};

ExportedCache::ExportedCache(const QString &pathCache, QObject *parent)
    : QObject(parent)
    , m_path(pathCache)
    , m_limit(LIMIT_CACHE)
    , m_currentCacheSize(0)
    , m_loading(false)
{
    static int nCPUs = 0;

    if (nCPUs == 0)
        while (QDir().exists(QString("/sys/devices/system/cpu/cpu") + QString::number(nCPUs)))
            ++nCPUs;

    m_threadPool.setMaxThreadCount(nCPUs);
    QDir().mkpath(pathCache);
    m_threadPool.start(new LoadWorker(this), -1);
}

ExportedCache::~ExportedCache()
{
    m_threadPool.clear();
    m_threadPool.waitForDone();
}

void ExportedCache::loadCache()
{
    m_loading = true;
    QDir dir(m_path);

    for (const auto &fileInfo : dir.entryInfoList({}, QDir::Files)) {
        auto fileId = fileInfo.fileName().toUInt(nullptr, 16);
        m_cache.insert(fileId, fileInfo);
        m_currentCacheSize += fileInfo.size();
        m_cacheSortedByCreated.insert(fileInfo.created(), fileId);
    }

    m_loading = false;
}

void ExportedCache::writeInList(const QString &fileId, const QString &format)
{
    m_mutex.lock();

    auto hash = qHash(ExportedCache::CacheKey(fileId, format));
    auto fileInfo = QFileInfo(makeCachePath(hash));
    m_cache.insert(hash, fileInfo);
    m_cacheSortedByCreated.insert(fileInfo.created(), hash);
    m_currentCacheSize += fileInfo.size();

    while (m_currentCacheSize > m_limit) {
        auto oldFileId = m_cacheSortedByCreated.first();
        m_cacheSortedByCreated.remove(m_cacheSortedByCreated.firstKey());
        auto oldFileInfo = m_cache.value(oldFileId);
        m_cache.remove(oldFileId);
        m_currentCacheSize -= oldFileInfo.size();
        QFile::remove(oldFileInfo.filePath());
    }

    m_mutex.unlock();
}

QString ExportedCache::makeCachePath(quint32 hash)
{
    return QString("%1/%2").arg(m_path).arg(hash, 0, 16);
}


bool ExportedCache::loadExportedDoc(const QString &fileId, const QString &pathExportedDoc, const QString &format)
{
    auto hash = qHash(ExportedCache::CacheKey(fileId, format));
    bool contains = m_cache.contains(hash);

    if (contains) {
        QString pathFrom = makeCachePath(hash);
        auto worker = new CopyWorker(pathFrom, pathExportedDoc);
        connect(worker, &CopyWorker::ready, [=] (bool status) {
            emit documentExported(pathExportedDoc, format, status);
        });
        m_threadPool.start(worker);
    }

    return contains;
}

void ExportedCache::insertExportedDoc(const QString &fileId, const QString &pathExportedDoc, const QString &format)
{
    auto hash = qHash(ExportedCache::CacheKey(fileId, format));
    if (m_cache.contains(hash))
        return;

    QString pathTo = makeCachePath(hash);
    auto worker = new CopyWorker(pathExportedDoc, pathTo);
    connect(worker, &CopyWorker::ready, [=] (bool status) {
            if (status)
                writeInList(fileId, format);
        });
    m_threadPool.start(worker, -1);
}
