/****************************************************************************
**
** 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 <QtTest/QtTest>
#include <QObject>

#include <qtcontacts.h>

#ifdef SYMBIANSIM_BACKEND_USE_ETEL_TESTSERVER
#include <etelmm_etel_test_server.h>
#else
#include <etelmm.h>
#endif
#include <mmtsy_names.h>

QTM_USE_NAMESPACE

#ifndef QTRY_COMPARE
#define QTRY_COMPARE(__expr, __expected) \
    do { \
        const int __step = 50; \
        const int __timeout = 5000; \
        if ((__expr) != (__expected)) { \
            QTest::qWait(0); \
        } \
        for (int __i = 0; __i < __timeout && ((__expr) != (__expected)); __i+=__step) { \
            QTest::qWait(__step); \
        } \
        QCOMPARE(__expr, __expected); \
    } while(0)
#endif

//TESTED_CLASS=
//TESTED_FILES=

/*!
*/
class tst_SimCM : public QObject
{
Q_OBJECT

public:
    tst_SimCM();
    virtual ~tst_SimCM();

public slots:
    void init();
    void cleanup();
    void initTestCase();
    void cleanupTestCase();

private slots:
    /* Test cases that need data */
    void initManager_data();
    void initManager();
    void hasFeature_data();
    void hasFeature();
    void supportedContactTypes_data();
    void supportedContactTypes();
    void detailDefinitions_data();
    void detailDefinitions();
    void addContact_data();
    void addContact();
    void fetchContacts_data();
    void fetchContacts();
    void updateContactDetail_data();
    void updateContactDetail();
    void batchOperations_data();
    void batchOperations();

    /* Test cases that take no data */
    void signalEmission();
    void sdnContacts();

private:
    void initManager(QString simStore);
    void getEtelStoreInfoL(const TDesC &phonebook, TDes8 &infoPckg) const;
    bool isContactSupported(QContact contact);
    void parseDetails(QContact &contact, QStringList details, QList<QContactDetail> &parsedDetails);
    void compareDetails(QContact contact, QList<QContactDetail> expectedDetails);
    QContact createContact(QString name, QString number);

private:
    QContactManager* m_cm;
#ifdef SYMBIANSIM_BACKEND_PHONEBOOKINFOV1
    RMobilePhoneBookStore::TMobilePhoneBookInfoV1 m_etelStoreInfo;
    RMobilePhoneBookStore::TMobilePhoneBookInfoV1Pckg m_etelStoreInfoPckg;
#else
    RMobilePhoneBookStore::TMobilePhoneBookInfoV5 m_etelStoreInfo;
    RMobilePhoneBookStore::TMobilePhoneBookInfoV5Pckg m_etelStoreInfoPckg;
#endif
};

tst_SimCM::tst_SimCM() :
    m_cm(0),
    m_etelStoreInfoPckg(m_etelStoreInfo)
{
}

tst_SimCM::~tst_SimCM()
{
}

void tst_SimCM::init()
{
    // remove all contacts
    QList<QContactLocalId> ids = m_cm->contactIds();
    m_cm->removeContacts(ids, 0);   
}

void tst_SimCM::cleanup()
{
    // remove all contacts
    QList<QContactLocalId> ids = m_cm->contactIds();
    m_cm->removeContacts(ids, 0);   
}

void tst_SimCM::initTestCase()
{
    initManager(QString());

    // TODO: how about other stores?
    TRAPD(err, getEtelStoreInfoL(KETelIccAdnPhoneBook, m_etelStoreInfoPckg));
    QCOMPARE(err, KErrNone);
}

void tst_SimCM::cleanupTestCase()
{
    delete m_cm;
    m_cm = 0;
}

void tst_SimCM::initManager_data()
{
    QTest::addColumn<QString>("simStore"); // empty (defaults to ADN), "ADN", "SDN" or "FDN"

    QString es;

    QTest::newRow("Empty store string (defaults to ADN store)") << es;
    QTest::newRow("Initialize SDN store") << "SDN";
    QTest::newRow("Initialize FDN store") << "FDN";
    QTest::newRow("Initialize ADN store") << "ADN";
}

void tst_SimCM::initManager()
{
    delete m_cm;
    m_cm = 0;

    QString uri("qtcontacts:symbiansim");

    // Set the sim store if available (simbackend defaults to ADN if not available)
    QFETCH(QString, simStore);
    if(!simStore.isEmpty()) {
        uri.append(":store=");
        uri.append(simStore);
    }

    m_cm = QContactManager::fromUri(uri);
    QVERIFY(m_cm);

    // NotSupportedError is allowed (the sim card may not support FDN and SDN stores)
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    } else {
        QCOMPARE(m_cm->error(), QContactManager::NoError);
    }
}

