/****************************************************************************
**
** 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 HbPlugins 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 <QTextCharFormat>
#include <QTimer>
#include <hbinputsettingproxy.h>
#include <hbinputkeymapfactory.h>
#include <hbinputkeymap.h>
#include <hbinpututils.h>

#include "hbinputmodehandler.h"
#include "hbinputmodehandler_p.h"
#include "hbinputabstractbase.h"

HbInputModeHandlerPrivate::HbInputModeHandlerPrivate()
:mKeymap(0),
mInputMethod(0),
mTimer(0)
{
}

HbInputModeHandlerPrivate::~HbInputModeHandlerPrivate()
{
}

void HbInputModeHandlerPrivate::init()
{
    Q_Q(HbInputModeHandler);
    mTimer = new QTimer(q);
    q->connect(mTimer, SIGNAL(timeout()), q, SLOT(_q_timeout()));
    mTimer->setSingleShot(true);
}

// A virtual timeout function mode handlers should implement this slot.
void HbInputModeHandlerPrivate::_q_timeout()
{
}

QString HbInputModeHandlerPrivate::surroundingText()
{
    if (mInputMethod->focusObject()) {
        return mInputMethod->focusObject()->editorSurroundingText();
    }
    return QString();
}

int HbInputModeHandlerPrivate::cursorPosition()
{
    if (mInputMethod->focusObject()) {
       return mInputMethod->focusObject()->editorCursorPosition();
    }

    return -1;
}

bool HbInputModeHandlerPrivate::isPunctuationKey(const Qt::Key key, const HbKeyboardType keypad)
{
    // Currently, Punctuation key is present only for For ITUT Keypad (i.e. Key_1 is is the punctuation key)
    if ( (!HbInputUtils::isQwertyKeyboard(keypad)) && (Qt::Key_1 == key) ) {
        return true;
    }
    return false;
}

void HbInputModeHandlerPrivate::getAndFilterCharactersBoundToKey(QString &allowedChars, HbKeyboardType type, \
                                                                 int key, HbModifiers modifiers)
{
    allowedChars.clear();
    HbInputLanguage language = mInputMethod->inputState().language();
    
    if (!mKeymap) {
        mKeymap = HbKeymapFactory::instance()->keymap(language);
    }
    const HbMappedKey* mappedKey = mKeymap->keyForKeycode(type, key);
    if (!mappedKey) {
        return;
    }
    QString chars = mappedKey->characters(modifiers);
    // check whether current input language supports native digits. if yes, replace latin digits with native digits    
    for (int i = 0; i < chars.length(); i++) {
        if (chars.at(i) >= '0' && chars.at(i) <= '9') {
            chars = chars.replace(chars.at(i), HbInputUtils::findFirstNumberCharacterBoundToKey(mappedKey,
                language, HbInputUtils::inputDigitType(language)));
        }       
    }       
    // We need to see which of the characters in keyData are allowed to the editor.
    // this looks like expensive operation, need to find out a better way/place to do it.
    HbInputFocusObject *focusedObject = mInputMethod->focusObject();
    if(focusedObject) {
        focusedObject->filterStringWithEditorFilter(chars,allowedChars);
    } 
}


void HbInputModeHandlerPrivate::updateTextCase()
{
    HbInputState nextState = mInputMethod->inputState();
    HbTextCase nextCase = HbTextCaseNone;

    switch(nextState.textCase()) {
        case HbTextCaseLower:
            // In multiline editor, if enter char is input and then shift is pressed twice,
            // the case should be reverted to automatic.
            if (mInputMethod->automaticTextCaseNeeded()) {
                nextCase = HbTextCaseAutomatic;
            } else {
                nextCase = HbTextCaseUpper;
            }
            break;
        case HbTextCaseUpper:
        case HbTextCaseAutomatic:
            nextCase = HbTextCaseLower;
            break;
        default:
            break;
        }
        nextState.setTextCase(nextCase);
        mInputMethod->activateState(nextState);
}

bool HbInputModeHandlerPrivate::isEnterCharacter(QChar character)
{
    if (character == 0x21B2 || character == 0x21b3) {
        return true;
    }
    return false;
}
HbInputModeHandler::HbInputModeHandler(HbInputModeHandlerPrivate &dd, HbInputAbstractMethod* inputMethod)
    :d_ptr(&dd)
{
    Q_D(HbInputModeHandler);
    d->q_ptr = this;
    d->init();
    d->mInputMethod = inputMethod;
}

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

/*!
 Gateway for all the mode handlers.
*/
bool HbInputModeHandler::filterEvent(const QEvent * event)
{
    if (event) {
        if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
            const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
            return filterEvent(keyEvent);
        }
    }
    return false;
}

