/****************************************************************************
**
** 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 HbServers 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 "hbsplashindicompositor_p.h"
#include "hbsplashgenerator_p.h"
#include "hbsplashdefs_p.h"
#include "hbmainwindow.h"
#include "hbmainwindow_p.h"
#include "hbsleepmodelistener_p.h"
#include "hbevent.h"
#include <QTimer>
#include <QApplication>

#ifdef Q_OS_SYMBIAN
#include <fbs.h>
_LIT_SECURITY_POLICY_PASS(KRdPolicy); // all pass
_LIT_SECURITY_POLICY_S0(KWrPolicy, hbsplash_server_uid3.iUid); // pass hbsplashgenerator only
#endif

// The indicator compositor renders a part of the mainwindow (the
// statusbar) into an image from time to time. This pixel data is then
// copied over to the splash screen bitmap whenever a client requests
// a screen (except if the screen is marked having a non-standard
// (hidden or transparent) statusbar).
//
// This ensures that there will be relatively up-to-date indicators in
// the splash screens.
//
// This module also publishes the global statusbar image that can be
// accessed in other processes via HbGlobalStatusBar.

HbSplashIndicatorCompositor::HbSplashIndicatorCompositor(HbSplashGenerator *gen)
    : mGenerator(gen), mSleeping(false), mSignalsConnected(false)
{
    // When the splash screens are regenerated the statusbar must be rendered
    // again too because the theme or the splashml files may have changed.
    connect(mGenerator, SIGNAL(regenerateStarted()), SLOT(renderStatusBar()), Qt::QueuedConnection);

    // Regenerate once using a singleshot timer (to have a little delay) but
    // then start listening to change notifications from the statusbar instead.
    mRenderTimer = new QTimer(this);
    mRenderTimer->setSingleShot(true);
    connect(mRenderTimer, SIGNAL(timeout()), SLOT(renderStatusBar()));
    mRenderTimer->start(5000); // 5 sec

    // There must be no activity while the device is sleeping so listen to sleep
    // mode events too.
    HbSleepModeListener::instance(); // just to make sure it is created
    QApplication::instance()->installEventFilter(this);

#ifdef Q_OS_SYMBIAN
    // The statusbar images will also be stored as CFbsBitmap and the
    // handles will be published in P&S properties.
    mStatusBarBitmapPrt = mStatusBarBitmapLsc = 0;
    RProperty::Define(hbsplash_server_uid3, HbSplashSbBitmapPrtKey,
                      RProperty::EInt, KRdPolicy, KWrPolicy);
    RProperty::Define(hbsplash_server_uid3, HbSplashSbBitmapLscKey,
                      RProperty::EInt, KRdPolicy, KWrPolicy);
    mStatusBarBitmapHandlePropPrt.Attach(hbsplash_server_uid3, HbSplashSbBitmapPrtKey);
    mStatusBarBitmapHandlePropLsc.Attach(hbsplash_server_uid3, HbSplashSbBitmapLscKey);
#endif
}

HbSplashIndicatorCompositor::~HbSplashIndicatorCompositor()
{
#ifdef Q_OS_SYMBIAN
    delete mStatusBarBitmapPrt;
    delete mStatusBarBitmapLsc;
    mStatusBarBitmapHandlePropPrt.Close();
    mStatusBarBitmapHandlePropLsc.Close();
#endif
}

void HbSplashIndicatorCompositor::release()
{
    delete this;
}

void HbSplashIndicatorCompositor::connectSignals()
{
    HbStatusBar *sb = HbMainWindowPrivate::d_ptr(mGenerator->ensureMainWindow())->mStatusBar;
    connect(sb, SIGNAL(contentChanged(HbStatusBar::ContentChangeFlags)),
            SLOT(handleStatusBarContentChange(HbStatusBar::ContentChangeFlags)));
    mSignalsConnected = true;
}

void HbSplashIndicatorCompositor::handleStatusBarContentChange(
    HbStatusBar::ContentChangeFlags changeType)
{
    // No need to rush when battery level changes while charging
    // because it is not the real level, just the animation.
    queueRender(changeType.testFlag(HbStatusBar::BatteryCharging));
}

void HbSplashIndicatorCompositor::queueRender(bool lazy)
{
    // Compress subsequent change notifications into one update.
    if (!mRenderTimer->isActive() && !mSleeping) {
        mRenderTimer->start(lazy ? 1500 : 100); // 1.5s if charging, 0.1s otherwise
    }
}

void HbSplashIndicatorCompositor::renderStatusBar()
{
    // Do nothing in sleep mode.
    if (mSleeping) {
        return;
    }
    // Try again later if a screen is just being generated. We share the same
    // mainwindow and our changes done here (orientation, statusbar visibility)
    // could possibly ruin the output.
    if (!mGenerator->lockMainWindow()) {
        mRenderTimer->start(1000); // 1 sec
        return;
    }
    try {
        if (!mSignalsConnected) {
            connectSignals();
            // The first rendering may be wrong due to the deferred
            // polish/layout handling.  So issue a regenerate request.
            queueRender();
        }
        HbMainWindow *mw = mGenerator->ensureMainWindow();
        HbSplashGenerator::setStatusBarElementsVisible(mw, true);
        mw->setOrientation(Qt::Vertical, false);
        doRender(mw, &mStatusBarImagePrt, &mStatusBarRectPrt);
        mw->setOrientation(Qt::Horizontal, false);
        doRender(mw, &mStatusBarImageLsc, &mStatusBarRectLsc);
#ifdef Q_OS_SYMBIAN
        publishAsBitmap(mStatusBarImagePrt, &mStatusBarBitmapPrt, &mStatusBarBitmapHandlePropPrt);
        publishAsBitmap(mStatusBarImageLsc, &mStatusBarBitmapLsc, &mStatusBarBitmapHandlePropLsc);
#endif
    } catch (const std::bad_alloc &) {
        qWarning("[hbsplashindicompositor] caught bad_alloc");
        mStatusBarImagePrt = mStatusBarImageLsc = QImage();
    }
    mGenerator->unlockMainWindow();
}

void HbSplashIndicatorCompositor::doRender(HbMainWindow *mw,
        QImage *statusBarImage,
        QRect *statusBarRect)
{
    *statusBarRect = mw->mapFromScene(HbMainWindowPrivate::d_ptr(mw)->mStatusBar->geometry())
                     .boundingRect().intersected(QRect(QPoint(0, 0), mw->size()));
    *statusBarImage = QImage(statusBarRect->size(), QImage::Format_ARGB32_Premultiplied);
    statusBarImage->fill(QColor(Qt::transparent).rgba());
    QPainter painter(statusBarImage);
    mw->render(&painter, statusBarImage->rect(), *statusBarRect);
}

void HbSplashIndicatorCompositor::composeToBitmap(void *bitmap,
        Qt::Orientation orientation,
        int splashExtraFlags)
{
#ifdef Q_OS_SYMBIAN
    if (!(splashExtraFlags & HbSplashNonStandardStatusBar)) {
        const QImage *srcImg = orientation == Qt::Horizontal ? &mStatusBarImageLsc
                               : &mStatusBarImagePrt;
        const QRect *sbRect = orientation == Qt::Horizontal ? &mStatusBarRectLsc
                              : &mStatusBarRectPrt;
        if (!srcImg->isNull()) {
            CFbsBitmap *bmp = static_cast<CFbsBitmap *>(bitmap);
            uchar *dst = reinterpret_cast<uchar *>(bmp->DataAddress());
            const int dstBpl = CFbsBitmap::ScanLineLength(bmp->SizeInPixels().iWidth,
                               bmp->DisplayMode());
            const uchar *src = srcImg->bits();
            const int srcBpl = srcImg->bytesPerLine();
            const int dstLineStartOffset = sbRect->left() * 4;
            const int y0 = sbRect->top();
            const int y1 = sbRect->bottom();
            for (int y = y0; y <= y1; ++y) {
                int dstOffset = y * dstBpl + dstLineStartOffset;
                int srcOffset = (y - y0) * srcBpl;
                qMemCopy(dst + dstOffset, src + srcOffset, srcBpl);
            }
        }
    }
#else
    Q_UNUSED(bitmap);
    Q_UNUSED(orientation);
    Q_UNUSED(splashExtraFlags);
#endif
}

bool HbSplashIndicatorCompositor::eventFilter(QObject *obj, QEvent *event)
{
    if (event->type() == HbEvent::SleepModeEnter && !mSleeping) {
        mSleeping = true;
    } else if (event->type() == HbEvent::SleepModeExit && mSleeping) {
        mSleeping = false;
        queueRender();
    }
    return QObject::eventFilter(obj, event);
}

#ifdef Q_OS_SYMBIAN
void HbSplashIndicatorCompositor::publishAsBitmap(const QImage &image,
                                                  CFbsBitmap **bitmap,
                                                  RProperty *prop)
{
    int w = image.width();
    int h = image.height();
    if (*bitmap) {
        TSize bitmapSize = (*bitmap)->SizeInPixels();
        if (bitmapSize.iWidth != w || bitmapSize.iHeight != h) {
            delete *bitmap;
            *bitmap = 0;
        }
    }
    if (!*bitmap) {
        *bitmap = new CFbsBitmap;
        if ((*bitmap)->Create(TSize(w, h), EColor16MAP) != KErrNone) {
            qWarning("[hbsplashindicompositor] bitmap Create() failed for size %dx%d", w, h);
            delete *bitmap;
            *bitmap = 0;
            return;
        }
    }
    uchar *dst = reinterpret_cast<uchar *>((*bitmap)->DataAddress());
    int dstBpl = CFbsBitmap::ScanLineLength(w, (*bitmap)->DisplayMode());
    const uchar *src = image.bits();
    int srcBpl = image.bytesPerLine();
    if (dstBpl == srcBpl) {
        qMemCopy(dst, src, h * srcBpl);
        prop->Set((*bitmap)->Handle());
    } else {
        qWarning("[hbsplashindicompositor] bpl mismatch (%d - %d)", srcBpl, dstBpl);
    }
}
#endif // Q_OS_SYMBIAN
