// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2012 Aurélien Gâteau <agateau@kde.org>

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 2
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA.

*/
// Self
#include "thumbnailgenerator.h"

// Local
#include "gwenview_lib_debug.h"
#include "gwenviewconfig.h"
#include "jpegcontent.h"

// KDCRAW
#ifdef KDCRAW_FOUND
#include <KDCRAW/KDcraw>
#endif

// KF

// Qt
#include <QBuffer>
#include <QCoreApplication>
#include <QImageReader>

namespace Gwenview
{
#undef ENABLE_LOG
#undef LOG
// #define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) // qCDebug(GWENVIEW_LIB_LOG) << x
#else
#define LOG(x) ;
#endif

//------------------------------------------------------------------------
//
// ThumbnailContext
//
//------------------------------------------------------------------------
bool ThumbnailContext::load(const QString &pixPath, int pixelSize)
{
    mImage = QImage();
    mNeedCaching = true;
    QImage originalImage;
    QSize originalSize;

    QByteArray formatHint = pixPath.section(QLatin1Char('.'), -1).toLocal8Bit().toLower();
    QImageReader reader(pixPath);

    JpegContent content;
    QByteArray format;
    QByteArray data;
    QBuffer buffer;
    int previewRatio = 1;

#ifdef KDCRAW_FOUND
    // raw images deserve special treatment
    if (KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(formatHint))) {
        // use KDCraw to extract the preview
        bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath);

        // We need QImage. Loading JpegContent from QImage - exif lost
        // Loading QImage from JpegContent - unimplemented, would go with loadFromData
        if (!ret) {
            // if the embedded preview loading failed, load half preview instead. That's slower...
            if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) {
                qCWarning(GWENVIEW_LIB_LOG) << "unable to get preview for " << pixPath.toUtf8().constData();
                return false;
            }
            previewRatio = 2;
        }

        // And we need JpegContent too because of EXIF (orientation!).
        if (!content.loadFromData(data)) {
            qCWarning(GWENVIEW_LIB_LOG) << "unable to load preview for " << pixPath.toUtf8().constData();
            return false;
        }

        buffer.setBuffer(&data);
        buffer.open(QIODevice::ReadOnly);
        reader.setDevice(&buffer);
        reader.setFormat("jpg");
    } else {
#else
    {
#endif
        if (QImageReader::imageFormat(pixPath) == QByteArray("raw")) {
            // make preview generation faster (same as KDcrawIface::KDcraw::loadHalfPreview)
            reader.setQuality(1);
            previewRatio = 2;
        }

        if (!reader.canRead()) {
            reader.setDecideFormatFromContent(true);
            // Set filename again, otherwise QImageReader won't restart from scratch
            reader.setFileName(pixPath);
        }

        if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) {
            content.load(pixPath);
        }
    }

    // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available.
    if (!content.rawData().isEmpty()) {
        QImage thumbnail = content.thumbnail();

        // If the user does not care about the generated thumbnails (by deleting them on exit), use ANY
        // embedded thumbnail, even if it's too small.
        if (!thumbnail.isNull() && (GwenviewConfig::lowResourceUsageMode() || qMax(thumbnail.width(), thumbnail.height()) >= pixelSize)) {
            mImage = std::move(thumbnail);
            mOriginalWidth = content.size().width();
            mOriginalHeight = content.size().height();
            return true;
        }
    }

    // Generate thumbnail from full image
    originalSize = reader.size();
    if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize) && qMax(originalSize.width(), originalSize.height()) >= pixelSize) {
        QSizeF scaledSize = originalSize;
        scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio);
        if (!scaledSize.isEmpty()) {
            reader.setScaledSize(scaledSize.toSize());
        }
    }

    // Rotate if necessary
    if (GwenviewConfig::applyExifOrientation()) {
        reader.setAutoTransform(true);
    }

    // format() is empty after QImageReader::read() is called
    format = reader.format();
    if (!reader.read(&originalImage)) {
        return false;
    }

    if (!originalSize.isValid()) {
        originalSize = originalImage.size();
    }
    mOriginalWidth = originalSize.width() * previewRatio;
    mOriginalHeight = originalSize.height() * previewRatio;

    if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) {
        mImage = originalImage;
        mNeedCaching = format != "png";
    } else {
        mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio);
    }

    if (reader.autoTransform() && (reader.transformation() & QImageIOHandler::TransformationRotate90)) {
        std::swap(mOriginalWidth, mOriginalHeight);
    }

    return true;
}

