/****************************************************************************
**
** 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 test suite of the Qt Toolkit.
**
** $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$
**
****************************************************************************/
/*  This file is part of the KDE project
    Copyright (C) 2006-2007 Matthias Kretz <kretz@kde.org>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of
    the License, or (at your option) version 3.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <QtTest/QtTest>
#include <QtCore/QDate>
#include <QtCore/QDebug>
#include <QtCore/QObject>
#include <QtCore/QUrl>

#ifndef QT_NO_PHONON
#include <phonon/path.h>
#include <phonon/audiooutput.h>
#include <phonon/mediaobject.h>
#include <phonon/phononnamespace.h>
#include <phonon/audiooutput.h>
#include <phonon/seekslider.h>
#include <phonon/mediaobject.h>
#include <phonon/volumeslider.h>
#include <phonon/videowidget.h>
#include <phonon/backendcapabilities.h>

#include "qtesthelper.h"
#include <cstdlib>
#endif

#ifndef Q_WS_WIN
#include <unistd.h>
#endif

#ifdef Q_OS_WINCE
#define MEDIA_FILE "/sax.wav"
#define MEDIA_FILEPATH ":/media/sax.wav"
const qint64 SEEK_BACKWARDS = 2000;
const qint64 ALLOWED_TIME_FOR_SEEKING = 1500; // 1.5s
const qint64 SEEKING_TOLERANCE = 250;
#else 
#if defined(Q_OS_WIN) || defined(Q_OS_MAC) || defined(Q_OS_SYMBIAN)
#define MEDIA_FILE "/sax.mp3"
#define MEDIA_FILEPATH ":/media/sax.mp3"
#else
#define MEDIA_FILE "/sax.ogg"
#define MEDIA_FILEPATH ":/media/sax.ogg"
#endif
const qint64 SEEK_BACKWARDS = 4000;
const qint64 ALLOWED_TIME_FOR_SEEKING = 1000; // 1s
const qint64 SEEKING_TOLERANCE = 0;
#endif //Q_OS_WINCE

class tst_MediaObject : public QObject
{
    Q_OBJECT
    public:
        tst_MediaObject()
            : m_success(false)
    {
        qputenv("PHONON_GST_AUDIOSINK", "fake");
    }

#ifndef QT_NO_PHONON

    Q_SIGNALS:
        void continueTestPlayOnFinish();

    protected Q_SLOTS:
        void enqueueMedia();
        void setMediaAndPlay();
        void stateChanged(Phonon::State, Phonon::State);
    private Q_SLOTS:
        void init();
        void cleanup();

        void testPlayFromResource();
        void testPlayIllegalFile();
        void initTestCase();
        void checkForDefaults();

        // state change tests
        void stopToStop();
        void stopToPause();
        void stopToPlay();
        void playToPlay();
        void playToPause();
        void playToStop();
        void pauseToPause();
        void pauseToPlay();
        void pauseToStop();
        void playSDP();

        void testPrefinishMark();
        void testSeek();
        void testTickSignal();
        void testJustInTimeQueuing();
        void testPlayOnFinish();
        void testPlayBeforeFinish();
        void testPauseOnFinish();
        void testReconnectBetweenTwoMediaObjects();
        void volumeSliderMuteVisibility();
        void cleanupTestCase();
    private:
        void _startPlayback(Phonon::State currentState = Phonon::StoppedState);
        void _stopPlayback(Phonon::State currentState);
        void _pausePlayback();
        void _testOneSeek(qint64 seekTo);

        QUrl m_url;
        Phonon::MediaObject *m_media;
        QSignalSpy *m_stateChangedSignalSpy;
        QString m_tmpFileName;

        static void copyMediaFile(const QString &original,
                                  const QString &name,
                                  QString &resultFilePath,
                                  QUrl *const asURL = 0);
#endif //QT_NO_PHONON
        bool m_success;
};

#ifndef QT_NO_PHONON

#define startPlayback() _startPlayback(); if (!m_success) return; m_success = false;
#define startPlayback2(currentState) _startPlayback(currentState); if (!m_success) return; m_success = false;
#define stopPlayback(currentState) _stopPlayback(currentState); if (!m_success) return; m_success = false;
#define pausePlayback() _pausePlayback(); if (!m_success) return; m_success = false;
#define testOneSeek(seekTo) _testOneSeek(seekTo); if (!m_success) return; m_success = false;

const qint64 ALLOWED_SEEK_INACCURACY = 300; // 0.3s
const qint64 ALLOWED_TICK_INACCURACY = 350; // allow +/- 350 ms inaccuracy

using namespace Phonon;

static qint64 castQVariantToInt64(const QVariant &variant)
{
    return *reinterpret_cast<const qint64 *>(variant.constData());
}

static qint32 castQVariantToInt32(const QVariant &variant)
{
    return *reinterpret_cast<const qint32 *>(variant.constData());
}

void tst_MediaObject::stateChanged(Phonon::State newstate, Phonon::State oldstate)
{
    if (newstate == Phonon::ErrorState)
        QWARN(QByteArray(QByteArray(QTest::toString(oldstate)) + " to " + QByteArray(QTest::toString(newstate))));
}

void tst_MediaObject::testPlayFromResource()
{
#ifdef Q_OS_SYMBIAN
    QSKIP("Not implemented yet.", SkipAll);
#else
    QFile file(MEDIA_FILEPATH);
    MediaObject media;
    media.setCurrentSource(&file);
    QVERIFY(media.state() != Phonon::ErrorState);
    if (media.state() != Phonon::StoppedState)
        QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
    QCOMPARE(media.state(), Phonon::StoppedState);
    media.play();
    if (media.state() != Phonon::PlayingState)
        QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
    QCOMPARE(media.state(), Phonon::PlayingState);
#endif
}

void tst_MediaObject::testPlayIllegalFile()
{
    QString filename = QDir::tempPath() + QString("/test.wav");
    QFile::remove(filename);
    QFile file(filename);
    file.open(QIODevice::WriteOnly);
    for (int i=0;i<0xffff;i++) {
        int r = qrand();
        file.write((const char*)&r, 2);
    }
    file.close();
    MediaObject media;
    media.setCurrentSource(filename);
    QTest::waitForSignal(&media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);
    QCOMPARE(media.state(), Phonon::ErrorState);
    media.play();
    QCOMPARE(media.state(), Phonon::ErrorState);
    QFile::remove(filename);
}

void tst_MediaObject::init()
{
    QCOMPARE(m_media->outputPaths().size(), 1);
    if (m_media->state() == Phonon::ErrorState) {
        m_media->setCurrentSource(m_url);
        if (m_media->state() == Phonon::ErrorState) {
            QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
        }
        if (m_media->state() == Phonon::LoadingState) {
            QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
        }
        m_stateChangedSignalSpy->clear();
    }

    // Ensure that m_media is in StoppedState
    if (m_media->state() != Phonon::StoppedState) {
        m_media->stop();
        QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
        QCOMPARE(m_media->state(), Phonon::StoppedState);
    }
}

void tst_MediaObject::cleanup()
{
    switch (m_media->state()) {
    case Phonon::PlayingState:
    case Phonon::BufferingState:
    case Phonon::PausedState:
        stopPlayback(m_media->state());
        break;
    default:
        break;
    }
    m_stateChangedSignalSpy->clear();
}

void tst_MediaObject::_startPlayback(Phonon::State currentState)
{
    m_stateChangedSignalSpy->clear();
    Phonon::State s = m_media->state();
    QCOMPARE(s, currentState);
    m_media->play();
    while (s != Phonon::PlayingState) {
        if (m_stateChangedSignalSpy->isEmpty()) {
            QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 3000);
            QApplication::processEvents();
        }
        while (!m_stateChangedSignalSpy->isEmpty()) {
            QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
            Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
            QCOMPARE(laststate, s);
            s = qvariant_cast<Phonon::State>(args.at(0));
            QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
        }
    }
    QCOMPARE(s, Phonon::PlayingState);
    QCOMPARE(m_media->state(), Phonon::PlayingState);
    m_success = true;
}

void tst_MediaObject::_stopPlayback(Phonon::State currentState)
{
    QVERIFY(currentState != Phonon::ErrorState);
    m_stateChangedSignalSpy->clear();
    Phonon::State s = m_media->state();
    QCOMPARE(s, currentState);
    m_media->stop();
    while (s != Phonon::StoppedState) {
        if (m_stateChangedSignalSpy->isEmpty()) {
            QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
        }
        while (!m_stateChangedSignalSpy->isEmpty()) {
            QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
            Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
            QCOMPARE(laststate, s);
            s = qvariant_cast<Phonon::State>(args.at(0));
            if (s == Phonon::StoppedState) {
                QVERIFY(m_stateChangedSignalSpy->isEmpty());
                break;
            }
            QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
        }
    }
    QCOMPARE(s, Phonon::StoppedState);
    QCOMPARE(m_media->state(), Phonon::StoppedState);
    m_success = true;
}

void tst_MediaObject::_pausePlayback()
{
    m_stateChangedSignalSpy->clear();
    Phonon::State s = m_media->state();
    m_media->pause();
    while (s != Phonon::PausedState) {
        if (m_stateChangedSignalSpy->isEmpty()) {
            QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
        }
        while (!m_stateChangedSignalSpy->isEmpty()) {
            QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
            Phonon::State laststate = qvariant_cast<Phonon::State>(args.at(1));
            QCOMPARE(laststate, s);
            s = qvariant_cast<Phonon::State>(args.at(0));
            if (s == Phonon::PausedState) {
                QVERIFY(m_stateChangedSignalSpy->isEmpty());
                break;
            }
            QVERIFY(s == Phonon::BufferingState || s == Phonon::PlayingState);
        }
    }
    QCOMPARE(s, Phonon::PausedState);
    QCOMPARE(m_media->state(), Phonon::PausedState);
    m_success = true;
}

/*!
  Copies the file \a name to the testing area. The resulting file name path is
  returned in resultFilePath, and also set as a URL in \a asURL.
 */
