/**
 * Copyright (C) 2013 Canonical, Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
 * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: Tiago Salem Herrmann <tiago.herrmann@canonical.com>
 */

#include <QtCore/QObject>
#include <QtTest/QtTest>
#include <QVariant>
#include <TelepathyQt/Contact>
#include <TelepathyQt/ContactManager>
#include <TelepathyQt/PendingContacts>
#include <TelepathyQt/PendingOperation>
#include <TelepathyQt/CallChannel>
#include "telepathyhelper.h"
#include "ofonophonesimhelper.h"
#include "handler.h"
#include "approvercall.h"

Q_DECLARE_METATYPE(Tp::CallChannelPtr);
Q_DECLARE_METATYPE(Tp::ChannelPtr);
Q_DECLARE_METATYPE(Tp::CallState);
Q_DECLARE_METATYPE(QList<Tp::ContactPtr>);

#define TELEPATHY_MUTE_IFACE "org.freedesktop.Telepathy.Call1.Interface.Mute"

class CallTest : public QObject
{
    Q_OBJECT

Q_SIGNALS:
    void contactsReceived(QList<Tp::ContactPtr> contacts);

private Q_SLOTS:
    void initTestCase();
    void testCallIncoming();
    void testCallIncomingPrivateNumber();
    void testCallIncomingUnknownNumber();
    void testNumberNormalization_data();
    void testNumberNormalization();
    void testCallOutgoing();
    void testCallHold();
    void testCallDTMF();
    void testCallMute();
    void testCallConference();

    // helper slots
    void onPendingContactsFinished(Tp::PendingOperation*);

private:
    Approver *mApprover;
    Handler *mHandler;
};

void CallTest::initTestCase()
{
    qRegisterMetaType<Tp::Presence>();
    qRegisterMetaType<Tp::CallState>();
    qRegisterMetaType<Tp::CallChannelPtr>();
    qRegisterMetaType<Tp::Channel::GroupMemberChangeDetails>();
    qRegisterMetaType<Tp::ChannelPtr>();
    qRegisterMetaType<Tp::PendingOperation*>();
    qRegisterMetaType<QList<Tp::ContactPtr> >();
    TelepathyHelper::instance();

    QSignalSpy spy(TelepathyHelper::instance(), &TelepathyHelper::accountReady);
    QTRY_COMPARE(spy.count(), 1);

    // Bring up the phonesim modem
    OfonoPhonesimHelper *phonesim = OfonoPhonesimHelper::instance();
    QTRY_COMPARE(phonesim->IsPresent(), true);
    phonesim->ModemSetOnline(true);
    QTRY_COMPARE(phonesim->NetworkRegistrationStatus(), "registered");

    // the account should be connected
    QTRY_VERIFY(TelepathyHelper::instance()->connected());

    mHandler = new Handler(this);
    TelepathyHelper::instance()->registerClient(mHandler, "TpOfonoTestHandler");
    QTRY_VERIFY(mHandler->isRegistered());

    mApprover = new Approver(this);
    TelepathyHelper::instance()->registerClient(mApprover, "TpOfonoTestApprover");
    QTRY_VERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(TELEPHONY_SERVICE_APPROVER));
}

