/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbCore module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbvgeffect_p.h"
#include "hbvgeffect_p_p.h"
#include <QPainter>
#include <QPaintEngine>
#include <QPixmapCache>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QSet>
#include <hbmainwindow.h>
#include <hbmainwindow_p.h>

/*!
 * \class HbVgEffect
 *
 * \brief Abstract base class for OpenVG effects.
 *
 * \internal
 * 
 * Brief guide for creating new effects:
 * <ul>
 *
 *    <li>Derive from HbVgEffect.
 *
 *    <li>Add getter/setter/property/signal for new effect parameters.
 *
 *    <li>In the setter make sure to call either updateEffectBoundingRect() or
 *    updateEffect(), depending on the parameter (some parameters affect the
 *    bounding rectangle, some not).
 *
 *    <li>Reimplement boundingRectFor().
 *
 *    <li>Implement performEffect(). This function will typically generate
 *    VGImages containing the filtered source image (or just the extra visuals
 *    that appear due to the effect) and draw them via the given painter's
 *    drawPixmap() (or vgDrawImage). Do not forget to draw the source image too,
 *    if needed.  The VGImage argument is passed in a variant to keep things
 *    building even without OpenVG headers/libraries.  Always use an appropriate
 *    ifdef for code that uses OpenVG types or functions.
 *
 * </ul>
 *
 * Note that these effects must not be installed using QGraphicsItem::setGraphicsEffect(),
 * use install() instead.
 *
 * \sa install()
 */

/*!
 * \fn void HbVgEffect::performEffect(QPainter *painter, const QPointF &offset,
 * const QVariant &vgImage, const QSize &vgImageSize)
 *
 * This function must be implemented in derived classes, providing the actual
 * implementation for the effect generation.
 *
 * \a painter is the painter to be used (it is guaranteed to be an OpenVG-based
 * one), \a offset is the target position, the variant \a vgImage contains a
 * VGImage handle for the source image, and \a vgImageSize is the size of that
 * image. The handle in \a vgImage is actually the underlying VGImage of the
 * pixmap stored in d->srcPixmap.
 *
 * Note that the size of this pixmap (and thus vgImageSize) already includes the
 * extra margins added by the effect's boundingRectFor, so drawing the entire
 * pixmap at any other position than \a offset will lead to painting outside the
 * item's effective bounding rectangle which may cause artifacts on the screen.
 *
 * \sa QGraphicsEffect::boundingRectFor()
 */

Q_GLOBAL_STATIC(QSet<QString>, cacheKeys)

HbVgEffectPrivate::HbVgEffectPrivate()
    : q_ptr(0),
      paramsChanged(true),
      cacheInvalidated(true),
      opacity(1),
      caching(false),
      rootEffect(0),
      sourceGraphicsItem(0),
      mainWindow(0),
      lastUsedRotation(0),
      lastRotationTransformAngle(0)
{
}

/*!
 * \internal
 */
HbVgEffectPrivate::~HbVgEffectPrivate()
{
}

#ifdef HB_EFFECTS_OPENVG
/*!
 * Makes sure that the given pixmap has the given \a size.  Returns
 * the underlying VGImage handle.  Typically used with dstPixmap and
 * tmpPixmap.
 *
 * \internal
 */
VGImage HbVgEffectPrivate::ensurePixmap(QPixmap *pixmap, const QSize& size)
{
    if (pixmap->size() != size)
        *pixmap = QPixmap(size);
    return qPixmapToVGImage(*pixmap);
}
#endif

/*!
 * Invalidates the cache but only if it has not been done already.
 *
 * \internal
 */
void HbVgEffectPrivate::ensureCacheInvalidated()
{
    if (!cacheInvalidated) {
#ifdef HBVG_TRACES
        Q_Q(HbVgEffect);
        qDebug("HbVgEffect [%x]: invalidating cache", (int) q);
#endif
        cacheInvalidated = true;
        notifyCacheInvalidated();
    }
}

/*!
 * Returns the rotation of the graphics view in degrees.
 *
 * For some effects it is important to know if the graphics view has been
 * transformed or not (e.g. a reflection effect may need to draw at a different
 * position when the graphics view is rotated).
 *
 * \internal
 */