void tst_MediaObject::copyMediaFile(const QString &original,
                                    const QString &name,
                                    QString &resultFilePath,
                                    QUrl *const asURL)
{
    resultFilePath = QDir::toNativeSeparators(QDir::tempPath() + name);
    if (asURL)
        *asURL = QUrl::fromLocalFile(resultFilePath);

    QFile::remove(resultFilePath);
    QVERIFY(QFile::copy(original, resultFilePath));
    QFile::setPermissions(resultFilePath, QFile::permissions(resultFilePath) | QFile::WriteOther);
}

void tst_MediaObject::initTestCase()
{
    QCoreApplication::setApplicationName("tst_MediaObject");
    m_stateChangedSignalSpy = 0;
    m_media = 0;

#ifdef Q_OS_WINCE
    QString pluginsPath = QLibraryInfo::location(QLibraryInfo::PluginsPath);
#ifdef DEBUG
    QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveoutd4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds9d4.dll"));
#else
    QVERIFY(QFile::exists(pluginsPath + "/phonon_backend/phonon_waveout4.dll") || QFile::exists(pluginsPath + "/phonon_backend/phonon_phonon_ds94.dll"));
#endif
#endif


    m_url = qgetenv("PHONON_TESTURL");
    m_media = new MediaObject(this);
    connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(stateChanged(Phonon::State, Phonon::State)));
    m_stateChangedSignalSpy = new QSignalSpy(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
    QVERIFY(m_stateChangedSignalSpy->isValid());
    m_stateChangedSignalSpy->clear();

    if (m_url.isEmpty())
        copyMediaFile(MEDIA_FILEPATH, MEDIA_FILE, m_tmpFileName, &m_url);
    
    qDebug() << "Using url:" << m_url.toString();

    // AudioOutput is needed else the backend might have no time source
    AudioOutput *audioOutput = new AudioOutput(Phonon::MusicCategory, this);
    //audioOutput->setVolume(0.0f);

    QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64)));
    QVERIFY(m_media->queue().isEmpty());
    QCOMPARE(m_media->currentSource().type(), MediaSource::Empty);
    QCOMPARE(m_media->state(), Phonon::LoadingState);
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);

    m_media->setCurrentSource(m_url);
    QCOMPARE(m_media->currentSource().type(), MediaSource::Url);
    QCOMPARE(m_media->currentSource().url(), m_url);

    int emits = m_stateChangedSignalSpy->count();
    Phonon::State s = m_media->state();
    if (s == Phonon::LoadingState) {
        // still in LoadingState, there should be no state change
        QCOMPARE(emits, 0);
        QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000);
        emits = m_stateChangedSignalSpy->count();
        s = m_media->state();
    }
    if (s != Phonon::LoadingState) {
        // there should exactly be one state change
        QCOMPARE(emits, 1);
        QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
        Phonon::State newstate = qvariant_cast<Phonon::State>(args.at(0));
        Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1));

        QCOMPARE(Phonon::LoadingState, oldstate);
        QCOMPARE(s, newstate);
        if (Phonon::ErrorState == s) {
#ifdef Q_WS_WIN
            if (m_media->errorString().contains(QLatin1String("no audio hardware is available")))
                QSKIP("On Windows we need an audio devide to perform the MediaObject tests", SkipAll); 
            else
#endif
            QFAIL("Loading the URL put the MediaObject into the ErrorState. Check that PHONON_TESTURL is set to a valid URL.");
        }
        QCOMPARE(Phonon::StoppedState, s);
        QCOMPARE(m_stateChangedSignalSpy->count(), 0);

        // check for totalTimeChanged signal
        QVERIFY(totalTimeChangedSignalSpy.count() > 0);
        args = totalTimeChangedSignalSpy.takeLast();
        QCOMPARE(m_media->totalTime(), castQVariantToInt64(args.at(0)));
    } else {
        QFAIL("Still in LoadingState after a stateChange signal was emitted. Cannot go on.");
    }

    Path path = createPath(m_media, audioOutput);
    QVERIFY(path.isValid());


    QCOMPARE(m_media->outputPaths().size(), 1);
    QCOMPARE(audioOutput->inputPaths().size(), 1);

}

