/*

Copyright 2012-2014 Adam Reichold

This file is part of qpdfview.

qpdfview 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.

qpdfview 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 qpdfview.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "tileitem.h"

#include <QIODevice>
#include <QPainter>

#include "settings.h"
#include "pageitem.h"

namespace qpdfview
{

namespace
{

inline int cacheCost(const QPixmap& pixmap)
{
    return qMax(1, pixmap.width() * pixmap.height() * pixmap.depth() / 8 / 1024);
}

} // anonymous

Settings* TileItem::s_settings = 0;

QCache< TileItem::CacheKey, TileItem::CacheObject > TileItem::s_cache;

TileItem::TileItem(PageItem* page) : RenderTaskParent(),
    m_page(page),
    m_rect(),
    m_cropRect(),
    m_pixmapError(false),
    m_pixmap(),
    m_obsoletePixmap(),
    m_deleteAfterRender(false),
    m_renderTask(m_page->m_page, this)
{
    if(s_settings == 0)
    {
        s_settings = Settings::instance();
    }

    s_cache.setMaxCost(s_settings->pageItem().cacheSize());
}

TileItem::~TileItem()
{
    m_renderTask.cancel(true);
    m_renderTask.wait();
}

void TileItem::setCropRect(const QRectF& cropRect)
{
    if(!m_page->m_renderParam.trimMargins())
    {
        return;
    }

    if(m_cropRect.isNull() && !cropRect.isNull())
    {
        m_cropRect = cropRect;

        m_page->updateCropRect();
    }
}

void TileItem::dropCachedPixmaps(PageItem* page)
{
    foreach(const CacheKey& key, s_cache.keys())
    {
        if(key.first == page)
        {
            s_cache.remove(key);
        }
    }
}

bool TileItem::paint(QPainter* painter, QPointF topLeft)
{
    const QPixmap& pixmap = takePixmap();

    if(!pixmap.isNull())
    {
        // pixmap

        painter->drawPixmap(m_rect.topLeft() + topLeft, pixmap);

        return true;
    }
    else if(!m_obsoletePixmap.isNull())
    {
        // obsolete pixmap

        painter->drawPixmap(QRectF(m_rect).translated(topLeft), m_obsoletePixmap, QRectF());

        return false;
    }
    else
    {
        const qreal iconExtent = qMin(0.1 * m_rect.width(), 0.1 * m_rect.height());
        const QRect iconRect(topLeft.x() + m_rect.left() + 0.01 * m_rect.width(),
                             topLeft.y() + m_rect.top() + 0.01 * m_rect.height(),
                             iconExtent, iconExtent);

        if(!m_pixmapError)
        {
            // progress icon

            s_settings->pageItem().progressIcon().paint(painter, iconRect);

            return false;
        }
        else
        {
            // error icon

            s_settings->pageItem().errorIcon().paint(painter, iconRect);

            return true;
        }
    }
}

void TileItem::refresh(bool keepObsoletePixmaps)
{
    if(keepObsoletePixmaps && s_settings->pageItem().keepObsoletePixmaps())
    {
        if(const CacheObject* object = s_cache.object(cacheKey()))
        {
            m_obsoletePixmap = object->first;
        }
    }
    else
    {
        m_obsoletePixmap = QPixmap();
    }

    if(!keepObsoletePixmaps)
    {
        m_cropRect = QRectF();
    }

    m_renderTask.cancel(true);

    m_pixmapError = false;
    m_pixmap = QPixmap();
}

int TileItem::startRender(bool prefetch)
{
    m_page->startLoadInteractiveElements();

    if(m_pixmapError || m_renderTask.isRunning() || (prefetch && s_cache.contains(cacheKey())))
    {
        return 0;
    }

    m_renderTask.start(m_page->m_renderParam, m_rect, prefetch);

    return 1;
}

void TileItem::cancelRender()
{
    m_renderTask.cancel();

    m_pixmap = QPixmap();
    m_obsoletePixmap = QPixmap();
}

void TileItem::deleteAfterRender()
{
    if(!m_renderTask.isRunning())
    {
        m_renderTask.deleteParentLater();
    }
    else
    {
        m_renderTask.cancel(true);

        m_deleteAfterRender = true;
    }
}

void TileItem::on_finished(const RenderParam& renderParam,
                           const QRect& rect, bool prefetch,
                           const QImage& image, const QRectF& cropRect)
{
    if(m_page->m_renderParam != renderParam || m_rect != rect)
    {
        on_finishedOrCanceled();
        return;
    }

    m_obsoletePixmap = QPixmap();

    if(image.isNull())
    {
        m_pixmapError = true;

        on_finishedOrCanceled();
        return;
    }

    if(prefetch && !m_renderTask.wasCanceledForcibly())
    {
        const QPixmap pixmap = QPixmap::fromImage(image);

        s_cache.insert(cacheKey(), new CacheObject(pixmap, cropRect), cacheCost(pixmap));

        setCropRect(cropRect);
    }
    else if(!m_renderTask.wasCanceled())
    {
        m_pixmap = QPixmap::fromImage(image);

        setCropRect(cropRect);
    }

    on_finishedOrCanceled();
}

void TileItem::on_canceled()
{
    on_finishedOrCanceled();
}

void TileItem::on_finishedOrCanceled()
{
    if(m_deleteAfterRender)
    {
        m_renderTask.deleteParentLater();
    }
    else if(!m_page->useTiling() || m_page->m_exposedTileItems.contains(this))
    {
        m_page->update();
    }
}

inline TileItem::CacheKey TileItem::cacheKey() const
{
    QByteArray key;
    QDataStream stream(&key, QIODevice::WriteOnly);

    stream << m_page->m_renderParam << m_rect;

    return qMakePair(m_page, key);
}

QPixmap TileItem::takePixmap()
{
    const CacheKey key = cacheKey();

    if(const CacheObject* object = s_cache.object(key))
    {
        m_obsoletePixmap = QPixmap();

        setCropRect(object->second);
        return object->first;
    }

    QPixmap pixmap;

    if(!m_pixmap.isNull())
    {
        s_cache.insert(key, new CacheObject(m_pixmap, m_cropRect), cacheCost(m_pixmap));

        pixmap = m_pixmap;
    }
    else
    {
        startRender();
    }

    return pixmap;
}

} // qpdfview