void CallTest::testCallIncoming()
{
    QSignalSpy spyNewCallChannel(mHandler, &Handler::callChannelAvailable);
    QSignalSpy spyNewCallApprover(mApprover, &Approver::newCall);
    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    OfonoPhonesimHelper::instance()->VoiceCallManagerIncomingCall("123");
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyNewCallApprover.count(), 1);

    mApprover->acceptCall();
    QTRY_COMPARE(spyNewCallChannel.count(), 1);

    Tp::CallChannelPtr channel = spyNewCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    const QString &path = spyOfonoCallAdded.first().first().value<QString>();
    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallIncomingPrivateNumber()
{
    QSignalSpy spyNewCallChannel(mHandler, &Handler::callChannelAvailable);
    QSignalSpy spyNewCallApprover(mApprover, &Approver::newCall);
    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    OfonoPhonesimHelper::instance()->VoiceCallManagerIncomingCall("withheld");
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyNewCallApprover.count(), 1);

    mApprover->acceptCall();
    QTRY_COMPARE(spyNewCallChannel.count(), 1);

    Tp::CallChannelPtr channel = spyNewCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    QCOMPARE(channel->initiatorContact()->id(), QString("x-ofono-private"));

    const QString &path = spyOfonoCallAdded.first().first().value<QString>();
    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallIncomingUnknownNumber()
{
    QSignalSpy spyNewCallChannel(mHandler, &Handler::callChannelAvailable);
    QSignalSpy spyNewCallApprover(mApprover, &Approver::newCall);
    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    OfonoPhonesimHelper::instance()->VoiceCallManagerIncomingCall("");
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyNewCallApprover.count(), 1);

    mApprover->acceptCall();
    QTRY_COMPARE(spyNewCallChannel.count(), 1);

    Tp::CallChannelPtr channel = spyNewCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

#if 0
    // This should be "unknown", but ofono (or phonesim) appears to
    // quietly replace empty LineIdentifier strings with "withheld".
    QCOMPARE(channel->initiatorContact()->id(), QString("x-ofono-unknown"));
#else
    QCOMPARE(channel->initiatorContact()->id(), QString("x-ofono-private"));
#endif

    const QString &path = spyOfonoCallAdded.first().first().value<QString>();
    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testNumberNormalization_data()
{
    QTest::addColumn<QString>("number");
    QTest::addColumn<QString>("expectedNumber");

    QTest::newRow("simple number") << "12345678" << "12345678";
    QTest::newRow("number with dash") << "1234-5678" << "12345678"; 
    QTest::newRow("number with area code") << "(123)12345678" << "12312345678";
    QTest::newRow("number with slash") << "+421 2/123 456 78" << "+421212345678";
}

void CallTest::testNumberNormalization()
{
    QFETCH(QString, number);
    QFETCH(QString, expectedNumber);

    Tp::AccountPtr account = TelepathyHelper::instance()->account();
    QSignalSpy spy(this, &CallTest::contactsReceived);

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << number),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString(expectedNumber));
}