void tst_MediaObject::checkForDefaults()
{
    QCOMPARE(m_media->tickInterval(), qint32(0));
    QCOMPARE(m_media->prefinishMark(), qint32(0));
}

void tst_MediaObject::stopToStop()
{
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);
    QCOMPARE(m_media->state(), Phonon::StoppedState);
    m_media->stop();
    QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 2000);
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);
    QCOMPARE(m_media->state(), Phonon::StoppedState);
}

void tst_MediaObject::stopToPause()
{
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);
    QCOMPARE(m_media->state(), Phonon::StoppedState);
    m_media->pause();
    if (m_stateChangedSignalSpy->isEmpty()) {
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 6000));
    }
    QCOMPARE(m_stateChangedSignalSpy->count(), 1);
    QCOMPARE(m_media->state(), Phonon::PausedState);
}

void tst_MediaObject::stopToPlay()
{
    startPlayback();
    QTest::waitForSignal(m_media, SIGNAL(finished()), 1000);
    stopPlayback(Phonon::PlayingState);
}

void tst_MediaObject::playToPlay()
{
    startPlayback();

    m_media->play();
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);
    QCOMPARE(m_media->state(), Phonon::PlayingState);

    stopPlayback(Phonon::PlayingState);
}

void tst_MediaObject::playToPause()
{
    startPlayback();
    QCOMPARE(m_media->state(), Phonon::PlayingState);
    pausePlayback();
    stopPlayback(Phonon::PausedState);
}