qreal HbVgEffectPrivate::mainWindowRotation() const
{
    if (!mainWindow) {
        Q_Q(const HbVgEffect);
        const QGraphicsItem *srcItem = q->sourceItemForRoot();
        if (srcItem) {
            QGraphicsScene *scene = srcItem->scene();
            if (scene) {
                QList<QGraphicsView *> views = scene->views();
                foreach (QGraphicsView *view, views) {
                    mainWindow = qobject_cast<HbMainWindow *>(view);
                    if (mainWindow)
                        break;
                }
            }
        }
    }
    return mainWindow ? HbMainWindowPrivate::d_ptr(mainWindow)->rotation() : 0;
}

/*!
 * Returns a transformation that defines the graphics view's current rotation.
 *
 * \internal
 */
QTransform HbVgEffectPrivate::rotationTransform() const
{
    int rotation = (int) mainWindowRotation();
    if (rotation != lastRotationTransformAngle) {
        lastRotationTransformAngle = rotation;
        lastRotationTransform = QTransform().rotate(rotation);
    }
    return lastRotationTransform;
}
    
/*!
 * Maps the given translation offset to an offset that is based on the current
 * rotation of the graphics view. Effects are drawing directly, in device
 * coordinates, however they must take into account the rotation of the graphics
 * view too. Therefore any translation they use must be mapped using this
 * function. (NB. this does not apply to the offset argument of
 * performEffect()).
 *
 * For example if a dropshadow effect gets (x, y) as the offset of the shadow,
 * it must call this function instead of using the offset as it is, because the
 * correct device coordinates for the offset may be something different,
 * e.g. (y, -x) if the graphics view is rotated -90 degrees.
 *
 * \internal
 */
QPointF HbVgEffectPrivate::mapOffset(const QPointF &offset) const
{
    return rotationTransform().map(offset);
}

/*!
 * Returns a size that has the width and height swapped if the current rotation
 * of the graphics view implies that. Typically used with size-like values,
 * e.g. the radius for a blur effect.
 *
 * \internal
 */
QSizeF HbVgEffectPrivate::mapSize(const QSizeF &size) const
{
    QPointF p = mapOffset(QPointF(size.width(), size.height()));
    return QSizeF(qAbs(p.x()), qAbs(p.y()));
}

/*!
 * \internal
 *
 * Returns a cache key that is unique to this effect instance (but only for the
 * lifetime of the instance).
 */
inline QString cacheKey(const HbVgEffect *e)
{
    return QString::number((qptrdiff) e);
}

HbVgEffect::HbVgEffect(QObject *parent)
    : QGraphicsEffect(parent), d_ptr(new HbVgEffectPrivate)
{
    Q_D(HbVgEffect);
    d->q_ptr = this;
}

HbVgEffect::HbVgEffect(HbVgEffectPrivate &dd, QObject *parent)
    : QGraphicsEffect(parent), d_ptr(&dd)
{
    Q_D(HbVgEffect);
    d->q_ptr = this;
}

HbVgEffect::~HbVgEffect()
{
    // Get rid of the cached pixmap if it is still present.
    QString key = cacheKey(this);
    QPixmapCache::remove(key);
    cacheKeys()->remove(key);
    delete d_ptr;
}

/*!
 * Updates the source item (and thus the effect). Also sets the paramsChanged
 * and cacheInvalidated flags. This function should be called from the effect
 * parameter setters only.
 *
 * Note that this is not sufficient if the results of boundingRectFor() are also
 * changing due to an updated parameter value. In that case call
 * updateEffectBoundingRect() instead.
 *
 * \sa updateEffectBoundingRect()
 */
void HbVgEffect::updateEffect()
{
    Q_D(HbVgEffect);
    d->paramsChanged = true;
    d->ensureCacheInvalidated();
    if (d->rootEffect) {
        d->rootEffect->updateEffect();
    } else {
        update();
    }
}

/*!
 * Notifies that the bounding rectangle for the effect (i.e. the return value of
 * boundingRectFor()) is possibly different than before. Also updates the source
 * item (and thus the effect) and sets the paramsChanged and cacheInvalidated
 * flags. This function should be called from the effect parameter setters only.
 *
 * When changing effect parameters that have no effect on the bounding
 * rectangle, use updateEffect() instead because that might be more efficient.
 *
 * \sa updateEffect()
 */
void HbVgEffect::updateEffectBoundingRect()
{
    Q_D(HbVgEffect);
    d->paramsChanged = true;
    d->ensureCacheInvalidated();
    if (d->rootEffect) {
        d->rootEffect->updateBoundingRect();
    } else {
        updateBoundingRect();
    }
}