void tst_SimCM::initManager(QString simStore)
{
    delete m_cm;
    m_cm = 0;

    QString uri("qtcontacts:symbiansim");

    // Set the sim store if available (simbackend defaults to ADN if not available)
    if(!simStore.isEmpty()) {
        uri.append(":store=");
        uri.append(simStore);
    }

    m_cm = QContactManager::fromUri(uri);
}

void tst_SimCM::hasFeature_data()
{
    QTest::addColumn<QString>("simStore");      // empty (defaults to ADN), "ADN", "SDN" or "FDN"
    QTest::addColumn<int>("managerFeature");              // one of QContactManager::ManagerFeature
    QTest::addColumn<bool>("expectedResult");   // true = has feature, false = does not have feature
    QString es;

    QTest::newRow("ADN store (default)") << es << (int) QContactManager::Groups << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::ActionPreferences << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::MutableDefinitions << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::Relationships << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::ArbitraryRelationshipTypes << false;
    // TODO: self contact may be supported on ADN? (so called "own number store")
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::SelfContact << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::Anonymous << false;
    QTest::newRow("ADN store (default)") << es << (int) QContactManager::ChangeLogs << false;

    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::Groups << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::ActionPreferences << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::MutableDefinitions << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::Relationships << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::ArbitraryRelationshipTypes << false;
    // TODO: self contact may be supported on ADN? (so called "own number store")
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::SelfContact << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::Anonymous << false;
    QTest::newRow("ADN store") << "ADN" << (int) QContactManager::ChangeLogs << false;

    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::Groups << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::ActionPreferences << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::MutableDefinitions << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::Relationships << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::ArbitraryRelationshipTypes << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::SelfContact << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::Anonymous << false;
    QTest::newRow("SDN store") << "SDN" << (int) QContactManager::ChangeLogs << false;

    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::Groups << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::ActionPreferences << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::MutableDefinitions << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::Relationships << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::ArbitraryRelationshipTypes << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::SelfContact << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::Anonymous << false;
    QTest::newRow("FDN store") << "FDN" << (int) QContactManager::ChangeLogs << false;
}

void tst_SimCM::hasFeature()
{
    // initialize
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }
    QFETCH(bool, expectedResult);
    QFETCH(int, managerFeature);

    // verify
    QCOMPARE(m_cm->hasFeature((QContactManager::ManagerFeature) managerFeature), expectedResult);
    QCOMPARE(m_cm->hasFeature((QContactManager::ManagerFeature) managerFeature, QContactType::TypeContact), expectedResult);
    QCOMPARE(m_cm->hasFeature((QContactManager::ManagerFeature) managerFeature, QContactType::TypeGroup), expectedResult);
}

void tst_SimCM::supportedContactTypes_data()
{
    QTest::addColumn<QString>("simStore");
    QTest::newRow("custom label") << "ADN";
    QTest::newRow("custom label") << "SDN";
    QTest::newRow("custom label") << "FDN";
}

void tst_SimCM::supportedContactTypes()
{
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }

    QStringList types = m_cm->supportedContactTypes();
    QCOMPARE(m_cm->error(), QContactManager::NoError);
    QVERIFY(types.count() > 0);
    // Contacts should always be supported
    QVERIFY(types.contains(QContactType::TypeContact));
    // Groups should not be supported
    QVERIFY(!types.contains(QContactType::TypeGroup));
}

void tst_SimCM::detailDefinitions_data()
{
    QTest::addColumn<QString>("simStore");
    QTest::newRow("custom label") << "ADN";
    QTest::newRow("custom label") << "SDN";
    QTest::newRow("custom label") << "FDN";
}

void tst_SimCM::detailDefinitions()
{
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }

    QMap<QString, QContactDetailDefinition> detailDefinitions = m_cm->detailDefinitions();
    QCOMPARE(m_cm->error(), QContactManager::NoError);

    QVERIFY(detailDefinitions.count() >= 2);
    QVERIFY(detailDefinitions.count() <= 7);

    // check that at least definitions for name and phone number exist
    QVERIFY(detailDefinitions.contains(QContactName::DefinitionName));
    QVERIFY(detailDefinitions.contains(QContactPhoneNumber::DefinitionName));
    QVERIFY(detailDefinitions.contains(QContactDisplayLabel::DefinitionName));
    QVERIFY(detailDefinitions.contains(QContactType::DefinitionName));
    QVERIFY(detailDefinitions.contains(QContactSyncTarget::DefinitionName));

    // Dynamic definitions (that depend on SIM card type)
    if(detailDefinitions.count() == 7) {
        QVERIFY(detailDefinitions.contains(QContactNickname::DefinitionName));
        QVERIFY(detailDefinitions.contains(QContactEmailAddress::DefinitionName));
    }
}

