/****************************************************************************
**
** Copyright (C) 2009 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 "qcontacttrackerbackend_p.h"

#include <QtTracker/Tracker>
#include <QtTracker/ontologies/nco.h>
#include <QtTracker/ontologies/nie.h>
#include <QtTracker/ontologies/nao.h>
#include <QRegExp>
#include <QDir>
#include <QFile>
#include <QSet>
#include <QList>

#include "qtcontacts.h"

#include "trackerchangelistener.h"
#include "qtrackercontactsaverequest.h"
#include <qtrackerrelationshipfetchrequest.h>
#include <qtrackerrelationshipsaverequest.h>
#include <qtrackercontactidfetchrequest.h>


QContactManagerEngine* ContactTrackerFactory::engine(const QMap<QString, QString>& parameters, QContactManager::Error* error)
{
    Q_UNUSED(error);
    QString version = QLatin1String(VERSION_INFO);
    return new QContactTrackerEngine(managerName(), version.toInt(), parameters);
}

QString ContactTrackerFactory::managerName() const
{
    return QString("tracker");
}
Q_EXPORT_PLUGIN2(qtcontacts_tracker, ContactTrackerFactory);

QContactTrackerEngine::QContactTrackerEngine(const QString& engineName, int engineVersion, const QMap<QString, QString>& parameters)
    : d(new QContactTrackerEngineData),
    contactArchiveFile("removed"),
    contactArchiveDir(QDir::homePath()+"/.contacts")
{
    Q_UNUSED(parameters);
    d->m_engineName = engineName;
    d->m_engineVersion = engineVersion;
    connectToSignals();
}

QContactTrackerEngine::QContactTrackerEngine(const QMap<QString, QString>& parameters)
    : d(new QContactTrackerEngineData),
    contactArchiveFile("removed"),
    contactArchiveDir(QDir::homePath()+"/.contacts")
{
    Q_UNUSED(parameters);
    connectToSignals();
}

QContactTrackerEngine::QContactTrackerEngine(const QContactTrackerEngine& other)
    : QContactManagerEngine(), d(other.d)
{
    Q_UNUSED(other);
    connectToSignals();
}

void QContactTrackerEngine::connectToSignals()
{
    TrackerChangeListener *listener = new TrackerChangeListener(this, this);
    connect(listener, SIGNAL(contactsAdded(const QList<QContactLocalId>&)), SIGNAL(contactsAdded(const QList<QContactLocalId>&)));
    connect(listener, SIGNAL(contactsChanged(const QList<QContactLocalId>&)), SIGNAL(contactsChanged(const QList<QContactLocalId>&)));
    connect(listener, SIGNAL(contactsRemoved(const QList<QContactLocalId>&)), SIGNAL(contactsRemoved(const QList<QContactLocalId>&)));
}

QContactTrackerEngine& QContactTrackerEngine::operator=(const QContactTrackerEngine& other)
{
    d = other.d;
    return *this;
}

QContactTrackerEngine::~QContactTrackerEngine()
{
}

QContactManagerEngine* QContactTrackerEngine::clone()
{
    // this engine allows sharing - so we increase the reference count.
    d->m_refCount.ref();
    return this;
}

void QContactTrackerEngine::deref()
{
    if (!d->m_refCount.deref())
        delete this;
}

QList<QContactLocalId> QContactTrackerEngine::contactIds(const QList<QContactSortOrder>& sortOrders, QContactManager::Error* error) const
{
    return contactIds(QContactFilter(), sortOrders, error);
}

QList<QContactLocalId> QContactTrackerEngine::contactIds(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, QContactManager::Error* error) const
{
    QContactLocalIdFetchRequest request;
    request.setFilter(filter);
    request.setSorting(sortOrders);

    QContactTrackerEngine engine(*this);
    engine.startRequest(&request);
    // 10 seconds should be enough
    engine.waitForRequestFinished(&request, 10000);
    if(!request.isFinished()) {
        *error = QContactManager::UnspecifiedError;
    }
    else {
        // leave the code for now while not all other code is fixed
        *error = request.error();
    }
    return request.ids();
}

QList<QContact> QContactTrackerEngine::contacts(const QContactFilter& filter, const QList<QContactSortOrder>& sortOrders, const QContactFetchHint& fetchHint, QContactManager::Error* error) const
{
    // the rest of the code is for internal usage, unit tests etc.
    QContactFetchRequest request;
    request.setFetchHint(fetchHint);
    request.setFilter(filter);
    request.setSorting(sortOrders);

    QContactTrackerEngine engine(*this);
    engine.startRequest(&request);
    // 10 seconds should be enough
    engine.waitForRequestFinished(&request, 10000);

    if( !request.isFinished()) {
        *error = QContactManager::UnspecifiedError;
    }
    else {
        // leave the code for now while not all other code is fixed
        *error = request.error();
    }
    return request.contacts();
}

QContact QContactTrackerEngine::contact(const QContactLocalId& contactId, const QContactFetchHint& fetchHint, QContactManager::Error* error) const
{
    // plan to keep this warning for a while - as message to customers using the API
    qWarning() << "QContactManager::contact()" << "api is blocking on dbus roundtrip while accessing tracker. Please, consider using asynchronous API QContactFetchRequest and not fetching contacts by id \n"
            "- reading 100 ids and 100 contact by ids is ~100 times slower then reading 100 contacts at once with QContactFetchRequest.";
    return contact_impl(contactId, fetchHint, error);
}

QContactLocalId QContactTrackerEngine::selfContactId(QContactManager::Error* error) const
{
    *error = QContactManager::NoError;
    return QContactLocalId(0xFFFFFFFF);
}

// used in tests, removed warning while decided if to provide sync api. Until then customers are advised to use async
QContact QContactTrackerEngine::contact_impl(const QContactLocalId& contactId, const QContactFetchHint& fetchHint, QContactManager::Error* error ) const
{
    QContactLocalIdFilter idlist;
    QList<QContactLocalId> ids; ids << contactId;
    idlist.setIds(ids);
    QContactFetchRequest request;
    QStringList definitionNames = fetchHint.detailDefinitionsHint();
    if (fetchHint.detailDefinitionsHint().isEmpty())
    {
        definitionNames << QContactAvatar::DefinitionName
                << QContactBirthday::DefinitionName
                << QContactAddress::DefinitionName
                << QContactEmailAddress::DefinitionName
                << QContactDisplayLabel::DefinitionName
                << QContactGender::DefinitionName
                << QContactAnniversary::DefinitionName
                << QContactName::DefinitionName
                << QContactOnlineAccount::DefinitionName
                << QContactOrganization::DefinitionName
                << QContactPhoneNumber::DefinitionName
                << QContactOnlineAccount::DefinitionName
                << QContactUrl::DefinitionName;
    }

    QContactFetchHint modifiedHint;
    modifiedHint.setDetailDefinitionsHint(definitionNames);
    request.setFetchHint(modifiedHint);
    request.setFilter(idlist);

    QContactTrackerEngine engine(*this);
    engine.startRequest(&request);
    // 10 seconds should be enough
    engine.waitForRequestFinished(&request, 10000);

    if( !request.isFinished()) {
        *error = QContactManager::UnspecifiedError;
        return QContact();
    }
    else if(request.contacts().size() == 0)
    {
        *error = QContactManager::DoesNotExistError;
        return QContact();
    }
    else {
        // leave the code for now while not all other code is fixed
        *error = request.error();
        return request.contacts()[0];
    }

}

bool QContactTrackerEngine::waitForRequestFinished(QContactAbstractRequest* req, int msecs)
{
    Q_ASSERT(req);
    if(!req->isActive())
    {
        return req->isFinished(); // might be already finished
    }
    QTime t;
    t.start();
    while(t.elapsed() < msecs || msecs == 0) // 0 for infinite
    {
        usleep(10000);
        QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
        if(req->isFinished())
            return true;
    }
    qDebug() << Q_FUNC_INFO <<"Status Finished" << req->isFinished();
    return req->isFinished();

}

bool QContactTrackerEngine::saveContact( QContact* contact, QContactManager::Error* error)
{
    // Signal emitted from TrackerChangeListener
    QContactSaveRequest request;
    QList<QContact> contacts(QList<QContact>()<<*contact);
    request.setContacts(contacts);
    QContactTrackerEngine engine(*this);
    engine.startRequest(&request);
    // 10 seconds should be enough
    engine.waitForRequestFinished(&request, 10000);
    *error = request.error();
    Q_ASSERT(request.contacts().size() == 1);
    *contact = request.contacts()[0];

    if( request.isFinished() && *error == QContactManager::NoError)
        return true;
    else
        return false;
}

bool QContactTrackerEngine::removeContact(const QContactLocalId& contactId, QContactManager::Error* error)
{
    *error = QContactManager::NoError;

    // TODO: Do with LiveNodes when they support strict querying.
    RDFVariable RDFContact = RDFVariable::fromType<nco::PersonContact>();
    RDFContact.property<nco::contactUID>() = LiteralValue(QString::number(contactId));
    RDFSelect query;

    query.addColumn("contact_uri", RDFContact);
    LiveNodes ncoContacts = ::tracker()->modelQuery(query);
    if(ncoContacts->rowCount() == 0) {
        *error = QContactManager::DoesNotExistError;
        return false;
    }


    Live< nco::PersonContact> ncoContact = ncoContacts->liveNode(0);
    LiveNodes contactMediums = ncoContact->getHasContactMediums();
    foreach(Live<nco::ContactMedium> media, contactMediums) {
        media->remove();
    }
    ncoContact->remove();

    //Temporary workaround to get removed- signal
    QList<QContactLocalId> removed;
    removed << contactId;
    emit contactsRemoved(removed);

    return true;
}

bool QContactTrackerEngine::saveContacts(QList<QContact>* contacts, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
    // @todo: Handle errors per saved contact.
    Q_UNUSED(errorMap)

    *error = QContactManager::NoError;

    if(contacts == 0) {
        *error = QContactManager::BadArgumentError;
        return false;
    }

    // Signal emitted from TrackerChangeListener
    QContactSaveRequest request;
    QList<QContact> contactList;
    for (int i = 0; i < contacts->size(); ++i) {
        contactList.append(contacts->at(i));
    }
    request.setContacts(contactList);
    QContactTrackerEngine engine(*this);
    engine.startRequest(&request);
    /// @todo what should be the delay
    engine.waitForRequestFinished(&request, 1000*contacts->size());
    /// @todo what should we do in case request.isFinished() == false
    if (request.isFinished() == false) {
        qWarning() << "QContactTrackerEngine::saveContacts:" << "request not finished";
    }
    *error = request.error();
    for (int i = 0; i < contacts->size(); ++i) {
        (*contacts)[i] = request.contacts().at(i);
    }

    // Returns false if we have any errors - true if everything went ok.
    return (request.errorMap().isEmpty() && *error == QContactManager::NoError);
}

bool QContactTrackerEngine::removeContacts(const QList<QContactLocalId>& contactIds, QMap<int, QContactManager::Error>* errorMap, QContactManager::Error* error)
{
    // Cannot report errors - giving up.
    if(!errorMap) {
        *error = QContactManager::BadArgumentError;
        return false;
    }

    // let's clear the error hash so there is nothing old haunting us.
    errorMap->clear();

    for (int i = 0; i < contactIds.count(); i++) {
        QContactManager::Error lastError;
        removeContact(contactIds.at(i), &lastError);
        if (lastError != QContactManager::NoError) {
            errorMap->insert(i, lastError);
        }
    }

    // Returns true if no errors were encountered - false if there was errors.
    // emit signals removed as they are fired from QContactManager
    return (errorMap->isEmpty());
}

QMap<QString, QContactDetailDefinition> QContactTrackerEngine::detailDefinitions(const QString& contactType,
                                                                                 QContactManager::Error* error) const
{
    if (contactType != QContactType::TypeContact) {
        *error = QContactManager::InvalidContactTypeError;
        return QMap<QString, QContactDetailDefinition>();
    }

    // lazy initialisation of schema definitions.
    if (d->m_definitions.isEmpty()) {
        // none in the list?  get the schema definitions, and modify them to match our capabilities.
        d->m_definitions = QContactManagerEngine::schemaDefinitions().value(QContactType::TypeContact);

        // modification: name is unique
        QContactDetailDefinition nameDef = d->m_definitions.value(QContactName::DefinitionName);
        nameDef.setUnique(true);
        d->m_definitions.insert(QContactName::DefinitionName, nameDef);

        // modification: avatar is unique.
        QContactDetailDefinition avatarDef = d->m_definitions.value(QContactAvatar::DefinitionName);
        avatarDef.setUnique(true);
        d->m_definitions.insert(QContactAvatar::DefinitionName, avatarDef);

        // modification: url is unique.
        {
            const QContactDetailDefinition urlDef = d->m_definitions.value(
                    QContactUrl::DefinitionName);
            QContactDetailDefinition newUrlDef;

            QMap<QString, QContactDetailFieldDefinition> urlFieldNames = urlDef.fields();
            QMap<QString, QContactDetailFieldDefinition> &fields(urlFieldNames);
            QContactDetailFieldDefinition f;

            f.setDataType(QVariant::String);
            QVariantList subTypes;
            // removing social networking url
            subTypes << QString(QLatin1String(QContactUrl::SubTypeFavourite));
            subTypes << QString(QLatin1String(QContactUrl::SubTypeHomePage));
            f.setAllowableValues(subTypes);
            fields.insert(QContactUrl::FieldSubType, f);
            newUrlDef.setFields(fields);
            newUrlDef.setUnique(true);
            newUrlDef.setName(QContactUrl::DefinitionName);
            d->m_definitions.insert(QContactUrl::DefinitionName, newUrlDef);
        }

        // QContactOnlineAccount custom fields
        {
            const QContactDetailDefinition accDef = d->m_definitions.value(QContactOnlineAccount::DefinitionName);
            QContactDetailDefinition newAccountDefinition;

            QMap<QString, QContactDetailFieldDefinition> accountFieldName = accDef.fields();
            QMap<QString, QContactDetailFieldDefinition> &fields(accountFieldName);
            QContactDetailFieldDefinition f;

            f.setDataType(QVariant::String);
            fields.insert("Account", f);
            fields.insert("AccountPath", f);
            newAccountDefinition.setFields(fields);
            newAccountDefinition.setName(QContactOnlineAccount::DefinitionName);
            d->m_definitions.insert(QContactOnlineAccount::DefinitionName, newAccountDefinition);
        }
    }

    *error = QContactManager::NoError;
    return d->m_definitions;
}

/*!
 * \reimp
 */