void tst_MediaObject::playToStop()
{
    startPlayback();
    stopPlayback(Phonon::PlayingState);
}

void tst_MediaObject::pauseToPause()
{
    startPlayback();
    pausePlayback();

    m_media->pause();
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);
    QCOMPARE(m_media->state(), Phonon::PausedState);

    stopPlayback(Phonon::PausedState);
}

void tst_MediaObject::pauseToPlay()
{
    startPlayback();
    pausePlayback();
    startPlayback2(Phonon::PausedState);
    stopPlayback(Phonon::PlayingState);
}

void tst_MediaObject::pauseToStop()
{
    startPlayback();
    pausePlayback();
    stopPlayback(Phonon::PausedState);
}

/*!

    We attempt to play a SDP file. An SDP file essentially describes different
    media streams and is hence a layer in front of the actual media(s).
    Sometimes the backend handles the SDP file, in other cases not.

    Some Phonon backends doesn't support SDP at all, ifdef appropriately. Real
    Player and Helix, the two backends for Symbian, are known to support SDP.
 */
void tst_MediaObject::playSDP()
{
#ifdef Q_OS_SYMBIAN
    QString sdpFile;
    copyMediaFile(QLatin1String(":/media/test.sdp"), QLatin1String("test.sdp"), sdpFile);

    // Let's verify our test setup.
    QVERIFY(QFileInfo(sdpFile).isReadable());

    // We need a window in order to setup the video.
    QWidget widget;
    widget.show();

    const MediaSource oldSource(m_media->currentSource());
    const MediaSource sdpSource(sdpFile);
    m_media->setCurrentSource(sdpSource);
    if (m_media->state() != Phonon::StoppedState)
        QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 10000);

    // MediaObject should have loaded the SDP, but be in error state due to absent media
    const bool stateMatch = (m_media->state() == Phonon::ErrorState);
    const bool errorStringMatch = (m_media->errorString() == QString::fromLatin1("Loading clip failed: Unknown error (-39)"));

    // Ensure that m_media is back in ground state prior to starting next test step
    m_media->setCurrentSource(oldSource);
    if (m_media->state() != Phonon::StoppedState)
       QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)));
    QCOMPARE(m_media->state(), Phonon::StoppedState);

    QVERIFY(stateMatch);
    QVERIFY(errorStringMatch);