/*!
 * Enables delegation of update requests to the given \a effect.
 *
 * This is needed when there are several effects in a chain and thus all update
 * requests should be routed from the individual elements to the container
 * effect.
 */
void HbVgEffect::setChainRoot(HbVgEffect *effect)
{
    Q_D(HbVgEffect);
    d->rootEffect = effect;
}

/*!
 * Returns the current root of the chain or null if this effect is not part of
 * any delegation chain. Do not assume that the returned effect is a
 * HbVgChainedEffect, it may also be something else (in theory), so use
 * qobject_cast if the return value needs to be casted to a more specific type.
 */
HbVgEffect *HbVgEffect::chainRoot() const
{
    Q_D(const HbVgEffect);
    return d->rootEffect;
}

/*!
 * Returns the current opacity parameter of the effect.
 *
 * \sa setOpacity()
 */
qreal HbVgEffect::opacity() const
{
    Q_D(const HbVgEffect);
    return d->opacity;
}

/*!
 * Sets the \a opacity parameter of the effect.
 * The value must fall between 0.0 and 1.0.
 *
 * \sa opacity()
 */
void HbVgEffect::setOpacity(qreal opacity)
{
    Q_D(HbVgEffect);
    if (d->opacity == opacity)
        return;
    d->opacity = opacity;
    updateEffect();
    emit opacityChanged(opacity);
}

/*!
 * \reimp
 *
 * Implementation of QGraphicsEffect's draw(). After setting up the environment
 * for direct OpenVG painting, the work is delegated to the performEffect()
 * implementation of the derived classes.
 */
void HbVgEffect::draw(QPainter *painter)
{
    // Just draw the source without effects if the painter's paint engine
    // is not using OpenVG.
    if (painter->paintEngine()->type() != QPaintEngine::OpenVG) {
        drawSource(painter);
        return;
    }

#ifdef HB_EFFECTS_OPENVG
    Q_D(HbVgEffect);
    // Render the source into a pixmap. Note that the effect's modified bounding
    // rectangle is already taken into account in the size of the pixmap created
    // here, so there is no need to waste time with boundingRectFor() calls
    // later in performEffect() when creating temporary buffers.
    QPoint offset;
    // Don't bother with optimizations based on sourceIsPixmap() because we will
    // rarely be used on QGraphicsPixmapItems anyway.
    d->srcPixmap = sourcePixmap(Qt::DeviceCoordinates, &offset);

    // Pull out the underlying VGImage from the pixmap.
    VGImage vgImage = qPixmapToVGImage(d->srcPixmap);
    // If something went wrong then just draw the source without effects.
    if (vgImage == VG_INVALID_HANDLE) {
        drawSource(painter);
        return;
    }

    // Save the world transformation and then reset it.
    d->worldTransform = painter->worldTransform();
    painter->setWorldTransform(QTransform());

    // Invalidate the cache if the rotation of the graphics view has changed.
    qreal rotation = d->mainWindowRotation();
    if (rotation != d->lastUsedRotation) {
        d->lastUsedRotation = rotation;
        d->ensureCacheInvalidated();
    }

    // Enter raw VG mode.
    painter->beginNativePainting();
    // Draw something.
    performEffect(painter, offset, QVariant::fromValue<VGImage>(vgImage), d->srcPixmap.size());
    // Clear the param/cache flags as all data should be up-to-date now.
    d->paramsChanged = d->cacheInvalidated = false;
    // Leave raw VG mode.
    painter->endNativePainting();

    // Restore the painter's previously set transformations.
    painter->setWorldTransform(d->worldTransform);

#else
    // OpenVG code disabled => effect is not shown (but have the source drawn still).
    drawSource(painter);
#endif
}

/*!
 *  \reimp
 */
void HbVgEffect::sourceChanged(ChangeFlags flags)
{
    // If the bounding rectangle or appearance of the source has changed then
    // set our flag to indicate that any cached visual data will have to be
    // dropped.
    if (flags & SourceBoundingRectChanged || flags & SourceInvalidated) {
        Q_D(HbVgEffect);
        d->ensureCacheInvalidated();
    }
}

/*!
 * Returns the current status of caching.
 *
 * \sa setCaching()
 */
bool HbVgEffect::caching() const
{
    Q_D(const HbVgEffect);
    return d->caching;
}

/*!
 * Enables or disables caching of the output of the effects.
 *
 * By default caching is disabled.
 *
 * Note that caching may not cause any increase in performance if the size or
 * apperance of the source item is changing rapidly, it is only useful for items
 * that are static enough.
 *
 * Enabling this setting may have no effect because some subclasses may not be
 * able to support it, it is treated as a hint only.
 *
 * \sa caching()
 */