void tst_SimCM::addContact_data()
{
    QTest::addColumn<QString>("simStore"); // empty (defaults to ADN), "ADN", "SDN" or "FDN"
    QTest::addColumn<int>("expectedResult"); // 1 = pass, 0 = fail, -1 = depends on the SIM card
    QTest::addColumn<QString>("expectedDisplayLabel");
    QTest::addColumn<QStringList>("details"); // format is <detail definition name>:<field name>:<value>
    QString unnamedLabel("Unnamed");
    QString es;
    QString tooLongText("James Hunt the 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890th");

    // TODO: what name field to use for a sim contact name?
    // Note: With the current implementation the value must not contain a ':' character
    QTest::newRow("ADN custom label")
        << QString("ADN")
        << 1 // expected to pass
        << "James"
        << (QStringList()
            << "Name:CustomLabel:James");

    QTest::newRow("ADN (explicitly defined store) custom label")
        << QString("ADN")
        << 1 // expected to pass
        << "James"
        << (QStringList()
            << "Name:CustomLabel:James");

    QTest::newRow("ADN custom label 2")
        << QString("ADN")
        << 1 // expected to pass
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt");

    QTest::newRow("ADN 2 custom labels")
        << QString("ADN")
        << 0 // expected to fail. Custom label is unique.
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James"
            << "Name:CustomLabel:James Hunt");

    QTest::newRow("ADN too long custom label")
        << QString("ADN")
        << 1 // expected to pass. Note: too long display label is truncated
        << tooLongText
        << (QStringList()
            << (QString("Name:CustomLabel:").append(tooLongText)));

    QTest::newRow("ADN custom label and nick name")
        << QString("ADN")
        << -1 // Depends on SIM card support (some cards support second name)
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "Nickname:Nickname:Hunt the Shunt");

    QTest::newRow("ADN custom label and too long nick name")
        << QString("ADN")
        << -1 // Depends on SIM card support (some cards support second name)
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << (QString("Nickname:Nickname:").append(tooLongText)));

    QTest::newRow("ADN phone number")
        << QString("ADN")
        << 1
        << unnamedLabel
        << (QStringList()
            << "PhoneNumber:PhoneNumber:+44752222222");

    QTest::newRow("ADN custom label and phone number")
        << QString("ADN")
        << 1
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:+44752222222");

    QTest::newRow("ADN custom label and funny (but legal) phone number")
        << QString("ADN")
        << 1
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:+0123456789*#p");

    QTest::newRow("ADN custom label and illegal phone number 1")
        << QString("ADN")
        << 0 // illegal characters in the phone number, should fail
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:+44(75)2222222");

    QTest::newRow("ADN custom label and illegal phone number 2")
        << QString("ADN")
        << 0 // illegal characters in the phone number, should fail
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:asdfqwer");

    QTest::newRow("ADN custom label and illegal phone number 3")
        << QString("ADN")
        << 0 // illegal characters in the phone number, should fail
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:1234567a");

    QTest::newRow("ADN custom label and illegal phone number 4")
        << QString("ADN")
        << 0 // illegal characters in the phone number, should fail
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:&1234567");

    QTest::newRow("ADN custom label and too long phone number")
        << QString("ADN")
        << 0 // long enough phone number to fail on any SIM card
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");

    QTest::newRow("ADN custom label and multiple phone numbers")
        << QString("ADN")
        << -1 // some SIM cards support multiple phone numbers
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:+44752222222"
            << "PhoneNumber:PhoneNumber:+44751111111");

    QTest::newRow("ADN custom label and multiple phone numbers, one phone number too long")
        << QString("ADN")
        << 0 // Long enough additional phone number to fail on any SIM card
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "PhoneNumber:PhoneNumber:+44752222222"
            << "PhoneNumber:PhoneNumber:1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");

    QTest::newRow("ADN custom label and email")
        << QString("ADN")
        << -1 // some SIM cards support e-mail address
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "EmailAddress:EmailAddress:james@mclaren.com");

    QTest::newRow("ADN custom label and multiple emails")
        << QString("ADN")
        << 0 // some SIM cards support multiple e-mail addresses, but not this many
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "EmailAddress:EmailAddress:james@march.com"
            << "EmailAddress:EmailAddress:james@hesketh.com"
            << "EmailAddress:EmailAddress:james@mclaren.com"
            << "EmailAddress:EmailAddress:james.hunt@bbc.co.uk");

    QTest::newRow("ADN custom label and too long email")
        << QString("ADN")
        << 0 // long enough e-mail to fail on any SIM card
        << "James Hunt"
        << (QStringList()
            << "Name:CustomLabel:James Hunt"
            << "EmailAddress:EmailAddress:james.hunt.the12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890th@mclaren.com");

    QTest::newRow("ADN non-supported field")
        << QString("ADN")
        << 0 // expected to fail
        << es
        << (QStringList()
            << "Name:IllegalNameDetailFieldName:James");

    QTest::newRow("ADN non-supported detail")
        << QString("ADN")
        << 0 // expected to fail
        << es
        << (QStringList()
            << "NotSupportedDetailDefinitionName:IllegalFieldName:FieldValue");

    QTest::newRow("ADN empty, non-supported detail")
        << QString("ADN")
        << 0 // expected to fail, since no valid details provided
        << es
        << (QStringList()
            << "NotSupportedDetailDefinitionName:IllegalFieldName:");

    QTest::newRow("SDN custom label")
        << "SDN"
        << 0 // It is not possible to save an SDN contact
        << "James"
        << (QStringList()
            << "Name:CustomLabel:James");

    // Note: Executing FDN test cases has a pre-condition that the user must
    // have been entered the PIN2 code successfully. On pre-10.1 platforms this
    // can be done by opening S60 platform Phonebook and making some
    // modifications that require PIN2 code; for example activate and
    // de-activate FDN feature.
    QTest::newRow("FDN custom label and phone number")
        << "FDN"
        << 1
        << "James"
        << (QStringList()
            << "Name:CustomLabel:James"
            << "PhoneNumber:PhoneNumber:+44752222222");
}