#else
    QSKIP("Unsupported on this platform.", SkipAll);
#endif
}

void tst_MediaObject::testPrefinishMark()
{
    const qint32 requestedPrefinishMarkTime = 2000;
    m_media->setPrefinishMark(requestedPrefinishMarkTime);
    QCOMPARE(m_media->prefinishMark(), requestedPrefinishMarkTime);
    QSignalSpy prefinishMarkReachedSpy(m_media, SIGNAL(prefinishMarkReached(qint32)));
    QSignalSpy finishSpy(m_media, SIGNAL(finished()));
    startPlayback();
    if (m_media->isSeekable()) {
        m_media->seek(m_media->totalTime() - SEEK_BACKWARDS - requestedPrefinishMarkTime); // give it 4 seconds
    }
    int wait = 10000;
    int total = 0;
    while (prefinishMarkReachedSpy.count() == 0 && (m_media->state() == Phonon::PlayingState ||
                m_media->state() == Phonon::BufferingState)) {
        wait = qMax(1000, wait / 2);
        QTest::waitForSignal(m_media, SIGNAL(prefinishMarkReached(qint32)), wait);
        total += wait;
        if (total >= 60*1000) // we wait 1 minute
            QFAIL("Timeout failure waiting for signal");
    }
    // at this point the media should be about to finish playing
    qint64 r = m_media->remainingTime();
    Phonon::State state = m_media->state();
    QCOMPARE(prefinishMarkReachedSpy.count(), 1);
    const qint32 prefinishMark = castQVariantToInt32(prefinishMarkReachedSpy.first().at(0));
    QVERIFY(prefinishMark <= requestedPrefinishMarkTime + 150); // allow it to be up to 150ms too early
    if (state == Phonon::PlayingState || state == Phonon::BufferingState) {
        if (r > prefinishMark) {
            qDebug() << "remainingTime =" << r;
            QFAIL("remainingTime needs to be less than or equal to prefinishMark");
        }
        QVERIFY(r <= prefinishMark);
        QTest::waitForSignal(m_media, SIGNAL(finished()), 10000);
    } else {
        QVERIFY(prefinishMark >= 0);
    }
    QCOMPARE(finishSpy.count(), 1);
}

void tst_MediaObject::enqueueMedia()
{
    m_media->enqueue(m_url);
}

Q_DECLARE_METATYPE(Phonon::MediaSource)
void tst_MediaObject::testJustInTimeQueuing()
{
#ifdef Q_OS_WINCE
    QSKIP("crashes on Windows CE", SkipAll);
#endif
    qRegisterMetaType<Phonon::MediaSource>("Phonon::MediaSource");
    QSignalSpy currentSourceChanged(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &)));
    QSignalSpy finished(m_media, SIGNAL(finished()));
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(enqueueMedia()));

    startPlayback();
    if (m_media->isSeekable()) {
        m_media->seek(m_media->totalTime() - SEEK_BACKWARDS);
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 6000));
    } else {
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(aboutToFinish()), 3000 + m_media->remainingTime()));
    }
    disconnect(m_media, SIGNAL(aboutToFinish()), this, SLOT(enqueueMedia()));
    if (currentSourceChanged.isEmpty()) {
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(currentSourceChanged(const Phonon::MediaSource &)), 3000));
    }
    QCOMPARE(currentSourceChanged.size(), 1);
    QCOMPARE(finished.size(), 0);
    QVERIFY(m_media->queue().isEmpty());
    stopPlayback(m_media->state());
}

void tst_MediaObject::testPauseOnFinish()
{
    startPlayback();
    QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
    QCOMPARE(m_media->state(), Phonon::PlayingState);
    if (m_media->isSeekable() && m_media->totalTime() > 2000)
        m_media->seek(m_media->totalTime() - 2000);
    QTest::waitForSignal(m_media, SIGNAL(finished()), 4000);
    QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);

    QCOMPARE(m_media->state(), Phonon::PausedState);
    connect(m_media, SIGNAL(finished()), m_media, SLOT(stop()));
    m_media->seek(m_media->totalTime() - 2000);
    m_media->play();

    QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
    QCOMPARE(m_media->state(), Phonon::PlayingState);
    QTest::waitForSignal(m_media, SIGNAL(finished()), 4000);
    QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 1000);
    stopPlayback(Phonon::StoppedState);
}