bool QContactTrackerEngine::hasFeature(QContactManager::ManagerFeature feature, const QString& contactType) const
{
    if (!supportedContactTypes().contains(contactType)) {
        return false;
    }

    switch (feature) {
        case QContactManager::Groups:
        case QContactManager::ActionPreferences:
        case QContactManager::Relationships:
        case QContactManager::SelfContact:
            return true;
        case QContactManager::ArbitraryRelationshipTypes:
            return true;
        case QContactManager::MutableDefinitions:
            return true;
        case QContactManager::ChangeLogs:
            return true;
        default:
            return false;
    }
}


/*!
 * \reimp
 */
/*!
 * Definition identifiers which are natively (fast) filterable
 * on the default backend store managed by the manager from which the capabilities object was accessed
 */
bool QContactTrackerEngine::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::ChangeLogFilter:
        case QContactFilter::RelationshipFilter:

// not yet done
//        case QContactFilter::IntersectionFilter:
//        case QContactFilter::UnionFilter:
            return true;
        default:
            return false;
    }
}

/*!
 * Returns the list of data types supported by the Tracker engine
 */
QList<QVariant::Type> QContactTrackerEngine::supportedDataTypes() const
{
    // TODO: Check supported datatypes for Tracker backend.
    QList<QVariant::Type> st;
    st.append(QVariant::String);
    st.append(QVariant::Date);
    st.append(QVariant::DateTime);
    return st;
}