void CallTest::testCallOutgoing()
{
    // Request the contact to start chatting to
    Tp::AccountPtr account = TelepathyHelper::instance()->account();
    QSignalSpy spy(this, &CallTest::contactsReceived);

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "321"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("321"));

    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    QSignalSpy spyCallChannel(mHandler, &Handler::callChannelAvailable);

    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    const QString &path = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    QSignalSpy spyCallChanged(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::VoiceCallChanged);
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QCOMPARE(spyCallChanged.first().first().value<QString>(), "alerting");

    QTRY_COMPARE(channel->callState(), Tp::CallStateInitialised);
    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateActive);
    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallHold()
{
    QSignalSpy spyNewCallChannel(mHandler, &Handler::callChannelAvailable);
    QSignalSpy spyNewCallApprover(mApprover, &Approver::newCall);
    OfonoPhonesimHelper::instance()->VoiceCallManagerIncomingCall("123");
    QTRY_COMPARE(spyNewCallApprover.count(), 1);

    mApprover->acceptCall();
    QTRY_COMPARE(spyNewCallChannel.count(), 1);

    Tp::CallChannelPtr channel = spyNewCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    channel->accept();
    QTRY_COMPARE(channel->callState(), Tp::CallStateActive);

    channel->requestHold(true);
    QTRY_COMPARE(channel->localHoldState(), Tp::LocalHoldStateHeld);

    channel->requestHold(false);
    QTRY_COMPARE(channel->localHoldState(), Tp::LocalHoldStateUnheld);

    channel->hangup();
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallDTMF()
{
    // Request the contact to start chatting to
    Tp::AccountPtr account = TelepathyHelper::instance()->account();
    QSignalSpy spy(this, &CallTest::contactsReceived);

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "321"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("321"));

    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    QSignalSpy spyCallChannel(mHandler, &Handler::callChannelAvailable);

    // Place the outgoing call
    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    const QString &path = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    // Have the remote side ring and then answer the call
    QSignalSpy spyCallChanged(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::VoiceCallChanged);
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QTRY_COMPARE(channel->callState(), Tp::CallStateInitialised);

    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateActive);


    // Send DTMF tones
    Q_FOREACH(const Tp::CallContentPtr &content, channel->contents()) {
        if (content->supportsDTMF()) {
            bool ok;
            QStringList keys;
            keys << "0" << "4" << "9" << "*" << "#";
            QStringListIterator keysIterator(keys);
            while (keysIterator.hasNext()) {
                QString key = keysIterator.next();
                Tp::DTMFEvent event = (Tp::DTMFEvent)key.toInt(&ok);
                if (!ok) {
                    if (!key.compare("*")) {
                        event = Tp::DTMFEventAsterisk;
                    } else if (!key.compare("#")) {
                        event = Tp::DTMFEventHash;
                    } else {
                        qWarning() << "Tone not recognized. DTMF failed";
                        return;
                    }
                }

                Tp::PendingOperation *op = content->startDTMFTone(event);
                bool valid = false;
                connect(op, &Tp::PendingOperation::finished, this,
                        [&](Tp::PendingOperation *op) {
                    valid = op->isValid();
                });
                QSignalSpy spyTonesSent(op, &Tp::PendingOperation::finished);
                QTRY_COMPARE(spyTonesSent.count(), 1);
                QCOMPARE(valid, true);
            }

            // TelepathyQt doesn't support the StoppedTones signal (emitted
            // when a tone finishes playing; documented at
            // https://telepathy.freedesktop.org/spec/Call_Content_Interface_DTMF.html).
            // VoiceCallManager's SendTones() blocks until complete, but
            // telepathy-ofono queues up all DTMFs and doesn't have a way to
            // let us know when one completes.
            //
            // A hack we use is to verify the tones are playing is by
            // checking that SwapCalls() fails with an InProgress error.
            //
            // Note that there's a very small window here for a race,
            // where a tone might have finished playing right when we
            // call SwapCalls(). Since the first tone plays for one full
            // second, we should avoid that race window as long as
            // SwapCalls() gets called soon after the initial tone.
            QSignalSpy spySwappedCalls(OfonoPhonesimHelper::instance(),
                    &OfonoPhonesimHelper::SwapCallsComplete);
            OfonoPhonesimHelper::instance()->VoiceCallManagerSwapCalls();
            QTRY_COMPARE(spySwappedCalls.count(), 1);
            bool success = spySwappedCalls.first().at(0).value<bool>();
            const QString &err = spySwappedCalls.first().at(1).value<QString>();
            QCOMPARE(success, false);
            QCOMPARE(err, "org.ofono.Error.InProgress");

            // tones are InProgress; kill them via stopDTMFTone().
            Tp::PendingOperation *op = content->stopDTMFTone();
            bool valid = false;
            connect(op, &Tp::PendingOperation::finished, this,
                    [&](Tp::PendingOperation *op) {
                        valid = op->isValid();
            });
            QSignalSpy spyToneStopped(op, &Tp::PendingOperation::finished);
            QTRY_COMPARE(spyToneStopped.count(), 1);
            QCOMPARE(valid, true);

            // line should no longer be busy, try swapping calls again
            spySwappedCalls.clear();
            OfonoPhonesimHelper::instance()->VoiceCallManagerSwapCalls();
            QTRY_COMPARE(spySwappedCalls.count(), 1);
            QCOMPARE(spySwappedCalls.first().at(0).value<bool>(), true);

#if 0
            // Phonesim doesn't give us a way to specify that the DTMF
            // tone delivery should fail, so we can't test that code path.
            // As mentioned above, even if tones *did* fail we wouldn't
            // know about it - telepathy-ofono would just keep the tones
            // queued and keep retrying until success of channel hangup.

            // test failed tones
            QSignalSpy spyOfonoTonesReceived(OfonoMockController::instance(), SIGNAL(TonesReceived(const QString&)));
            OfonoMockController::instance()->VoiceCallManagerFailNextDtmf();
            content->startDTMFTone((Tp::DTMFEvent)QString("1").toInt(&ok));
            OfonoMockController::instance()->VoiceCallManagerFailNextDtmf();
            content->startDTMFTone((Tp::DTMFEvent)QString("2").toInt(&ok));
            content->startDTMFTone((Tp::DTMFEvent)QString("3").toInt(&ok));
            QTRY_COMPARE(spyOfonoTonesReceived.count(), 2);

            QCOMPARE(spyOfonoTonesReceived.first().first().value<QString>(), QString("1"));
            spyOfonoTonesReceived.removeFirst();
            QCOMPARE(spyOfonoTonesReceived.first().first().value<QString>(), QString("23"));
#endif
        }
    }

    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallMute()
{
    // Request the contact to start chatting to
    Tp::AccountPtr account = TelepathyHelper::instance()->account();
    QSignalSpy spy(this, &CallTest::contactsReceived);

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "321"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("321"));

    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    QSignalSpy spyCallChannel(mHandler, &Handler::callChannelAvailable);

    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    const QString &path = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel);

    QSignalSpy spyCallChanged(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::VoiceCallChanged);
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QCOMPARE(spyCallChanged.first().first().value<QString>(), "alerting");
    QTRY_COMPARE(channel->callState(), Tp::CallStateInitialised);

    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateActive);


    QDBusInterface muteInterface(channel->busName(), channel->objectPath(), TELEPATHY_MUTE_IFACE);
    QSignalSpy spyMuteChanged(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::MutedChanged);
    muteInterface.call("RequestMuted", true);
    QTRY_COMPARE(spyMuteChanged.count(), 1);
    QTRY_COMPARE(spyMuteChanged.first().first().value<bool>(), true);
    spyMuteChanged.clear();

    muteInterface.call("RequestMuted", false);
    QTRY_COMPARE(spyMuteChanged.count(), 1);
    QTRY_COMPARE(spyMuteChanged.first().first().value<bool>(), false);

    OfonoPhonesimHelper::instance()->VoiceCallHangup(path);
    QTRY_COMPARE(channel->callState(), Tp::CallStateEnded);
}