void tst_MediaObject::testReconnectBetweenTwoMediaObjects(){
    // Purpose: Test that phonon can handle swithing the same sink
    // between different media objects.

    Phonon::MediaObject obj1;
    Phonon::MediaObject obj2;
    Phonon::AudioOutput audio1;
    Phonon::Path p1 = Phonon::createPath(&obj1, &audio1);
    QVERIFY(p1.isValid());

    QVERIFY(p1.reconnect(&obj1, &audio1));
    QVERIFY(p1.isValid());
    QVERIFY(p1.reconnect(&obj2, &audio1));
    QVERIFY(p1.isValid());
    QVERIFY(p1.reconnect(&obj1, &audio1));
    QVERIFY(p1.isValid());

    // Repeat the same test while playing:
    QFile file(MEDIA_FILEPATH);
    obj1.setCurrentSource(&file);
    QFile file2(MEDIA_FILEPATH);
    obj2.setCurrentSource(&file2);
    obj1.play();
    obj2.play();
    
    QVERIFY(p1.reconnect(&obj1, &audio1));
    QVERIFY(p1.isValid());
    QVERIFY(p1.reconnect(&obj2, &audio1));
    QVERIFY(p1.isValid());
    QVERIFY(p1.reconnect(&obj1, &audio1));
    QVERIFY(p1.isValid());
}

void tst_MediaObject::testPlayOnFinish()
{
    connect(m_media, SIGNAL(finished()), SLOT(setMediaAndPlay()));
    startPlayback();
    if (m_media->isSeekable()) {
        m_media->seek(m_media->totalTime() - SEEK_BACKWARDS);
        QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 6000));
    } else {
        QVERIFY(QTest::waitForSignal(this, SIGNAL(continueTestPlayOnFinish()), 3000 + m_media->remainingTime()));
    }
    QTest::waitForSignal(m_media, SIGNAL(finished()), 1000);
    stopPlayback(m_media->state());
}

void tst_MediaObject::testTickSignal()
{
    QTime start1;
    QTime start2;
#ifdef Q_OS_WINCE //On Windows CE we only provide ticks above 400ms
    for (qint32 tickInterval = 400; tickInterval <= 1000; tickInterval *= 2)
#else
    for (qint32 tickInterval = 80; tickInterval <= 500; tickInterval *= 2)
#endif
    {
        QSignalSpy tickSpy(m_media, SIGNAL(tick(qint64)));
        //qDebug() << "Test 20 ticks with an interval of" <<  tickInterval << "ms";
        m_media->setTickInterval(tickInterval);
        QVERIFY(m_media->tickInterval() <= tickInterval);
        QVERIFY(m_media->tickInterval() >= tickInterval/2);
        QVERIFY(tickSpy.isEmpty());
        m_media->seek(0); //let's go back to the beginning
        start1.start();
        startPlayback();
        start2.start();
        int lastCount = 0;
        qint64 s1, s2 = start2.elapsed();
        while (tickSpy.count() < 20 && (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState))
        {
            if (tickSpy.count() > lastCount)
            {
                s1 = start1.elapsed();
                qint64 tickTime = castQVariantToInt64(tickSpy.last().at(0));
                lastCount = tickSpy.count();
                // s1 is the time from before the beginning of the playback to
                // after the tick signal
                // s2 is the time from after the beginning of the playback to
                // before the tick signal
                // so: s2 <= s1
                QVERIFY(tickTime <= m_media->currentTime());
                if (s1 + ALLOWED_TICK_INACCURACY < tickTime || s2 - ALLOWED_TICK_INACCURACY > tickTime) {
                    qDebug()
                        << "\n" << lastCount << "ticks have been received"
                        << "\ntime from before playback was started to after the tick signal was received:" << s1 << "ms"
                        << "\ntime from after playback was started to before the tick signal was received:" << s2 << "ms"
                        << "\nreported tick time:" << tickTime << "ms"
                        << "\nallowed inaccuracy: +/-" << ALLOWED_TICK_INACCURACY << "ms";
                    for (int i = 0; i < tickSpy.count(); ++i) {
                        qDebug() << castQVariantToInt64(tickSpy[i].at(0));
                    }
                }
                QVERIFY(s1 + ALLOWED_TICK_INACCURACY >= tickTime);
                QVERIFY(s2 - ALLOWED_TICK_INACCURACY <= tickTime);
#ifndef Q_OS_WINCE
                QVERIFY(s1 >= lastCount * m_media->tickInterval());
#else
                QVERIFY(s1 >= lastCount * m_media->tickInterval() - ALLOWED_TICK_INACCURACY);
#endif
                if (s2 > (lastCount + 1) * m_media->tickInterval())
                    QWARN(qPrintable(QString("%1. tick came too late: %2ms elapsed while this tick should have come before %3ms")
                            .arg(lastCount).arg(s2).arg((lastCount + 1) * m_media->tickInterval())));
            } else if (lastCount == 0 && s2 > 20 * m_media->tickInterval()) {
                QFAIL("no tick signals are being received");
            }
            s2 = start2.elapsed();
            QTest::waitForSignal(m_media, SIGNAL(tick(qint64)), 2000);
        }
#ifndef Q_OS_WINCE //the shorter wave file is finished on Windows CE...
        stopPlayback(Phonon::PlayingState);
#else
        stopPlayback(m_media->state());
#endif
    }
}