/*!
 * Returns the name of the Tracker engine
 */
QString QContactTrackerEngine::managerName() const
{
    return d->m_engineName;
}

/*!
 * Returns the manager version of this engine
 */
int QContactTrackerEngine::managerVersion() const
{
    return d->m_engineVersion;
}

RDFVariable QContactTrackerEngine::contactDetail2Rdf(const RDFVariable& rdfContact, const QString& definitionName,
                                                      const QString& fieldName) const
{
    if (definitionName == QContactName::DefinitionName) {
        if (fieldName == QContactName::FieldFirstName) {
            return rdfContact.property<nco::nameGiven>();
        }
        else if (fieldName == QContactName::FieldLastName) {
            return rdfContact.property<nco::nameFamily>();
        }
        else if (fieldName == QContactName::FieldMiddleName) {
            return rdfContact.property<nco::nameAdditional>();
        }
        else if (fieldName == QContactName::FieldPrefix) {
            return rdfContact.property<nco::nameHonorificPrefix>();
        }
        else if (fieldName == QContactName::FieldSuffix) {
            return rdfContact.property<nco::nameHonorificSuffix>();
        }
        else if (fieldName == QContactNickname::FieldNickname) {
            return rdfContact.property<nco::nickname>();
        }
    }
    return RDFVariable();
}