void HbVgEffect::setCaching(bool caching)
{
    Q_D(HbVgEffect);
    if (d->caching == caching)
        return;
    d->caching = caching;
    emit cachingChanged(caching);
}

/*!
 * Returns the cached pixmap that is associated with this effect instance. Will
 * return a null pixmap if caching is disabled or there was no tryCache() call
 * for this instance before or the cache has been invalidated (e.g. because the
 * source item's appearance has changed).
 *
 * \sa tryCache()
 */
QPixmap HbVgEffect::cached(const QSize &size) const
{
    Q_D(const HbVgEffect);
    if (d->caching) {
        QString key = cacheKey(this);
        if (d->cacheInvalidated) {
            QPixmapCache::remove(key);
            cacheKeys()->remove(key);
        } else {
            QPixmap cachedPm;
            // Check if we have a pixmap in the cache, then check the size
            // too. The size check is important because the effects will
            // typically render their source using device coordinates which
            // causes a clipping to the device viewport, therefore the cached
            // pixmap for an item that was/is clipped should not be used.
            if (QPixmapCache::find(key, &cachedPm)
                && (size.isNull() || cachedPm.size() == size))
            {
#ifdef HBVG_TRACES
                qDebug("HbVgEffect [%x]: cache hit", (int) this);
#endif
                return cachedPm;
            }
        }
    }
    return QPixmap();
}

/*!
 * Inserts/replaces the given pixmap into/in the cache if caching is enabled.
 * The pixmap will be associated with this effect instance and can be retrieved
 * later by calling cached().
 *
 * Note that the OpenVG effects rely on "out-of-sync" pixmaps, i.e. pixmaps
 * where the underlying QImage and VGImage are not synchronized. Therefore the
 * QPixmapCache must be cleared whenever there is a risk that some other entity
 * (Qt) would destroy the underlying VGImages. This is currently handled in
 * HbForegroundWatcher and HbOogmWatcher by calling releaseCachedResources().
 *
 * \sa cached()
 */
void HbVgEffect::tryCache(const QPixmap &pm)
{
    Q_D(HbVgEffect);
    if (d->caching) {
        QString key = cacheKey(this);
        QPixmapCache::insert(key, pm);
        cacheKeys()->insert(key);
    }
}

/*!
 * Drops all cached data used by any effect in the application. Other pixmaps
 * (not related to effects) are left intact in the global pixmap cache.
 */
void HbVgEffect::releaseCachedResources()
{
    QSet<QString> *keys = cacheKeys();
    foreach (const QString &key, *keys) {
        QPixmapCache::remove(key);
    }
    keys->clear();
}

/*!
 * Returns the source graphics item for this effect or null if it is not
 * available for some reason. Also works for chained effects (i.e. it uses the
 * root effect if needed).
 *
 * NOTE: If the effect has not been attached to a graphics item using install()
 * then this function returns null (even if setGraphicsEffect() was used). So
 * use install().
 */
const QGraphicsItem *HbVgEffect::sourceItemForRoot() const
{
    Q_D(const HbVgEffect);
    const HbVgEffectPrivate *p = d->rootEffect ? HbVgEffectPrivate::d_ptr(d->rootEffect) : d;
    return p->sourceGraphicsItem;
}

/*!
 * Returns the bounding rectangle of the source graphics item. Also works for
 * chained effects (it will use the root effect if needed).
 */
QRectF HbVgEffect::sourceBoundingRectForRoot() const
{
    Q_D(const HbVgEffect);
    return d->rootEffect ? d->rootEffect->sourceBoundingRect() : sourceBoundingRect();
}

/*!
 * Installs the effect on a given graphics item. This is merely a wrapper to
 * QGraphicsItem::setGraphicsEffect(). To uninstall the effect use
 * setGraphicsEffect in the usual way (i.e. pass null). This function will do
 * nothing if \a item is null.
 *
 * Note that installing effects derived from HbVgEffect using
 * QGraphicsItem::setGraphicsEffect() will not work and will lead to undefined
 * behaviour. Use this function instead. You have been warned.
 */
void HbVgEffect::install(QGraphicsItem *item)
{
    if (item) {
        Q_D(HbVgEffect);
        d->sourceGraphicsItem = item;
        item->setGraphicsEffect(this);
    }
}