/*!
 This function handles the most common key events.
*/
bool HbInputModeHandler::filterEvent(const QKeyEvent * event)
{
    Q_UNUSED(event);
    return false;
}

/*!
 Action hanlder. Currently it handles only keymapping loading.
*/
bool HbInputModeHandler::actionHandler(HbInputModeAction action)
{
    Q_D(HbInputModeHandler);
    switch (action) {
    case HbInputModeActionInit:
        if (d->mKeymap) {
            break;
        }
    case HbInputModeActionPrimaryLanguageChanged: {
        HbInputLanguage primaryLanguage = HbInputSettingProxy::instance()->globalInputLanguage();
        d->mKeymap = HbKeymapFactory::instance()->keymap(primaryLanguage);
        if (!d->mKeymap) {
            qDebug(" HbInputModeHandler::actionHandler --> Failed to get keymapData!!!");
            return false;
        }
        }
        break;
    case HbInputModeActionDeleteAndCommit: {
        HbInputFocusObject *focusObject = 0;
        focusObject = d->mInputMethod->focusObject();

        if (focusObject && focusObject->editorCursorPosition()) {
            QString empty;
            QList<QInputMethodEvent::Attribute> list;
            QInputMethodEvent event(QString(), list);
            event.setCommitString(empty, -1, 1);
            sendAndUpdate(event);
        }
        break;
        }
    default :
        return false;
    };

    return true;
}

/*!
Call-back implementation to indicate that a character was selected from the SCT. With this, the character is committed to the
editor and editor is again made to focus.
*/
void HbInputModeHandler::sctCharacterSelected(QString aChar)
{
    commitAndUpdate(aChar);
}

void HbInputModeHandler::smileySelected(QString smiley)
{
    Q_D(HbInputModeHandler);
    HbInputFocusObject *focusObject = 0;
    focusObject = d->mInputMethod->focusObject();
    if (!focusObject) {
        qDebug("HbInputModeHandler::smileySelected no focusObject ... failed!!");
        return ;
    }
    QStringList patterns = focusObject->editorInterface().smileyTheme().patterns(smiley);
    foreach( const QString string, patterns) {
        QString filtered;
        focusObject->filterStringWithEditorFilter(string, filtered);
        if (filtered == string) {
            focusObject->commitSmiley(smiley);
            break;
        }
    }
    d->mInputMethod->updateState();
}

void HbInputModeHandler::cursorPositionChanged(int oldPos, int newPos)
{
    Q_D(HbInputModeHandler);
    d->mInputMethod->updateState();
    Q_UNUSED(oldPos);
    Q_UNUSED(newPos);
}

/*!
 this function commits the first number found in the key.
*/
void HbInputModeHandler::commitFirstMappedNumber(int key, HbKeyboardType type)
{
    Q_D(HbInputModeHandler);

    HbInputLanguage language = d->mInputMethod->inputState().language();
    // This is long key press number shortcut functionality.
    if (!d->mKeymap) {
        d->mKeymap = HbKeymapFactory::instance()->keymap(language);
    }
    bool isNumericEditor = d->mInputMethod->focusObject()->editorInterface().isNumericEditor();
    HbInputDigitType digitType = HbInputUtils::inputDigitType(language);
    if (isNumericEditor) {
        QLocale::Language systemLanguage = QLocale::system().language();         
        if (language.language() != systemLanguage) {
            digitType = HbDigitTypeLatin;
        }
    }   
    QChar numChr = HbInputUtils::findFirstNumberCharacterBoundToKey(
        d->mKeymap->keyForKeycode(type, key), language, digitType);
    // when a number is to be entered, it should commit 
    // the previous string and then append the number to the string
    if (numChr != 0) {
        commitAndAppendString(numChr);
    }
}