/*! \reimp */
void QContactTrackerEngine::requestDestroyed(QContactAbstractRequest* req)
{
    if( d->m_requests.contains(req) )
    {
        QTrackerContactAsyncRequest *request = d->m_requests.take(req);
        delete request;
    }
}

/*! \reimp */
bool QContactTrackerEngine::startRequest(QContactAbstractRequest* req)
{
    QTrackerContactAsyncRequest *request = 0;
    switch (req->type())
    {
        case QContactAbstractRequest::ContactLocalIdFetchRequest:
            request = new QTrackerContactIdFetchRequest(req, this);
            break;
        case QContactAbstractRequest::ContactFetchRequest:
            request = new QTrackerContactFetchRequest(req, this);
            break;
        case QContactAbstractRequest::ContactSaveRequest:
            request = new QTrackerContactSaveRequest(req, this);
            break;
        case QContactAbstractRequest::RelationshipFetchRequest:
            request = new QTrackerRelationshipFetchRequest(req, this);
            break;
        case QContactAbstractRequest::RelationshipSaveRequest:
            request = new QTrackerRelationshipSaveRequest(req, this);
            break;
        default:
            return false;
    }
    d->m_requests[req] = request;
    return true;
}

/*! \reimp */
QString QContactTrackerEngine::synthesizedDisplayLabel(const QContact& contact, QContactManager::Error* error) const
{
    QString label = QContactManagerEngine::synthesizedDisplayLabel(contact, error);
    if (label.isEmpty())
        label = contact.detail<QContactNickname>().nickname();
    //XXX TODO: FIXME - take the nickname from the presence field associated with the online account
    //if(label.isEmpty())
    //    label = contact.detail<QContactOnlineAccount>().nickname();

    qDebug() << Q_FUNC_INFO << label;
    return label;
}