void CallTest::testCallConference()
{
    // Request the contact to start chatting to
    Tp::AccountPtr account = TelepathyHelper::instance()->account();
    QSignalSpy spy(this, &CallTest::contactsReceived);
    QSignalSpy spyOfonoCallAdded(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::CallAdded);
    QSignalSpy spyCallChannel(mHandler, &Handler::callChannelAvailable);

    // Call #1
    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "333"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    QList<Tp::ContactPtr> contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("333"));

    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    QString path1 = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel1 = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel1);

    QSignalSpy spyCallChanged(OfonoPhonesimHelper::instance(),
            &OfonoPhonesimHelper::VoiceCallChanged);
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path1);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QCOMPARE(spyCallChanged.first().first().value<QString>(), "alerting");
    QTRY_COMPARE(channel1->callState(), Tp::CallStateInitialised);

    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path1);
    QTRY_COMPARE(channel1->callState(), Tp::CallStateActive);

    spy.clear();
    spyOfonoCallAdded.clear();
    spyCallChannel.clear();

    // Call #2

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "444"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("444"));

    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    QString path2 = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel2 = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel2);

    spyCallChanged.clear();
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path2);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QCOMPARE(spyCallChanged.first().first().value<QString>(), "alerting");
    QTRY_COMPARE(channel2->callState(), Tp::CallStateInitialised);

    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path2);
    QTRY_COMPARE(channel2->callState(), Tp::CallStateActive);

    spy.clear();
    spyOfonoCallAdded.clear();
    spyCallChannel.clear();

    // create conference
    QList<Tp::ChannelPtr> calls;
    calls << channel1 << channel2;
    TelepathyHelper::instance()->account()->createConferenceCall(calls, QStringList(), QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    QTRY_COMPARE(spyCallChannel.count(), 1);

    Tp::CallChannelPtr conferenceChannel = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(conferenceChannel);
    
    QSignalSpy spyConferenceChannelMerged(conferenceChannel.data(),
            &Tp::Channel::conferenceChannelMerged);
    QSignalSpy spyConferenceChannelRemoved(conferenceChannel.data(),
            &Tp::Channel::conferenceChannelRemoved);

    spy.clear();
    spyOfonoCallAdded.clear();
    spyCallChannel.clear();

    // Call #3

    connect(account->connection()->contactManager()->contactsForIdentifiers(QStringList() << "555"),
            &Tp::PendingOperation::finished,
            this, &CallTest::onPendingContactsFinished);

    QTRY_COMPARE(spy.count(), 1);

    contacts = spy.first().first().value<QList<Tp::ContactPtr> >();
    QCOMPARE(contacts.count(), 1);
    QCOMPARE(contacts.first()->id(), QString("555"));

    Q_FOREACH(Tp::ContactPtr contact, contacts) {
        account->ensureAudioCall(contact, "audio", QDateTime::currentDateTime(), TP_QT_IFACE_CLIENT + ".TpOfonoTestHandler");
    }
    QTRY_COMPARE(spyOfonoCallAdded.count(), 1);
    QTRY_COMPARE(spyCallChannel.count(), 1);
    QString path3 = spyOfonoCallAdded.first().first().value<QString>();

    Tp::CallChannelPtr channel3 = spyCallChannel.first().first().value<Tp::CallChannelPtr>();
    QVERIFY(channel3);

    spyCallChanged.clear();
    OfonoPhonesimHelper::instance()->VoiceCallSetAlerting(path3);
    QTRY_COMPARE(spyCallChanged.count(), 1);
    QCOMPARE(spyCallChanged.first().first().value<QString>(), "alerting");
    QTRY_COMPARE(channel3->callState(), Tp::CallStateInitialised);

    OfonoPhonesimHelper::instance()->VoiceCallAnswer(path3);
    QTRY_COMPARE(channel3->callState(), Tp::CallStateActive);

    spy.clear();
    spyOfonoCallAdded.clear();
    spyCallChannel.clear();

    // merge channel 3 into the conference
    conferenceChannel->conferenceMergeChannel(channel3);
    QTRY_COMPARE(spyConferenceChannelMerged.count(), 1);

    // hangup first call so we end up with 2 calls in a conference
    OfonoPhonesimHelper::instance()->VoiceCallHangup(path1);
    QTRY_COMPARE(channel1->callState(), Tp::CallStateEnded);
    QTRY_COMPARE(spyConferenceChannelRemoved.count(), 1);

    // split conference so we end up with 2 individual calls
    channel2->conferenceSplitChannel();
    QTRY_COMPARE(channel2->callState(), Tp::CallStateActive);

    // check if the conference was finished
    QTRY_COMPARE(conferenceChannel->callState(), Tp::CallStateEnded);

    OfonoPhonesimHelper::instance()->VoiceCallHangup(path2);
    QTRY_COMPARE(channel2->callState(), Tp::CallStateEnded);

    OfonoPhonesimHelper::instance()->VoiceCallHangup(path3);
    QTRY_COMPARE(channel3->callState(), Tp::CallStateEnded);
}

void CallTest::onPendingContactsFinished(Tp::PendingOperation *op)
{
    Tp::PendingContacts *pc = qobject_cast<Tp::PendingContacts*>(op);
    if (!pc) {
        return;
    }

    Q_EMIT contactsReceived(pc->contacts());
}

QTEST_MAIN(CallTest)
#include "CallTest.moc"