/*
 * Tests if different contacts can be saved to SIM card.
 * Steps:
 * 1. Parse contact details from test parameters
 * 2. Determine the expected result
 * 3.1 (if expected to pass) Save contact, verify result and remove contact
 * 3.2 (if expected to fail) Check that saving a contact fails
 */
void tst_SimCM::addContact()
{
    // Make debugging easier by getting the test case name
    QString tescaseName = QTest::currentDataTag();

    // init
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }
    QFETCH(int, expectedResult);
    QFETCH(QString, expectedDisplayLabel);
    QFETCH(QStringList, details);

    QContact contact;
    QList<QContactDetail> expectedDetails;

    // 1. Parse details and add them to the contact
    parseDetails(contact, details, expectedDetails);

    // 2. Determine the expected result
    if (expectedResult == -1) {
        // Unknown expected result, so we need to check what details the SIM
        // card supports
        if (isContactSupported(contact)) {
            expectedResult = 1; 
        } else {
            expectedResult = 0;
        }
    }

    // Get the contact count for verification purposes
    QList<QContactLocalId> idsBefore = m_cm->contactIds();
    QVERIFY(m_cm->error() == QContactManager::NoError || m_cm->error() == QContactManager::DoesNotExistError);

    // 3.1 (if expected to pass) Save contact, verify result and remove contact
    if (expectedResult)
    {
        // verify contact can be saved
        QVERIFY(m_cm->saveContact(&contact));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
        QList<QContactLocalId> idsAfterSave = m_cm->contactIds();
        QCOMPARE(idsAfterSave.count(), idsBefore.count() + 1);

        // verify contact id
        QVERIFY(contact.id() != QContactId());

        // verify that the details were saved as expected
        compareDetails(contact, expectedDetails);

        // verify display label, allow truncating to the max text length
        QCOMPARE(contact.displayLabel(), expectedDisplayLabel.left(m_etelStoreInfo.iMaxTextLength));

        // TODO: verify that no extra details were added?
        //?QCOMPARE(contact.details().count(), detailsUnderTest.count() + 2);

        // verify contact removal
        QVERIFY(m_cm->removeContact(contact.localId()));
        QCOMPARE(m_cm->error(), QContactManager::NoError);

    // 3.2 (if expected to fail) Check that saving a contact fails
    } else {
        // verify that the contact cannot be saved
        QVERIFY(!m_cm->saveContact(&contact));
        // TODO: what is the expected error code? does it depend on the case?

        // verify contact id is untouched 
        QVERIFY(contact.id() == QContactId());
    }

    QList<QContactLocalId> idsAfterRemove = m_cm->contactIds();
    QCOMPARE(idsAfterRemove.count(), idsBefore.count());
}