/*!
Gets the character at index in a key and incriments the index by 1. Returns the Zeroth character if
this function finds the index to be out of range and incriments index to 1.
*/
QChar HbInputModeHandler::getNthCharacterInKey(int &index, int key, HbKeyboardType type)
{
    Q_D(HbInputModeHandler);
    HbModifiers modifiers = 0;
    int textCase = d->mInputMethod->inputState().textCase();
    if ( (type != HbKeyboardSctPortrait && type != HbKeyboardSctUrl && type != HbKeyboardSctEmail) && 
        (textCase == HbTextCaseUpper || textCase == HbTextCaseAutomatic)) {
        modifiers |= HbModifierShiftPressed;
    }

    QString allowedChars;
    getAndFilterCharactersBoundToKey(allowedChars,type,key,modifiers);

    QChar character = 0;
    if (!allowedChars.isNull()) {
        if (index >= allowedChars.length() || index < 0) {
            index = 0;
        }
        character = allowedChars.at(index);
        index++;
        if (index >= allowedChars.length() || index < 0) {
            index = 0;
        }
    }
    return character;
}

/*!
    This function gets all the characters bound to a key and filters those character based on the editor.
*/
void HbInputModeHandler::getAndFilterCharactersBoundToKey(QString &allowedChars, HbKeyboardType type, \
                                                          int key, HbModifiers modifiers)
{
    Q_D(HbInputModeHandler);
    d->getAndFilterCharactersBoundToKey(allowedChars,type,key,modifiers);
}

/*!
    A virtual function, this should be called from plugin when ever there is mouse event.
*/
void HbInputModeHandler::mouseHandler(int x, QMouseEvent* mouseEvent)
{
    Q_UNUSED(mouseEvent);
    Q_D(HbInputModeHandler);
    if(d->cursorPosition() != x) {
        actionHandler(HbInputModeActionCommit);
    }
}

/*!
    A virtual function, default implementation first commits any inline text by calling actionHandler with
    HbInputModeActionCommit. Then commits the string.
*/
void HbInputModeHandler::commitAndAppendString(const QString& string)
{
    // first commit the pre-edit string if any.
    actionHandler(HbInputModeActionCommit);
    commitAndUpdate(string);
}

/*!
Ignores currently active pre-edit text if any and directly commits the passed string in to the editor. This
function also updates the inputmethod state.
*/
void HbInputModeHandler::commitAndUpdate(const QString& string, int replaceFrom, int replaceLength, bool isAsync)
{
    Q_D(HbInputModeHandler);
    HbInputFocusObject *focusObject = 0;
    focusObject = d->mInputMethod->focusObject();
    if (!focusObject) {
        qDebug("HbInputModeHandler::commitAndUpdate no focusObject ... failed!!");
        return ;
    }

    QString filtered;
    focusObject->filterStringWithEditorFilter(string, filtered);

    // now commit the string which is passed in.
    QList<QInputMethodEvent::Attribute> list;

    if(isAsync) {
        QInputMethodEvent *event = new QInputMethodEvent(QString(), list);
        event->setCommitString(filtered, replaceFrom, replaceLength);
        focusObject->postEvent(*event);
    } else {
        QInputMethodEvent event;
        event.setCommitString(filtered, replaceFrom, replaceLength);
        focusObject->sendEvent(event);
    }
    d->mInputMethod->updateState();
}

/*!
Sends the passed event to the focused object and upates the inputmethod state.
*/
void HbInputModeHandler::sendAndUpdate(QEvent& event)
{
    Q_D(HbInputModeHandler);
    HbInputFocusObject *focusObject = 0;
    focusObject = d->mInputMethod->focusObject();
    if (!focusObject) {
        qDebug("HbInputModeHandler::sendAndUpdate no focusObject ... failed!!");
        return;
    }

    focusObject->sendEvent(event);
    d->mInputMethod->updateState();
}

void HbInputModeHandler::setKeymap(const HbKeymap* keymap)
{
    Q_D(HbInputModeHandler);
    d->mKeymap = keymap;
}

/*!
Toggles prediction after doing a check if the editor allows it.
*/
void HbInputModeHandler::togglePrediction()
{
    Q_D(HbInputModeHandler);
    bool currentStatus = HbInputSettingProxy::instance()->predictiveInputStatusForActiveKeyboard();
    HbInputFocusObject* focusedObject = 0;
    focusedObject = d->mInputMethod->focusObject();
    bool isPredictionAllowed = focusedObject->editorInterface().isPredictionAllowed();
    if (currentStatus) {
        HbInputSettingProxy::instance()->setPredictiveInputStatusForActiveKeyboard(false);
    } else if (isPredictionAllowed) {
        HbInputSettingProxy::instance()->setPredictiveInputStatusForActiveKeyboard(true);
    }
}

#include "moc_hbinputmodehandler.cpp"
// EOF