//------------------------------------------------------------------------
//
// ThumbnailGenerator
//
//------------------------------------------------------------------------
ThumbnailGenerator::ThumbnailGenerator()
    : mCancel(false)
{
    connect(
        qApp,
        &QCoreApplication::aboutToQuit,
        this,
        [=]() {
            cancel();
            wait();
        },
        Qt::DirectConnection);
    start();
}

void ThumbnailGenerator::load(const QString &originalUri,
                              time_t originalTime,
                              KIO::filesize_t originalFileSize,
                              const QString &originalMimeType,
                              const QString &pixPath,
                              const QString &thumbnailPath,
                              ThumbnailGroup::Enum group)
{
    QMutexLocker lock(&mMutex);
    Q_ASSERT(mPixPath.isNull());

    mOriginalUri = originalUri;
    mOriginalTime = originalTime;
    mOriginalFileSize = originalFileSize;
    mOriginalMimeType = originalMimeType;
    mPixPath = pixPath;
    mThumbnailPath = thumbnailPath;
    mThumbnailGroup = group;
    mCond.wakeOne();
}

QString ThumbnailGenerator::originalUri() const
{
    return mOriginalUri;
}

bool ThumbnailGenerator::isStopped()
{
    QMutexLocker lock(&mMutex);
    return mStopped;
}

time_t ThumbnailGenerator::originalTime() const
{
    return mOriginalTime;
}

KIO::filesize_t ThumbnailGenerator::originalFileSize() const
{
    return mOriginalFileSize;
}

QString ThumbnailGenerator::originalMimeType() const
{
    return mOriginalMimeType;
}

bool ThumbnailGenerator::testCancel()
{
    QMutexLocker lock(&mMutex);
    return mCancel;
}

void ThumbnailGenerator::cancel()
{
    QMutexLocker lock(&mMutex);
    mCancel = true;
    mCond.wakeOne();
}

void ThumbnailGenerator::run()
{
    while (!testCancel()) {
        QString pixPath;
        int pixelSize;
        {
            QMutexLocker lock(&mMutex);
            // empty mPixPath means nothing to do
            if (mPixPath.isNull()) {
                mCond.wait(&mMutex);
            }
        }
        if (testCancel()) {
            break;
        }
        {
            QMutexLocker lock(&mMutex);
            pixPath = mPixPath;
            pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup);
        }

        Q_ASSERT(!pixPath.isNull());
        LOG("Loading" << pixPath);
        ThumbnailContext context;
        bool ok = context.load(pixPath, pixelSize);

        {
            QMutexLocker lock(&mMutex);
            if (ok) {
                mImage = context.mImage;
                mOriginalWidth = context.mOriginalWidth;
                mOriginalHeight = context.mOriginalHeight;
                if (context.mNeedCaching && mThumbnailGroup <= ThumbnailGroup::XXLarge) {
                    cacheThumbnail();
                }
            } else {
                // avoid emitting the thumb from the previous successful run
                mImage = QImage();
                qCWarning(GWENVIEW_LIB_LOG) << "Could not generate thumbnail for file" << mOriginalUri;
            }
            mPixPath.clear(); // done, ready for next
        }
        if (testCancel()) {
            break;
        }
        {
            QSize size(mOriginalWidth, mOriginalHeight);
            LOG("emitting done signal, size=" << size);
            QMutexLocker lock(&mMutex);
            Q_EMIT done(mImage, size);
            LOG("Done");
        }
    }

    LOG("Ending thread");

    QMutexLocker lock(&mMutex);
    mStopped = true;
    deleteLater();
}

void ThumbnailGenerator::cacheThumbnail()
{
    mImage.setText(QStringLiteral("Thumb::URI"), mOriginalUri);
    mImage.setText(QStringLiteral("Thumb::MTime"), QString::number(mOriginalTime));
    mImage.setText(QStringLiteral("Thumb::Size"), QString::number(mOriginalFileSize));
    mImage.setText(QStringLiteral("Thumb::Mimetype"), mOriginalMimeType);
    mImage.setText(QStringLiteral("Thumb::Image::Width"), QString::number(mOriginalWidth));
    mImage.setText(QStringLiteral("Thumb::Image::Height"), QString::number(mOriginalHeight));
    mImage.setText(QStringLiteral("Software"), QStringLiteral("Gwenview"));

    Q_EMIT thumbnailReadyToBeCached(mThumbnailPath, mImage);
}

} // namespace

#include "moc_thumbnailgenerator.cpp"
