/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, 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 qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QMutex>
#include <QDebug>
#include "qcontactmanager.h"
#include "qcontactchangeset.h"
#include "qtcontacts.h"

#define INITGUID
#include "qcontactwincebackend_p.h"

/*
 * This file is most of the engine plumbing - conversion to/from POOM is in
 * the contactconversions.cpp file.
 */

/*
 * TODOS
 *
 * - refactor (much needed)
 *  - single properties should have common code
 *  - error reporting when we run out of room
 * - use QScopedPointer
 * - pronunciation (needs schema update)
 * - any XXX comments
 * - store extra metadata
 *  - Voice subtype for phone, don't always assume
 * - Address formatting - PIMPR_HOME_ADDRESS seems to be read only
 */


QContactWinCEEngine::QContactWinCEEngine(ContactWinceFactory* factory, const QString& engineName, const QMap<QString, QString>& , QContactManager::Error* error)
    : d(new QContactWinCEEngineData)
{
    *error = QContactManager::NoError;
    
    buildHashForContactDetailToPoomPropId();
    d->m_engineName = engineName;
    d->m_factory = factory;
    if (SUCCEEDED(d->m_cominit.hr())) {
        if (SUCCEEDED(CoCreateInstance(CLSID_Application, NULL,
                                       CLSCTX_INPROC_SERVER, IID_IPOutlookApp2,
                                       reinterpret_cast<void **>(&d->m_app)))) {
            if(FAILED(d->m_app->Logon(NULL))) {
                qWarning() << "Failed to log on";
                d->m_app = 0;
            } else {
                if(SUCCEEDED(d->m_app->GetDefaultFolder(olFolderContacts, &d->m_folder))) {
                    if(SUCCEEDED(d->m_folder->get_Items(&d->m_collection))) {
                        // Register/retrieve our custom ids
                        LPCWSTR customIds[4] = { L"QTCONTACTS_PHONE_META", L"QTCONTACTS_EMAIL_META", L"QTCONTACTS_AVATAR_IMAGE_META",  L"QTCONTACTS_AVATAR_VIDEO_META" };
                        CEPROPID outIds[4];

                        if (SUCCEEDED(d->m_app->GetIDsFromNames(4, customIds, PIM_CREATE | CEVT_LPWSTR, outIds))) {
                            d->m_phonemeta = outIds[0];
                            d->m_emailmeta = outIds[1];
                            d->m_avatarImageMeta = outIds[2];
                            d->m_avatarVideoMeta = outIds[3];
                        }

                        // get an IPOLItems2 pointer for the collection, too
                        if (SUCCEEDED(d->m_collection->QueryInterface<IPOlItems2>(&d->m_items2))) {
                            d->m_ids = convertP2QIdList(d->m_collection);
                        }
                    } else {
                        qWarning() << "Failed to get items";
                        d->m_collection = 0;
                    }
                } else {
                    qWarning() << "Failed to get contacts folder";
                    d->m_folder = 0;
                }
            }
        }
    }
    d->m_requestWorker.start();
}

QContactWinCEEngine::QContactWinCEEngine(const QContactWinCEEngine& other)
    : QContactManagerEngine(), d(other.d)
{

}

QContactWinCEEngine& QContactWinCEEngine::operator=(const QContactWinCEEngine& other)
{
    // assign
    d = other.d;

    return *this;
}

QContactWinCEEngine::~QContactWinCEEngine()
{
    if (d->m_app) {
        d->m_app->Logoff();
    }
    d->m_factory->resetEngine();
}

QString QContactWinCEEngine::managerName() const
{
    return d->m_engineName;
}

QContact QContactWinCEEngine::contact(const QContactLocalId& contactId, const QtMobility::QContactFetchHint& hint, QContactManager::Error* error) const
{
    // TODO: implementation for definitionRestrictions!
    Q_UNUSED(hint);
    QContact ret;

    // id == 0 gives a bad argument error from POOM, so don't even try it
    if (contactId != 0) {
        // Fetch!
        SimpleComPointer<IItem> item = 0;
        HRESULT hr = d->m_app->GetItemFromOidEx(contactId, 0, &item);
        if (SUCCEEDED(hr)) {
            if (item) {
                *error = QContactManager::NoError;
                ret = convertToQContact(item);
            } else {
                *error = QContactManager::DoesNotExistError;
            }
        } else {
            if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
                *error = QContactManager::DoesNotExistError;
            } else {
                qWarning() << "Failed to retrieve contact:" << HRESULT_CODE(hr);
                *error = QContactManager::UnspecifiedError;
            }
        }
    } else {
        *error = QContactManager::DoesNotExistError;
    }
    return ret;
}

