/****************************************************************************
**
** 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 HbWidgets 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 "hbsmileyengine_p.h"
#include "hbsmileyengine_p_p.h"
#include <hbiconanimator.h>
#include <hbicon.h>
#include <QTextDocument>
#include <QFontMetricsF>
#include <QChar>
#include <QPainter>

HbSmileyTheme HbSmileyEnginePrivate::mDefaultTheme = HbSmileyTheme();

QSizeF HbIconTextObject::intrinsicSize(QTextDocument *doc, int posInDocument,
                                    const QTextFormat &format)
{
    Q_UNUSED(doc)
    Q_UNUSED(posInDocument)

    HbSmileyDataType smiley = qVariantValue<HbSmileyDataType>(format.property(HbSmileyData));
    HbIconAnimator *animator = smiley.animator;
    Q_ASSERT(animator);

    QFont f = static_cast<const QTextCharFormat&>(format).font();
    QFontMetricsF fm(f);

    // TODO: optimize this
    HbIcon icon = animator->icon();
    qreal iconHeight = fm.height() * smiley.engine->smileyScaleFactor();

    if(icon.height() != iconHeight) {
        icon.setHeight(iconHeight);
        animator->setIcon(icon);
    }
    // We are going to draw the smiley on the descent line not on the baseline
    // therefore the size needs to be adjusted accordingly.
    return (icon.size()-QSizeF(0,fm.descent()));
}

void HbIconTextObject::drawObject(QPainter *painter, const QRectF &rect,
                               QTextDocument *doc, int posInDocument,
                               const QTextFormat &format)
{
    Q_UNUSED(doc)
    Q_UNUSED(posInDocument)

    HbSmileyDataType smiley = qVariantValue<HbSmileyDataType>(format.property(HbSmileyData));

    QFont f = static_cast<const QTextCharFormat&>(format).font();
    QFontMetricsF fm(f);

    // Draw smiley on descent line
    QTransform matrix = painter->transform();
    painter->translate(0,fm.descent()-1);

    HbIconAnimator *animator = smiley.animator;
    animator->paint(painter,rect);
    painter->setTransform(matrix);
}

HbSmileyEnginePrivate::HbSmileyEnginePrivate()
    :mDocument(0),
     mEdited(true),
     mIconTextObject(new HbIconTextObject),
     mScaleFactor(1)
{
    q_ptr = 0;
}

HbSmileyEnginePrivate::~HbSmileyEnginePrivate()
{
    cleanUp();
    delete mIconTextObject;
}


void HbSmileyEnginePrivate::init()
{
    // Load default smiley theme
    if(mDefaultTheme.isNull()) {
        HbSmileyEnginePrivate::mDefaultTheme.load(":smileys/smileys_theme.sml");
        //
        //TODO: uncomment the lines below if animation definition file is provided for
        //      default smiley theme.
        //HbIconAnimationManager* m = HbIconAnimationManager::global();
        //m->addDefinitionFile(":smileys/smileys_animations.xml");
    }
    mSmileyTheme = HbSmileyEnginePrivate::mDefaultTheme;
}

void HbSmileyEnginePrivate::cleanUp()
{
    QList<QTextCursor*> cursors = mCursorToAnimator.keys();
    qDeleteAll(cursors);

    QList<HbIconAnimator*> animators = mAnimatorToCursors.keys();
    qDeleteAll(animators);

    mAnimatorToCursors.clear();
    mCursorToAnimator.clear();
    mSmileyAnimator.clear();
}

void HbSmileyEnginePrivate::setDocument(QTextDocument *doc)
{
    Q_Q(HbSmileyEngine);
    mDocument = doc;
    mDocument->documentLayout()->registerHandler(HbIconTextFormat, mIconTextObject);
    q->connect(mDocument, SIGNAL(contentsChange(int,int,int)), q, SLOT(_q_documentContentsChanged(int,int,int)));
    cleanUp();
}

void HbSmileyEnginePrivate::insertSmiley( QTextCursor cursor, const QString& name)
{
    Q_Q(HbSmileyEngine);
    QTextCharFormat hbiconFormat;
    QTextCursor *tmpCursor = new QTextCursor(cursor);
    hbiconFormat.setObjectType(HbIconTextFormat);

    HbSmileyDataType smiley;
    HbIconAnimator *animator = lookupAnimator(name);
    smiley.cursor = tmpCursor;
    smiley.animator = animator;
    smiley.engine = q;

    hbiconFormat.setProperty(HbIconTextObject::HbSmileyData, qVariantFromValue(smiley));

    mEdited = false;
    tmpCursor->insertText(QString(QChar::ObjectReplacementCharacter), hbiconFormat);
    mEdited = true;
    tmpCursor->setPosition(tmpCursor->position()-1);
    mAnimatorToCursors[animator] << tmpCursor;
    mCursorToAnimator[tmpCursor] = animator;
}

void HbSmileyEnginePrivate::insertSmileys( QTextCursor cursor, bool insertOne)
{
    QTextCursor initialCursor = cursor;
    initialCursor.beginEditBlock();

    const QRegExp &rx = mSmileyTheme.regExp();

    cursor = mDocument->find(rx, cursor);
    while ( !cursor.isNull()){
        insertSmiley(cursor, mSmileyTheme.smiley(cursor.selectedText()));
        if (insertOne) {
            break;
        }
        cursor = mDocument->find(rx, cursor);
    }
    initialCursor.endEditBlock();
}


HbIconAnimator* HbSmileyEnginePrivate::lookupAnimator(const QString& name)
{
    Q_Q(HbSmileyEngine);
    HbIconAnimator *animator = mSmileyAnimator.value(name);

    // Init icon if it doesn't already exits
    if (!animator) {
        HbIconAnimator *newAnimator = new HbIconAnimator();
        animator = newAnimator;
        HbIcon icon = HbIcon(name);
        icon.setFlags(HbIcon::NoAutoStartAnimation);
        animator->setIcon(icon);
        q->connect(animator,SIGNAL(animationProgressed()),q,SLOT(_q_animationProgressed()));
        animator->startAnimation();
        mSmileyAnimator[name] = animator;
    }

    return animator;
}


bool HbSmileyEnginePrivate::isCursorValid(QTextCursor* cursor) const
{
    bool ret = true;

    if (cursor) {
        if (mDocument->characterAt(cursor->position()) != QChar::ObjectReplacementCharacter) {
            ret = false;
         } else {
             QTextCursor tmpCursor(*cursor);
             tmpCursor.setPosition(tmpCursor.position()+1);
             QTextCharFormat format = tmpCursor.charFormat();

             HbSmileyDataType smiley = qVariantValue<HbSmileyDataType>(format.property(HbIconTextObject::HbSmileyData));
             if (cursor != smiley.cursor) {
                ret = false;
             }
         }
     } else {
        ret = false;
     }
    return ret;
}

void HbSmileyEnginePrivate::convertToText(QTextDocument *copyDoc) const
{
    QList<QTextCursor> cursors;
    // copy the cursors to copy document so that the positions get automatically updated
    foreach(QTextCursor *cursor, mCursorToAnimator.keys()) {
        if(isCursorValid(cursor)) {
            QTextCursor copyCursor(copyDoc);
            copyCursor.setPosition(cursor->position());
            cursors << copyCursor;
        }
    }

    foreach(QTextCursor copyCursor, cursors) {
        copyCursor.setPosition(copyCursor.position()+1, QTextCursor::KeepAnchor);
        QTextFormat format = copyCursor.charFormat();
        HbSmileyDataType smiley = qVariantValue<HbSmileyDataType>(format.property(HbIconTextObject::HbSmileyData));
        HbIconAnimator *animator = smiley.animator;
        Q_ASSERT(animator);

        QString pattern = mSmileyTheme.patterns(mSmileyAnimator.key(animator)).first();
        copyCursor.deleteChar();
        copyCursor.insertText(pattern);
    }
}

void HbSmileyEnginePrivate::_q_animationProgressed()
{
    Q_Q(HbSmileyEngine);
    HbIconAnimator *animator = qobject_cast<HbIconAnimator *>(q->sender());
    Q_ASSERT(animator);
    q->updateSmileys(mAnimatorToCursors.value(animator));
}

void HbSmileyEnginePrivate::_q_documentContentsChanged(int position, int charsRemoved, int charsAdded)
{
    Q_UNUSED(position);
    Q_UNUSED(charsRemoved);
    Q_UNUSED(charsAdded);

    if(charsRemoved > 0 && mEdited) {
        foreach (QTextCursor* cursor, mCursorToAnimator.keys()) {          
            if (!isCursorValid(cursor)) {
                HbIconAnimator * animator = mCursorToAnimator.value(cursor);
                mCursorToAnimator.remove(cursor);
                QList<QTextCursor*> & cursorList = mAnimatorToCursors[animator];
                cursorList.removeFirst();
                if (!cursorList.count()) {
                    mAnimatorToCursors.remove(animator);
                    mSmileyAnimator.remove(mSmileyAnimator.key(animator));
                    animator->deleteLater();
                }
                delete cursor;
            }
        }
    }
}


HbSmileyEngine::HbSmileyEngine(QObject *parent)
    :QObject(parent),
    d_ptr(new HbSmileyEnginePrivate)

{
    Q_D(HbSmileyEngine);
    d->q_ptr = this;
    d->init();
}

HbSmileyEngine::HbSmileyEngine(HbSmileyEnginePrivate &dd, QObject *parent)
    :QObject(parent),
    d_ptr(&dd)
{
    Q_D(HbSmileyEngine);
    d->q_ptr = this;
    d->init();
}

HbSmileyEngine::~HbSmileyEngine()
{
    delete d_ptr;
}

void HbSmileyEngine::setDocument(QTextDocument *doc)
{
    Q_D(HbSmileyEngine);
    d->setDocument(doc);
}

void HbSmileyEngine::setTheme(const HbSmileyTheme& theme)
{
    Q_D(HbSmileyEngine);
    d->mSmileyTheme = theme;
}

HbSmileyTheme HbSmileyEngine::theme() const
{
    Q_D(const HbSmileyEngine);
    return d->mSmileyTheme;
}

HbSmileyTheme HbSmileyEngine::defaultTheme() const
{
    return HbSmileyEnginePrivate::mDefaultTheme;
}


QString HbSmileyEngine::toPlainText() const
{
    Q_D(const HbSmileyEngine);
    QTextDocument *copyDoc = d->mDocument->clone();
    d->convertToText(copyDoc);
    QString plainText = copyDoc->toPlainText();
    delete copyDoc;
    return plainText;
}

QString HbSmileyEngine::toHtml() const
{
    Q_D(const HbSmileyEngine);
    QTextDocument *copyDoc = d->mDocument->clone();
    d->convertToText(copyDoc);
    QString htmlString = copyDoc->toHtml();
    return htmlString;
}

void HbSmileyEngine::setSmileyScaleFactor(qreal scaleFactor)
{
    Q_D(HbSmileyEngine);
    if (d->mScaleFactor != scaleFactor) {
        d->mScaleFactor = scaleFactor;
        updateSmileys(d->mCursorToAnimator.keys());
    }
}

qreal HbSmileyEngine::smileyScaleFactor() const
{
    Q_D(const HbSmileyEngine);
    return d->mScaleFactor;
}

void HbSmileyEngine::startAnimation()
{
    Q_D(HbSmileyEngine);
    foreach (HbIconAnimator *animator, d->mAnimatorToCursors.keys()) {
        animator->startAnimation();
    }
}

void HbSmileyEngine::stopAnimation()
{
    Q_D(HbSmileyEngine);
    foreach (HbIconAnimator *animator, d->mAnimatorToCursors.keys()) {
        animator->stopAnimation();
    }
}

void HbSmileyEngine::insertSmileys()
{
    Q_D(HbSmileyEngine);

    QTextCursor cursor(d->mDocument);
    d->insertSmileys(cursor);
}

void HbSmileyEngine::insertSmileys(const QTextCursor& cursor)
{
    Q_D(HbSmileyEngine);
    d->insertSmileys(cursor);
}

void HbSmileyEngine::insertSmiley(const QTextCursor& cursor)
{
    Q_D(HbSmileyEngine);
    d->insertSmileys(cursor,true);
}

void HbSmileyEngine::updateSmileys(QList<QTextCursor*> cursors)
{
    Q_D(HbSmileyEngine);

    if(cursors.isEmpty()) {
        return;
    }

    QTextCursor* initialCursor = cursors.first();
    initialCursor->beginEditBlock();

    foreach(QTextCursor *cursor, cursors) {
        QTextCursor tmpCursor(*cursor);
        tmpCursor.setPosition(tmpCursor.position()+1, QTextCursor::KeepAnchor);

        // update a bogus property, which will trigger a paint
        QTextCharFormat format;
        format.setProperty(HbIconTextObject::HbSmileyData+1, QString("Dummy"));
        d->mEdited = false;
        tmpCursor.mergeCharFormat(format);
        d->mEdited = true;
    }
    initialCursor->endEditBlock();
}

#include "moc_hbsmileyengine_p.cpp"
