/****************************************************************************
**
** 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 "hbtimer_p.h"
#include <QApplication>

#if QT_VERSION < 0x040600
#include <QtAbstractAnimation>
#define QAbstractAnimation QtAbstractAnimation
#else
#include <QAbstractAnimation>
#endif

// A custom animation. We will use this instead of creating a timer ourselves, in
// order to use the animation fw's QUnifiedTimer under the hood.
class HbTimerAnim : public QAbstractAnimation
{
public:
    HbTimerAnim(HbTimerEntry *entry) : mEntry(entry), mDuration(entry->interval()) { }
    int duration() const {
        return mDuration;
    }
    void updateCurrentTime(int) { }
    void updateState(QAbstractAnimation::State newState, QAbstractAnimation::State oldState) {
        if (oldState != QAbstractAnimation::Stopped && newState == QAbstractAnimation::Stopped && mEntry) {
            mEntry->timerFired();
            // mEntry may be nulled out due to an unregister call from the callback.
            if (mEntry) {
                mEntry->mAnim = 0; // to prevent confusing unregisterEntry() and double deletion
                HbTimer::instance()->unregisterEntry(mEntry);
                if (mEntry->mDeleteWhenFinishedNormally) {
                    delete mEntry;
                }
            }
        }
    }
    HbTimerEntry *mEntry;
    int mDuration;
};

/*!
 * \class HbTimer
 *
 * \brief Provides a unified timer to avoid creating several separate singleshot
 * timers.
 *
 * \internal
 *
 * For example, the following code
 * \code
 *   QTimer *timer = new QTimer(this);
 *   timer->setSingleShot(true);
 *   connect(timer, SIGNAL(timeout()), SLOT(onTimeout()));
 *   timer->start(milliseconds);
 * \code
 * simply becomes
 * \code
 *   HbTimer::instance()->addTimeout(milliseconds, this, SLOT(onTimeout()));
 * \endcode
 *
 * Alternatively a class C can inherit from HbTimerEntry, pass the timer
 * interval to the HbTimerEntry constructor, and implement timerFired().  In
 * this case HbTimer::instance()->registerEntry(instance_of_class_C) can be
 * called to have timerFired() invoked on instance_of_class_C after the number
 * of milliseconds that has been passed to the HbTimerEntry constructor.
 */

HbTimer::HbTimer(QObject *parent)
    : QObject(parent)
{
}

HbTimer *HbTimer::instance()
{
    static HbTimer *inst = new HbTimer(qApp);
    return inst;
}

/*!
 * Requests a notification after the interval specified in \entry.
 *
 * The notification will mean calling timerFired() on \a entry.
 *
 * If \a deleteWhenFinishedNormally is true then the entry is
 * automatically deleted after invoking timerFired(). Note that the
 * entry is not deleted when unregisterEntry() is called explicitly.
 *
 * \sa addTimeout
 */
void HbTimer::registerEntry(HbTimerEntry *entry, bool deleteWhenFinishedNormally)
{
    if (!entryList.contains(entry)) {
        entryList << entry;
        entry->mDeleteWhenFinishedNormally = deleteWhenFinishedNormally;
        entry->mAnim = new HbTimerAnim(entry);
        entry->mAnim->start(QAbstractAnimation::DeleteWhenStopped);
    }
}

/*!
 * Cancels a pending timer notification. The HbTimerEntry instance (entry) is
 * not deleted.
 *
 * \return true if the entry was successfully unregistered, false if the entry
 * was not found amongst the registered ones.
 */
bool HbTimer::unregisterEntry(HbTimerEntry *entry)
{
    int idx = entryList.indexOf(entry);
    if (idx >= 0) {
        // Stop the animation but first set the anim's entry backptr to null
        // so no callback and deletion will occur in updateState().
        if (entry->mAnim) {
            entry->mAnim->mEntry = 0;
            entry->mAnim->stop(); // this will also result in deleting the HbTimerAnim instance
            entry->mAnim = 0;
        }
        entryList.removeAll(entry);
        return true;
    }
    return false;
}

/*!
 * Convenience wrapper for registerEntry().  The given \a slot on the
 * given QObject will be invoked after \a interval miliseconds.  The
 * returned HbTimerSignalEntry instance can be used to cancel the
 * timer; note that it will automatically be deleted after the slot
 * has been invoked, however it has to be deleted manually if
 * unregisterEntry() was called explicitly.
 *
 * \sa registerEntry
 */
HbTimerSignalEntry *HbTimer::addTimeout(int interval, QObject *receiver, const char *slot)
{
    HbTimerSignalEntry *entry = new HbTimerSignalEntry(interval);
    connect(entry, SIGNAL(timeout()), receiver, slot);
    registerEntry(entry, true);
    return entry;
}

/*!
 * Constructs a new timer entry instance. Interval is in milliseconds.
 */
HbTimerEntry::HbTimerEntry(int interval)
    : mInterval(interval), mAnim(0), mDeleteWhenFinishedNormally(false)
{
}

HbTimerEntry::~HbTimerEntry()
{
    if (mAnim) {
        delete mAnim;
    }
}

int HbTimerEntry::interval() const
{
    return mInterval;
}

HbTimerSignalEntry::HbTimerSignalEntry(int interval)
    : HbTimerEntry(interval)
{
}

void HbTimerSignalEntry::timerFired()
{
    emit timeout();
}