void tst_SimCM::fetchContacts_data()
{
    QTest::addColumn<QString>("simStore");
    QTest::addColumn<int>("contactCount");

    QTest::newRow("ADN")
        << QString("ADN")
        << int(10);

    QTest::newRow("SDN")
        << QString("SDN")
        << int(0); // You cannot save a contact to SDN

    // TODO: How to save to FDN? A dialog for PIN2 should be shown...
    QTest::newRow("FDN")
        << QString("FDN")
        << int(1);
}

/*
 * Tests if QContactManager::contacts and ::contactIds work.
 */
void tst_SimCM::fetchContacts()
{
    // init
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }
    QFETCH(int, contactCount);

    const int existingContactCount = m_cm->contactIds().count();

    QList<QContactLocalId> addedContacts = QList<QContactLocalId> ();

    // 1. Add contacts (optionally)
    for(int i(0); i < contactCount; i++) {
        QContact contact = createContact(
            QString("James").append(QString::number(i + 1)),
            QString("1234567890").append(QString::number(i + 1)));

        QVERIFY(m_cm->saveContact(&contact));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
        QVERIFY(contact.id() != QContactId());
        addedContacts.append(contact.localId());
    }

    // 2. Fetch contacts
    QList<QContact> contacts= m_cm->contacts();
    QCOMPARE(m_cm->error(), QContactManager::NoError);
    QList<QContactLocalId> contactIds = m_cm->contactIds();
    QCOMPARE(m_cm->error(), QContactManager::NoError);

    // 3. Verify result
    QVERIFY(contacts.count() > 0);
    QCOMPARE(contacts.count(), existingContactCount + contactCount);
    foreach (const QContact& contact, contacts) {
        QVERIFY(contact.id() != QContactId());
    }

    QVERIFY(contactIds.count() > 0);
    QCOMPARE(contactIds.count(), existingContactCount + contactCount);
    foreach (QContactLocalId id, addedContacts) {
        QVERIFY(id != QContactLocalId());
    }

    // 4. Remove contacts
    foreach (QContactLocalId id, addedContacts) {
        QVERIFY(m_cm->removeContact(id));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
    }

}