void tst_MediaObject::testSeek()
{
    m_media->seek(0); // let's seek back to the beginning
    startPlayback();
    QTime timer;
    timer.start();
    qint64 t = m_media->totalTime();
    qint64 c = m_media->currentTime();
    qint64 r = m_media->remainingTime();
    int elapsed = timer.elapsed();
    if (c + r > t + elapsed || c + r < t - elapsed) {
    //  qDebug() << "currentTime:" << c
    //      << "remainingTime:" << r
    //      << "totalTime:" << t;
        QFAIL("currentTime + remainingTime doesn't come close enough to totalTime");
    }

    QVERIFY(c + r <= t + elapsed);
    QVERIFY(c + r >= t - elapsed);
    if (m_media->isSeekable())
        if (r > 0)
        {
            m_media->setTickInterval(20);
            qint64 s = c + r / 2;
            testOneSeek(s);

            s /= 2;
            testOneSeek(s);
            s = s * 3 / 2;
            testOneSeek(s);

            pausePlayback();

            s = s * 3 / 2;
            testOneSeek(s);
            s /= 2;
            testOneSeek(s);

            m_media->setTickInterval(0);


            stopPlayback(Phonon::PausedState);
            return;
        }
        else
            QWARN("didn't test seeking as the MediaObject reported a remaining size <= 0");
    else
        QWARN("didn't test seeking as the MediaObject is not seekable");
    stopPlayback(Phonon::PlayingState);
}


void tst_MediaObject::setMediaAndPlay()
{
    m_stateChangedSignalSpy->clear();
    QCOMPARE(m_stateChangedSignalSpy->count(), 0);

    QSignalSpy totalTimeChangedSignalSpy(m_media, SIGNAL(totalTimeChanged(qint64)));
    QVERIFY(m_media->currentSource().type() != MediaSource::Invalid);
    Phonon::State state = m_media->state();
    QVERIFY(state == Phonon::StoppedState || state == Phonon::PlayingState || Phonon::PausedState);
    m_media->setCurrentSource(m_url);
    // before calling play() we better make sure that if play() finishes very fast that we don't get
    // called again
    disconnect(m_media, SIGNAL(finished()), this, SLOT(setMediaAndPlay()));
    state = m_media->state();
    startPlayback2(state);

    emit continueTestPlayOnFinish();
}

void tst_MediaObject::testPlayBeforeFinish()
{
    startPlayback();
    QCOMPARE(m_stateChangedSignalSpy->size(), 0);
    Phonon::State state = m_media->state();
    QCOMPARE(state, Phonon::PlayingState);
    m_media->setCurrentSource(m_url);
    m_media->play();
    if (m_stateChangedSignalSpy->isEmpty()) {
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
    }
    // first (optional) state to reach is StoppedState
    QList<QVariant> args = m_stateChangedSignalSpy->takeFirst();
    Phonon::State oldstate = qvariant_cast<Phonon::State>(args.at(1));
    QCOMPARE(oldstate, state);
    state = qvariant_cast<Phonon::State>(args.at(0));
    if (state == Phonon::StoppedState) {
        if (m_stateChangedSignalSpy->isEmpty()) {
            QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
        }
        args = m_stateChangedSignalSpy->takeFirst();
        oldstate = qvariant_cast<Phonon::State>(args.at(1));
        QCOMPARE(oldstate, state);
        state = qvariant_cast<Phonon::State>(args.at(0));
    }
    // next LoadingState
    QCOMPARE(state, Phonon::LoadingState);
    if (m_stateChangedSignalSpy->isEmpty()) {
        QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000));
    }
    // next either BufferingState or PlayingState
    args = m_stateChangedSignalSpy->takeFirst();
    oldstate = qvariant_cast<Phonon::State>(args.at(1));
    QCOMPARE(oldstate, state);
    state = qvariant_cast<Phonon::State>(args.at(0));
    if (state == Phonon::BufferingState) {
        if (m_stateChangedSignalSpy->isEmpty()) {
            QVERIFY(QTest::waitForSignal(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), 4000)); // buffering can take a while
        }
        args = m_stateChangedSignalSpy->takeFirst();
        oldstate = qvariant_cast<Phonon::State>(args.at(1));
        QCOMPARE(oldstate, state);
        state = qvariant_cast<Phonon::State>(args.at(0));
    }