bool QContactWinCEEngine::saveContact(QContact* contact, QContactManager::Error* error)
{
    if (contact == 0) {
        *error = QContactManager::BadArgumentError;
        return false;
    }

    QContactChangeSet cs;

    // ensure that the contact's details conform to their definitions
    if (!validateContact(*contact, error)) {
        *error = QContactManager::InvalidDetailError;
        return false;
    }

    SimpleComPointer<IItem> icontact;
    bool wasOld = false;
    // Figure out if this is a new or old contact
    if (d->m_ids.contains(contact->localId())) {
        // update existing contact
        HRESULT hr = d->m_app->GetItemFromOidEx(contact->localId(), 0, &icontact);
        if (SUCCEEDED(hr)) {
            wasOld = true;
        } else {
            if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
                // Well, doesn't exist any more
                *error = QContactManager::DoesNotExistError;
                d->m_ids.removeAll(contact->localId());
            } else {
                qWarning() << "Didn't get old contact" << HRESULT_CODE(hr);
                *error = QContactManager::UnspecifiedError;
            }
        }
    } else if (contact->localId() == 0) {
        // new contact!
        SimpleComPointer<IDispatch> idisp = 0;
        HRESULT hr = d->m_collection->Add(&idisp);
        if (SUCCEEDED(hr)) {
            // now get an actual item out of it (was IContact, which is not IItem)
            hr = idisp->QueryInterface<IItem>(&icontact);

            if (SUCCEEDED(hr)) {
            } else {
                qWarning() << "Failed to query interface" << HRESULT_CODE(hr);
                *error = QContactManager::UnspecifiedError;
            }
        } else {
            qWarning() << "Failed to create contact: "<< HRESULT_CODE(hr);
            *error = QContactManager::OutOfMemoryError;
        }
    } else {
        // Saving a contact with a non zero id, but that doesn't exist
        *error = QContactManager::DoesNotExistError;
    }

    if (icontact) {
        // Convert our QContact to the Icontact (via setProps)
        if (convertFromQContact(*contact, icontact, *error)) {
            HRESULT hr = icontact->Save();
            if (SUCCEEDED(hr)) {
                // yay! we also need to set the new contact id
                long oid = 0;
                hr = icontact->get_Oid(&oid);
                if (SUCCEEDED(hr)) {
                    *error = QContactManager::NoError;
                    QContact c = this->contact((QContactLocalId)oid, QContactFetchHint(), error);
                    
                    if (*error == QContactManager::NoError) {
                        *contact = c;
                        if (wasOld) {
                            cs.insertChangedContact(contact->localId());
                        } else {
                            cs.insertAddedContact(contact->localId());
                            d->m_ids.append(contact->localId());
                        }
                    }

                    cs.emitSignals(this);
                    return true;
                }
                qWarning() << "Saved contact, but couldn't retrieve id again??" << HRESULT_CODE(hr);
                // Blargh.
                *error = QContactManager::UnspecifiedError;
            } else {
                qWarning() << "Failed to save contact" << HRESULT_CODE(hr);
            }
        } else {
            qWarning() << "Failed to convert contact";
        }
    }

    // error should have been set.
    return false;
}

bool QContactWinCEEngine::removeContact(const QContactLocalId& contactId, QContactManager::Error* error)
{
    // Fetch an IItem* for this
    if (contactId != 0) {
        SimpleComPointer<IItem> item ;
        QContactChangeSet cs;

        HRESULT hr = d->m_app->GetItemFromOidEx(contactId, 0, &item);
        if (SUCCEEDED(hr)) {
            hr = item->Delete();
            if (SUCCEEDED(hr)) {
                *error = QContactManager::NoError;
                d->m_ids.removeAll(contactId);
                cs.insertRemovedContact(contactId);
                cs.emitSignals(this);
                return true;
            }
            qWarning() << "Failed to delete:" << HRESULT_CODE(hr);
            *error = QContactManager::UnspecifiedError;
        } else {
            if (HRESULT_CODE(hr) == ERROR_NOT_FOUND) {
                *error = QContactManager::DoesNotExistError;
            } else {
                qWarning() << "Failed to retrieve item pointer in delete" << HRESULT_CODE(hr);
                *error = QContactManager::UnspecifiedError;
            }
        }
    } else {
        // Id 0 does not exist
        *error = QContactManager::DoesNotExistError;
    }
    return false;
}