void tst_SimCM::updateContactDetail_data()
{
    QTest::addColumn<QString>("simStore");
    // The initial contact details,
    // for example: <"Name:First:James">; <"PhoneNumber:PhoneNumber:1234567890">
    QTest::addColumn<QStringList>("initialDetails");
    // The updated contact details,
    // for example: <"Name:First:James">; <"PhoneNumber:PhoneNumber:0987654321">
    QTest::addColumn<QStringList>("updatedDetails");

    QTest::newRow("update custom label")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James")
        << (QStringList()
            << "Name:CustomLabel:James Hunt");

    QTest::newRow("add phone number detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "PhoneNumber:PhoneNumber:+44752222222");

    QTest::newRow("update phone number detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "PhoneNumber:PhoneNumber:+44751111111")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "PhoneNumber:PhoneNumber:+44752222222");

    QTest::newRow("remove phone number detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "PhoneNumber:PhoneNumber:+44751111111")
        << (QStringList()
            << "Name:CustomLabel:James");

    QTest::newRow("add e-mail detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "EmailAddress:EmailAddress:james@hesketh.com");

    QTest::newRow("update e-mail detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "EmailAddress:EmailAddress:james@march.com")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "EmailAddress:EmailAddress:james@hesketh.com");

    QTest::newRow("remove e-mail detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "EmailAddress:EmailAddress:james@march.com")
        << (QStringList()
            << "Name:CustomLabel:James");

    QTest::newRow("add nickname detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "Nickname:Nickname:Hunt the Shunt");

    QTest::newRow("update nickname detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "Nickname:Nickname:James")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "Nickname:Nickname:Hunt the Shunt");

    QTest::newRow("remove nickname detail")
        << QString("ADN")
        << (QStringList()
            << "Name:CustomLabel:James"
            << "Nickname:Nickname:James")
        << (QStringList()
            << "Name:CustomLabel:James");
}

/*
 * Tests if SIM contacts can be changed. I.e. add, update and remove contact
 * details. Steps:
 * 1. Parse details from test data
 * 2. Add a contact with initial details
 * 3. Modify the contact (save with updated details)
 * 4. Remove the contact
 */
void tst_SimCM::updateContactDetail()
{
    QString tescaseName = QTest::currentDataTag();

    // init
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }
    QFETCH(QStringList, initialDetails);
    QFETCH(QStringList, updatedDetails);

    // 1. Parse details
    QContact contact;
    QList<QContactDetail> parsedDetails;
    parseDetails(contact, initialDetails, parsedDetails);

    // 2. Save contact and verify result
    if (!isContactSupported(contact)) {
        QSKIP("The contact cannot be saved onto the SIM card", SkipSingle);
    }
    QVERIFY(m_cm->saveContact(&contact));
    QCOMPARE(m_cm->error(), QContactManager::NoError);
    compareDetails(contact, parsedDetails);

    // 3. Update contact detail and verify result
    foreach (const QContactDetail& detail, parsedDetails) {
        QContactDetail savedDetail = contact.detail(detail.definitionName());
        QVERIFY(contact.removeDetail(&savedDetail));
    }
    parseDetails(contact, updatedDetails, parsedDetails);
    if (!isContactSupported(contact)) {
        QVERIFY(m_cm->removeContact(contact.localId()));
        QSKIP("The contact cannot be saved onto the SIM card", SkipSingle);
    }
    QVERIFY(m_cm->saveContact(&contact));
    QCOMPARE(m_cm->error(), QContactManager::NoError);
    compareDetails(contact, parsedDetails);

    // 4. Remove the contact
    QVERIFY(m_cm->removeContact(contact.localId()));
}

void tst_SimCM::batchOperations_data()
{
    QTest::addColumn<QString>("simStore");
    QTest::addColumn<int>("contactCount");
    QTest::addColumn<bool>("expectedResult");

    QTest::newRow("ADN")
        << QString("ADN")
        << 10
        << true;

    QTest::newRow("SDN")
        << QString("SDN")
        << 10
        << false; // You cannot save contacts to SDN

    // TODO: How to save to FDN? A dialog for PIN2 should be shown...
    QTest::newRow("FDN")
        << QString("FDN")
        << 10
        << true;
}

/*
 * Tests batch operations saveContacts and removeContacts.
 * 1. Add contacts
 * 2. Update contacts
 * 3. Remove contacts
 */
void tst_SimCM::batchOperations()
{
    // init
    QFETCH(QString, simStore);
    initManager(simStore);
    QVERIFY(m_cm);
    if(m_cm->error() == QContactManager::NotSupportedError) {
        QSKIP("The store not supported by the SIM card", SkipSingle);
    }
    QFETCH(int, contactCount);
    QFETCH(bool, expectedResult);

    QMap<int, QContactManager::Error> errorMap;
    QList<QContact> contacts;
    for(int i(0); i < contactCount; i++) {
        QContact contact = createContact(
            QString("James").append(QString::number(i + 1)),
            QString("1234567890").append(QString::number(i + 1)));
        contacts.append(contact);
    }

    // 1. Add contacts
    if (expectedResult) {
        QVERIFY(m_cm->saveContacts(&contacts, &errorMap));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
        QCOMPARE(errorMap.count(), 0);
        foreach (const QContact& contact, contacts) {
            QVERIFY(contact.id() != QContactId());
        }
    } else {
        QVERIFY(!m_cm->saveContacts(&contacts, &errorMap));
        QVERIFY(m_cm->error() != QContactManager::NoError);
        QCOMPARE(errorMap.count(), 10);
        foreach (const QContact& contact, contacts) {
            QCOMPARE(contact.id(), QContactId());
        }
    }

    // 2. Update contacts (updates name of each contact)
    if(expectedResult) {
        foreach (QContact contact, contacts) {
            int index = contacts.indexOf(contact);
            QContactName name = contact.detail(QContactName::DefinitionName);
            name.setCustomLabel(name.customLabel() + QString("u"));
            contact.saveDetail(&name);
            contacts.replace(index, contact);
        }
        QVERIFY(m_cm->saveContacts(&contacts, &errorMap));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
        QCOMPARE(errorMap.count(), 0);
    }

    // 3. Remove contacts
    if(expectedResult) {
        QList<QContactLocalId> contactIds;
        foreach (const QContact& contact, contacts) {
            contactIds.append(contact.localId());
        }
        QVERIFY(m_cm->removeContacts(contactIds, &errorMap));
        QCOMPARE(m_cm->error(), QContactManager::NoError);
        QCOMPARE(errorMap.count(), 0);
    }
}

/*
 * Test if signals contactsAdded, contactsChanged and contactsRemoved are
 * emitted correctly.
 */
void tst_SimCM::signalEmission()
{
    initManager("ADN");
    qRegisterMetaType<QContactLocalId>("QContactLocalId");
    qRegisterMetaType<QList<QContactLocalId> >("QList<QContactLocalId>");
    QSignalSpy spyAdded(m_cm, SIGNAL(contactsAdded(QList<QContactLocalId>)));
    QSignalSpy spyChanged(m_cm, SIGNAL(contactsChanged(QList<QContactLocalId>)));
    QSignalSpy spyRemoved(m_cm, SIGNAL(contactsRemoved(QList<QContactLocalId>)));

    QContact contact = createContact("James Hunt", "+44751111111");

    // 1. one contact added
    QVERIFY(m_cm->saveContact(&contact));
    QTRY_COMPARE(spyAdded.count(), 1);

    // 2. one contact changed
    QVERIFY(m_cm->saveContact(&contact));
    QTRY_COMPARE(spyChanged.count(), 1);

    // 3. one contact removed
    QVERIFY(m_cm->removeContact(contact.localId()));
    QTRY_COMPARE(spyRemoved.count(), 1);

    // 4. contacts added
    spyAdded.clear();
    int batchOpCount(10);
    QList<QContact> contacts;
    for(int i(0); i < batchOpCount; i++) {
        QContact contact = createContact(
            QString("James").append(QString::number(i + 1)),
            QString("1234567890").append(QString::number(i + 1)));
        contacts.append(contact);
    }
    QMap<int, QContactManager::Error> errorMap;
    QVERIFY(m_cm->saveContacts(&contacts, &errorMap));
    QTRY_COMPARE(spyAdded.count(), batchOpCount);

    // 5. contacts changed
    spyChanged.clear();
    QVERIFY(m_cm->saveContacts(&contacts, &errorMap));
    QTRY_COMPARE(spyChanged.count(), batchOpCount);

    // 6. contacts removed
    spyRemoved.clear();
    QList<QContactLocalId> contactIds;
    foreach(const QContact& contact, contacts) {
        contactIds.append(contact.localId());
    }
    QVERIFY(m_cm->removeContacts(contactIds, &errorMap));
    QTRY_COMPARE(spyRemoved.count(), batchOpCount);
}

/*!
 * Tests SDN store specific stuff
 */
void tst_SimCM::sdnContacts()
{
    QString uri("qtcontacts:symbiansim:store=SDN");
    QScopedPointer<QContactManager> cm(QContactManager::fromUri(uri));
    if (cm->error() == QContactManager::NotSupportedError)
        QSKIP("The store not supported by the SIM card", SkipSingle);
    
    QVERIFY(cm->error() == QContactManager::NoError);
    
    // Verify that contact details have read only flag
    QList<QContact> contacts = cm->contacts();
    QVERIFY(contacts.count());
    foreach(const QContact& c, contacts) {
        foreach(const QContactDetail& d, c.details()) {
            QVERIFY(d.accessConstraints().testFlag(QContactDetail::ReadOnly));
        }
    }

    // Writing should fail
    QContact c = createContact("foo", "1234567");
    QVERIFY(!cm->saveContact(&c));
}

/*!
 * Private helper function for checking the data format that the store supports
 */
void tst_SimCM::getEtelStoreInfoL(const TDesC &phonebook, TDes8 &infoPckg) const
{
    RTelServer etelServer;
    User::LeaveIfError(etelServer.Connect());
    CleanupClosePushL(etelServer);
    User::LeaveIfError(etelServer.LoadPhoneModule(KMmTsyModuleName));
 
    RMobilePhone etelPhone;
    RTelServer::TPhoneInfo info;
    User::LeaveIfError(etelServer.GetPhoneInfo(0, info));
    User::LeaveIfError(etelPhone.Open(etelServer, info.iName));
    CleanupClosePushL(etelPhone);

    //check what information can be saved to the Etel store
    RMobilePhoneBookStore etelStore;
    User::LeaveIfError(etelStore.Open(etelPhone, phonebook));
    CleanupClosePushL(etelStore);
    TRequestStatus requestStatus;
    etelStore.GetInfo(requestStatus, infoPckg);
    User::WaitForRequest(requestStatus);
    User::LeaveIfError(requestStatus.Int());

    CleanupStack::PopAndDestroy(&etelStore);
    CleanupStack::PopAndDestroy(&etelPhone);
    CleanupStack::PopAndDestroy(&etelServer);
}

/*!
 * Private helper function for checking if the SIM backend supports the
 * contact. This can be used in cases where it depends on the SIM card features
 * if the contact details can be saved or not.
 */
bool tst_SimCM::isContactSupported(QContact contact)
{
    QMap<QString, QContactDetailDefinition> detailDefinitions = m_cm->detailDefinitions();

    if(!m_cm->supportedContactTypes().contains(contact.type()))
        return false;
        
    QList<QString> uniqueDetails = QList<QString>();

    foreach(const QContactDetail& detail, contact.details()) {
        QString definitionName = detail.definitionName();

        // TODO: should we save a contact that has empty, non-supported details?
        // The current implementation is to ignore empty details here which
        // means that the backend should also ignore the empty details, even
        // if the detail in question is not supported.
        if (detail.isEmpty()) {
            continue;
        }

        // check if the detail is supported by the SIM
        if (detailDefinitions.contains(detail.definitionName())) {

            QContactDetailDefinition detailDef = detailDefinitions.value(detail.definitionName());

            // If the detail is unique check that there are no duplicates
            if (detailDef.isUnique()) {
                if (uniqueDetails.contains(detail.definitionName())) {
                    return false;
                } else {
                    uniqueDetails.append(detail.definitionName());
                }
            }

            // check the fields of the detail
            foreach (const QString& fieldKey, detail.variantValues().keys()) {
                if (!detailDef.fields().contains(fieldKey)) {
                    return false;
                }
            }

        } else {
            qDebug() << "Detail" << definitionName << "Not supported";
            return false;
        }
    }

    return true;
}

/*!
 * Private helper function for parsing test data (creates QContactDetails from
 * string lists).
 */
void tst_SimCM::parseDetails(QContact &contact, QStringList details, QList<QContactDetail> &parsedDetails)
{
    parsedDetails.clear();
    foreach (const QString& detail, details) {
        // the expected format is <detail definition name>:<field name>:<value>
        QStringList detailParts = detail.split(QChar(':'), QString::KeepEmptyParts, Qt::CaseSensitive);
        QVERIFY(detailParts.count() == 3);
    
        // Use existing detail if available and would not cause an overwrite of
        // a field value
        QContactDetail contactDetail = QContactDetail(detailParts[0]);
        if (contact.details().contains(detailParts[0])
            && contact.detail(detailParts[0]).variantValues().key(detailParts[1]).isNull()) {
            contactDetail = contact.detail(detailParts[0]);
        }

        // Set the field value only if not empty (do not add empty fields)  
        if (!detailParts[2].isEmpty()) {
            QVERIFY(contactDetail.setValue(detailParts[1], detailParts[2]));
        }

        QVERIFY(contact.saveDetail(&contactDetail));
        parsedDetails.append(contactDetail);
    }
}

/*
 * Private helper function for comparing QContact details to a well-known set
 * of QContactDetails.
 * \return true if all the expected contact details have a match in the \contact.
 */
void tst_SimCM::compareDetails(QContact contact, QList<QContactDetail> expectedDetails)
{
    foreach (QContactDetail expectedDetail, expectedDetails) {
        QContactDetail actualDetail = contact.detail(expectedDetail.definitionName());
        QVERIFY(!actualDetail.isEmpty());

        // Allow truncating the custom label to the max text length
        if (expectedDetail.definitionName() == QContactName::DefinitionName) {
            QContactName nameDetail = static_cast<QContactName>(expectedDetail);
            nameDetail.setCustomLabel(nameDetail.customLabel().left(m_etelStoreInfo.iMaxTextLength));
            expectedDetail = static_cast<QContactDetail>(nameDetail);
        // Allow truncating the nick name to the max text length
        } else if (expectedDetail.definitionName() == QContactNickname::DefinitionName) {
                QContactNickname nick = static_cast<QContactNickname>(expectedDetail);
                nick.setNickname(nick.nickname().left(m_etelStoreInfo.iMaxTextLength));
                expectedDetail = static_cast<QContactDetail>(nick);
        }

        if(!contact.details().contains(expectedDetail)) {
            // FAIL! Make it easier to debug the output by
            // comparing the contact detail field contents
            foreach (const QString& key, expectedDetail.variantValues().keys()) {
                QVariant value1 = actualDetail.value(key);
                QVariant value2 = expectedDetail.value(key);
                QCOMPARE(actualDetail.value(key), expectedDetail.value(key));
            }
        }
    }
}

QContact tst_SimCM::createContact(QString name, QString number)
{
    QContact c;
    
    QContactName n;
    n.setCustomLabel(name);
    c.saveDetail(&n);
    
    QContactPhoneNumber nb;
    nb.setNumber(number);
    c.saveDetail(&nb);

    return c;
}

QTEST_MAIN(tst_SimCM)
#include "tst_simcm.moc"