#ifdef Q_WS_MAC
    // m_media->setCurrentSource(m_url) in phonon frontend will always call
    // 'stop' on the backend before calling 'setSource'. So the QT7 backend
    // goes into stop, and naturally remains there after setting the new source.
    // So going into playing state cannot happend when the backend is synchronized.
    // Thats the reason for the ifdef.
    QCOMPARE(state, Phonon::StoppedState);
#else
    stopPlayback(Phonon::PlayingState);
#endif
}

void tst_MediaObject::cleanupTestCase()
{
    if (m_stateChangedSignalSpy)
      delete m_stateChangedSignalSpy;
    if (m_media)
      delete m_media;
#ifdef Q_OS_WINCE
    QTest::qWait(200);
#endif
    if (!m_tmpFileName.isNull()) {
        QVERIFY(QFile::remove(m_tmpFileName));
    }
}

void tst_MediaObject::_testOneSeek(qint64 seekTo)
{
   qint64 t = m_media->totalTime();
    qint64 oldTime = m_media->currentTime();
    if (oldTime == seekTo) {
        return;
    }

    QTime seekDuration;
    seekDuration.start();
    m_media->seek(seekTo);

    QVERIFY(oldTime == 0 || seekTo == 0 || m_media->currentTime() != 0);

    int bufferingTime = 0;
    Phonon::State s = m_media->state();
    QTime timer;
    if (s == Phonon::BufferingState) {
        timer.start();
    }
    QEventLoop loop;
    connect(m_media, SIGNAL(tick(qint64)), &loop, SLOT(quit()));
    connect(m_media, SIGNAL(stateChanged(Phonon::State,Phonon::State)), &loop, SLOT(quit()));

    qint64 c = m_media->currentTime();
    qint64 r = m_media->remainingTime();
    int elapsed = 0;
    while (qAbs(c - seekTo) > ALLOWED_SEEK_INACCURACY){
        QTimer::singleShot(ALLOWED_TIME_FOR_SEEKING, &loop, SLOT(quit()));

        loop.exec();
        c = m_media->currentTime();
        r = m_media->remainingTime();
        if (s == Phonon::BufferingState) {
            bufferingTime += timer.restart();
        } else {
            timer.start();
        }
        s = m_media->state();
        elapsed = seekDuration.elapsed();
        QVERIFY(elapsed - bufferingTime < (ALLOWED_TIME_FOR_SEEKING + SEEKING_TOLERANCE));
    }

    QVERIFY(c >= seekTo - ALLOWED_SEEK_INACCURACY);
    if (s == Phonon::PausedState) {
        QVERIFY(bufferingTime == 0);
        elapsed = 0;
    }
    if (c > seekTo + ALLOWED_SEEK_INACCURACY + elapsed - bufferingTime) {
        QFAIL("currentTime is greater than the requested time + the time that elapsed since the seek started.");
    }
    if (c + r > t + 200 || c + r < t - 200) {
        QFAIL("currentTime + remainingTime doesn't come close enough to totalTime");
    }
    m_success = true;
}

void tst_MediaObject::volumeSliderMuteVisibility()
{
    //this test doesn't really belong to mediaobject
    // ### see if we should create a realy Phonon::VolumeSlider autotest
    Phonon::VolumeSlider slider;
    QVERIFY(slider.isMuteVisible()); // that is the default value
    slider.setMuteVisible(true);
    QVERIFY(slider.isMuteVisible());

    //let's check that changing the visibility of the slider itself
    //doesn't change what the slider reports
    slider.setVisible(false);
    QVERIFY(slider.isMuteVisible());
    slider.setVisible(true);

    slider.setMuteVisible(false);
    QVERIFY(!slider.isMuteVisible());
    slider.setMuteVisible(true);
    QVERIFY(slider.isMuteVisible());
}


#endif //QT_NO_PHONON


QTEST_MAIN(tst_MediaObject)

#include "tst_mediaobject.moc"
// vim: sw=4 ts=4