QMap<QString, QContactDetailDefinition> QContactWinCEEngine::detailDefinitions(const QString& contactType, QContactManager::Error* error) const
{
    *error = QContactManager::NoError;
    QMap<QString, QMap<QString, QContactDetailDefinition> > defns = QContactManagerEngine::schemaDefinitions();

    // Remove the details we don't support
    defns[contactType].remove(QContactSyncTarget::DefinitionName);
    defns[contactType].remove(QContactGeoLocation::DefinitionName);
    defns[contactType].remove(QContactTimestamp::DefinitionName);
    defns[contactType].remove(QContactGuid::DefinitionName);
    defns[contactType].remove(QContactGender::DefinitionName); // ? Surprising

    QMap<QString, QContactDetailFieldDefinition> fields = defns[contactType][QContactAnniversary::DefinitionName].fields();
    // Simple anniversarys
    fields.remove(QContactAnniversary::FieldCalendarId);
    fields.remove(QContactAnniversary::FieldEvent);
    fields.remove(QContactAnniversary::FieldSubType);
    defns[contactType][QContactAnniversary::DefinitionName].setFields(fields);

    // No logo for organisation
    fields = defns[contactType][QContactOrganization::DefinitionName].fields();
    fields.remove(QContactOrganization::FieldLogoUrl);
    defns[contactType][QContactOrganization::DefinitionName].setFields(fields);

    // No subtypes for these details
    fields = defns[contactType][QContactUrl::DefinitionName].fields();
    fields.remove(QContactUrl::FieldSubType);
    defns[contactType][QContactUrl::DefinitionName].setFields(fields);

    // No contexts for these details
    fields =  defns[contactType][QContactAvatar::DefinitionName].fields();
    fields.remove(QContactDetail::FieldContext);
    defns[contactType][QContactAvatar::DefinitionName].setFields(fields);

    // Simple phone number types (non multiple)
    // defns[QContactPhoneNumber::DefinitionName].fields()[QContactPhoneNumber::FieldSubTypes].dataType = QVariant::String; // XXX doesn't work
    fields = defns[contactType][QContactPhoneNumber::DefinitionName].fields();
    fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeBulletinBoardSystem)));
    fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeLandline)));
    fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeMessagingCapable)));
    fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeModem)));
    fields[QContactPhoneNumber::FieldSubTypes].allowableValues().removeAll(QString(QLatin1String(QContactPhoneNumber::SubTypeVideo)));
    defns[contactType][QContactPhoneNumber::DefinitionName].setFields(fields);
    
    // XXX temporary definitions that we should support but don't yet.
    defns[contactType].remove(QContactOnlineAccount::DefinitionName);

    return defns[contactType];
}


/*! \reimp */
void QContactWinCEEngine::requestDestroyed(QContactAbstractRequest* req)
{
    d->m_requestWorker.removeRequest(req);
}

/*! \reimp */
bool QContactWinCEEngine::startRequest(QContactAbstractRequest* req)
{
    return d->m_requestWorker.addRequest(req);
}

/*! \reimp */
bool QContactWinCEEngine::cancelRequest(QContactAbstractRequest* req)
{
    return  d->m_requestWorker.cancelRequest(req);
}


/*! \reimp */
bool QContactWinCEEngine::waitForRequestFinished(QContactAbstractRequest* req, int msecs)
{
    return d->m_requestWorker.waitRequest(req, msecs) && req->isFinished();
}

/*! \reimp */
bool QContactWinCEEngine::hasFeature(QContactManager::ManagerFeature feature, const QString& contactType) const
{
    Q_UNUSED(contactType);
    // The Windows CE backend is an "isolated" backend
    if (feature == QContactManager::Anonymous)
        return true;

    // Windows CE backend does not support Mutable Definitions, Relationships or Action Preferences
    return false;
}

/* Synthesise the display label of a contact */
QString QContactWinCEEngine::synthesizedDisplayLabel(const QContact& contact, QContactManager::Error* error) const
{
    Q_UNUSED(error)
    // The POOM API (well, lack thereof) makes this a bit strange.
    // It's basically just "Last, First" or "Company", if "FileAs" is not set.
    QContactName name = contact.detail<QContactName>();
    QContactOrganization org = contact.detail<QContactOrganization>();

    // Basically we ignore any existing labels for this contact, since we're being
    // asked what the synthesized label would be

    // XXX For greatest accuracy we might be better off converting this contact to
    // a real item (but don't save it), and then retrieve it...
    if (!name.customLabel().isEmpty()) {
        return name.customLabel();
    }
    else if (!name.lastName().isEmpty()) {
        if (!name.firstName().isEmpty()) {
            return QString(QLatin1String("%1, %2")).arg(name.lastName()).arg(name.firstName());
        } else {
            // Just last
            return name.lastName();
        }
    } else if (!name.firstName().isEmpty()) {
        return name.firstName();
    } else if (!org.name().isEmpty()) {
        return org.name();
    } else {
        // XXX grargh.
        return QLatin1String("Unnamed");
    }
}



PROPID QContactWinCEEngine::metaAvatarImage() const
{
    return d->m_avatarImageMeta;
}

PROPID QContactWinCEEngine::metaAvatarVideo() const
{
    return d->m_avatarVideoMeta;
}

PROPID QContactWinCEEngine::metaEmail() const
{
    return d->m_emailmeta;
}

PROPID QContactWinCEEngine::metaPhone() const
{
    return d->m_phonemeta;
}

/*! \reimp */
bool QContactWinCEEngine::isFilterSupported(const QContactFilter& filter) const
{
    switch (filter.type()) {
        case QContactFilter::InvalidFilter:
        case QContactFilter::DefaultFilter:
        case QContactFilter::LocalIdFilter:
        case QContactFilter::ContactDetailFilter:
        case QContactFilter::ContactDetailRangeFilter:
        case QContactFilter::ActionFilter:
        case QContactFilter::IntersectionFilter:
        case QContactFilter::UnionFilter:
            return true;
    }
    return false;
}

/*! \reimp */
int QContactWinCEEngine::managerVersion() const
{
    return QTCONTACTS_VERSION;
}

/*! \reimp */
QList<QContact> QContactWinCEEngine::contacts(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, const QContactFetchHint& fetchHint, QContactManager::Error* error) const
{
    QList<QContactLocalId> ids = contactIds(filter, sortOrders, error);
    QList<QContact> cs;
    if (*error == QContactManager::NoError) {
        foreach (const QContactLocalId& id, ids) {
            cs << contact(id, fetchHint, error);
        }
    }
    return cs;
}

/*! \reimp */
bool QContactWinCEEngine::saveContacts(QList<QContact>* contacts, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
    bool ret = true;

    for (int j = 0; j < contacts->size(); j++) {
        if (!saveContact(&((*contacts)[j]), error)) {
            ret = false;
        }
        if (*error != QContactManager::NoError) {
            errorMap->insert(j, *error);
        }
    }
    return ret;
}

/*! \reimp */
bool QContactWinCEEngine::removeContacts(const QList<QContactLocalId>& contactIds, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
    bool ret = true;

    for (int j = 0; j < contactIds.size(); j++) {
        if (!removeContact(contactIds[j], error)) {
            ret = false;
        }

        if (*error != QContactManager::NoError) {
            errorMap->insert(j, *error);
        }
    }
    return ret;
}

/*!
 * Returns the list of data types supported by the WinCE engine
 */
QList<QVariant::Type> QContactWinCEEngine::supportedDataTypes() const
{
    QList<QVariant::Type> st;
    st.append(QVariant::String);
    st.append(QVariant::Int);
    st.append(QVariant::UInt);
    st.append(QVariant::Double);
    st.append(QVariant::Date);
    st.append(QVariant::DateTime);

    return st;
}

/* The default constructor of wince contact manager engine factory */
ContactWinceFactory::ContactWinceFactory()
:m_engine(0)
{
}

/* Factory lives here in the basement */
QContactManagerEngine* ContactWinceFactory::engine(const QMap<QString, QString>& parameters, QContactManager::Error* error)
{
    QMutexLocker locker(&m_mutex);
    if (!m_engine) {
        m_engine = new QContactWinCEEngine(this, managerName(), parameters, error);
    }
    return m_engine;
}

QString ContactWinceFactory::managerName() const
{
    return QString("wince");
}

 void ContactWinceFactory::resetEngine()
 {
     QMutexLocker locker(&m_mutex);
     m_engine = 0;
 }
Q_EXPORT_PLUGIN2(qtcontacts_wince, ContactWinceFactory);
