/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the Qt Mobility Components.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "s60mediaplayersession.h"

#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/qvariant.h>
#include <QtCore/qtimer.h>
#include <mmf/common/mmferrors.h>
#include <qmediatimerange.h>

S60MediaPlayerSession::S60MediaPlayerSession(QObject *parent)
    : QObject(parent)
    , m_playbackRate(0)
    , m_muted(false)
    , m_volume(0)
    , m_state(QMediaPlayer::StoppedState)
    , m_mediaStatus(QMediaPlayer::UnknownMediaStatus)
    , m_progressTimer(new QTimer(this))
    , m_stalledTimer(new QTimer(this))
    , m_error(KErrNone)
    , m_play_requested(false)
    , m_stream(false)
{    
    connect(m_progressTimer, SIGNAL(timeout()), this, SLOT(tick()));
    connect(m_stalledTimer, SIGNAL(timeout()), this, SLOT(stalled()));
}

S60MediaPlayerSession::~S60MediaPlayerSession()
{
}

int S60MediaPlayerSession::volume() const
{
    return m_volume;
}

void S60MediaPlayerSession::setVolume(int volume)
{
    if (m_volume == volume)
        return;
    
    m_volume = volume;
    // Dont set symbian players volume until media loaded.
    // Leaves with KerrNotReady although documentation says otherwise.
    if (!m_muted && 
        (  mediaStatus() == QMediaPlayer::LoadedMedia 
        || mediaStatus() == QMediaPlayer::StalledMedia 
        || mediaStatus() == QMediaPlayer::BufferingMedia
        || mediaStatus() == QMediaPlayer::BufferedMedia
        || mediaStatus() == QMediaPlayer::EndOfMedia)) {
        TRAPD(err, doSetVolumeL(m_volume));
        setError(err);
    }
}

bool S60MediaPlayerSession::isMuted() const
{
    return m_muted;
}

bool S60MediaPlayerSession::isSeekable() const
{
    return (m_stream)?false:true;
}

void S60MediaPlayerSession::setMediaStatus(QMediaPlayer::MediaStatus status)
{
    if (m_mediaStatus == status)
        return;
    
    m_mediaStatus = status;
    
    emit mediaStatusChanged(m_mediaStatus);
    
    if (m_play_requested)
        play();
}

void S60MediaPlayerSession::setState(QMediaPlayer::State state)
{
    if (m_state == state)
        return;
    
    m_state = state;
    emit stateChanged(m_state);
}

QMediaPlayer::State S60MediaPlayerSession::state() const 
{ 
    return m_state; 
}

QMediaPlayer::MediaStatus S60MediaPlayerSession::mediaStatus() const 
{ 
    return m_mediaStatus; 
}

void S60MediaPlayerSession::load(QUrl url)
{
    setMediaStatus(QMediaPlayer::LoadingMedia);
    startStalledTimer();
    m_stream = (url.scheme() == "file")?false:true;
    TRAPD(err,
        if(m_stream)
            doLoadUrlL(QString2TPtrC(url.toString()));
        else
            doLoadL(QString2TPtrC(QDir::toNativeSeparators(url.toLocalFile()))));    
    setError(err);    
}

void S60MediaPlayerSession::play()
{
    if (state() == QMediaPlayer::PlayingState
        || mediaStatus() == QMediaPlayer::UnknownMediaStatus
        || mediaStatus() == QMediaPlayer::NoMedia
        || mediaStatus() == QMediaPlayer::InvalidMedia)
        return;
    
    if (mediaStatus() == QMediaPlayer::LoadingMedia) {
        m_play_requested = true;
        return;
    }
    
    m_play_requested = false;
    setState(QMediaPlayer::PlayingState);
    startProgressTimer();
    doPlay();
}

void S60MediaPlayerSession::pause()
{
    if (mediaStatus() == QMediaPlayer::NoMedia ||
        mediaStatus() == QMediaPlayer::InvalidMedia)
        return;    
    
    setState(QMediaPlayer::PausedState);
    stopProgressTimer();    
    TRAP_IGNORE(doPauseL());
}

void S60MediaPlayerSession::stop()
{
    m_play_requested = false;
    setState(QMediaPlayer::StoppedState);
    if (mediaStatus() == QMediaPlayer::BufferingMedia ||
        mediaStatus() == QMediaPlayer::BufferedMedia)
        setMediaStatus(QMediaPlayer::LoadedMedia);
    if (mediaStatus() == QMediaPlayer::LoadingMedia)
        setMediaStatus(QMediaPlayer::UnknownMediaStatus);    
    stopProgressTimer();
    stopStalledTimer();
    doStop();
    emit positionChanged(0);
}
void S60MediaPlayerSession::reset()
{
    m_play_requested = false;    
    setError(KErrNone, QString(), true);
    stopProgressTimer();
    stopStalledTimer();
    doStop();
    setState(QMediaPlayer::StoppedState);
    setMediaStatus(QMediaPlayer::UnknownMediaStatus);
}

void S60MediaPlayerSession::setVideoRenderer(QObject *renderer)
{
    Q_UNUSED(renderer);
}

int S60MediaPlayerSession::bufferStatus()
{
    if(   mediaStatus() == QMediaPlayer::LoadingMedia
       || mediaStatus() == QMediaPlayer::UnknownMediaStatus
       || mediaStatus() == QMediaPlayer::NoMedia
       || mediaStatus() == QMediaPlayer::InvalidMedia)
        return 0;
    
    int progress = 0;
    TRAPD(err, progress = doGetBufferStatusL());
    
    // If buffer status query not supported by codec return 100
    // do not set error
    if(err == KErrNotSupported)
        return 100;
    
    setError(err);
    return progress;
}

bool S60MediaPlayerSession::isMetadataAvailable() const
{
    return !m_metaDataMap.isEmpty();    
}

QVariant S60MediaPlayerSession::metaData(const QString &key) const
{
    return m_metaDataMap.value(key);    
}

QMap<QString, QVariant> S60MediaPlayerSession::availableMetaData() const
{
    return m_metaDataMap;
}

void S60MediaPlayerSession::setMuted(bool muted)
{
    m_muted = muted;
    
    if(   m_mediaStatus == QMediaPlayer::LoadedMedia 
       || m_mediaStatus == QMediaPlayer::StalledMedia 
       || m_mediaStatus == QMediaPlayer::BufferingMedia
       || m_mediaStatus == QMediaPlayer::BufferedMedia
       || m_mediaStatus == QMediaPlayer::EndOfMedia) {
        TRAPD(err, doSetVolumeL((m_muted)?0:m_volume));
        setError(err);
    }
}

qint64 S60MediaPlayerSession::duration() const
{
    if(   mediaStatus() == QMediaPlayer::LoadingMedia
       || mediaStatus() == QMediaPlayer::UnknownMediaStatus
       || mediaStatus() == QMediaPlayer::NoMedia
       || mediaStatus() == QMediaPlayer::InvalidMedia)
        return -1;
    
    qint64 pos = 0;
    TRAP_IGNORE(pos = doGetDurationL());
    return pos;
}

qint64 S60MediaPlayerSession::position() const
{
    if(   mediaStatus() == QMediaPlayer::LoadingMedia
       || mediaStatus() == QMediaPlayer::UnknownMediaStatus
       || mediaStatus() == QMediaPlayer::NoMedia
       || mediaStatus() == QMediaPlayer::InvalidMedia)
        return 0;
    
    qint64 pos = 0;
    TRAP_IGNORE(pos = doGetPositionL());
    return pos;
}

void S60MediaPlayerSession::setPosition(qint64 pos)
{
    if (position() == pos)
        return;
    
    if (state() == QMediaPlayer::PlayingState) 
        pause();

    TRAPD(err, doSetPositionL(pos * 1000));
    setError(err);

    if (state() == QMediaPlayer::PausedState)
        play();

    emit positionChanged(position());
}

void S60MediaPlayerSession::setAudioEndpoint(const QString& audioEndpoint)
{
    doSetAudioEndpoint(audioEndpoint);
}

void S60MediaPlayerSession::loaded()
{
    stopStalledTimer();
    if (m_error == KErrNone || m_error == KErrMMPartialPlayback) {
        setMediaStatus(QMediaPlayer::LoadedMedia);
        TRAPD(err, updateMetaDataEntriesL());
        setError(err);
        setVolume(m_volume);
        setMuted(m_muted);
        emit durationChanged(duration());
        emit videoAvailableChanged(isVideoAvailable());
        emit audioAvailableChanged(isAudioAvailable());
    }
}

void S60MediaPlayerSession::endOfMedia()
{
    setMediaStatus(QMediaPlayer::EndOfMedia);
    setState(QMediaPlayer::StoppedState);
    emit positionChanged(0);
}

void S60MediaPlayerSession::buffering()
{
    startStalledTimer();
    setMediaStatus(QMediaPlayer::BufferingMedia);
}

void S60MediaPlayerSession::buffered()
{
    stopStalledTimer();
    setMediaStatus(QMediaPlayer::BufferedMedia);
}
void S60MediaPlayerSession::stalled()
{
    setMediaStatus(QMediaPlayer::StalledMedia);
}

QMap<QString, QVariant>& S60MediaPlayerSession::metaDataEntries()
{
    return m_metaDataMap;
}

QMediaPlayer::Error S60MediaPlayerSession::fromSymbianErrorToMultimediaError(int error)
{
    switch(error) {
        case KErrNoMemory:
        case KErrNotFound:
        case KErrBadHandle:
        case KErrAbort:
        case KErrNotSupported:
        case KErrCorrupt:
        case KErrGeneral:
        case KErrArgument:
        case KErrPathNotFound:
        case KErrDied:
        case KErrServerTerminated:
        case KErrServerBusy:
        case KErrCompletion:  
        case KErrBadPower:    
            return QMediaPlayer::ResourceError;
        
        case KErrMMPartialPlayback:   
            return QMediaPlayer::FormatError;

        case KErrMMAudioDevice:
        case KErrMMVideoDevice:
        case KErrMMDecoder:
        case KErrUnknown:    
            return QMediaPlayer::ServiceMissingError;
            
        case KErrMMNotEnoughBandwidth:
        case KErrMMSocketServiceNotFound:
        case KErrMMNetworkRead:
        case KErrMMNetworkWrite:
        case KErrMMServerSocket:
        case KErrMMServerNotSupported:
        case KErrMMUDPReceive:
        case KErrMMInvalidProtocol:
        case KErrMMInvalidURL:
        case KErrMMMulticast:
        case KErrMMProxyServer:
        case KErrMMProxyServerNotSupported:
        case KErrMMProxyServerConnect:
            return QMediaPlayer::NetworkError;

        case KErrNotReady:
        case KErrInUse:
        case KErrAccessDenied:
        case KErrLocked:
        case KErrMMDRMNotAuthorized:
        case KErrPermissionDenied:
        case KErrCancel:
        case KErrAlreadyExists:
            return QMediaPlayer::AccessDeniedError;

        case KErrNone:
        default:
            return QMediaPlayer::NoError;
    }
}

void S60MediaPlayerSession::setError(int error, const QString &errorString, bool forceReset)
{
    if( forceReset ) {
        m_error = KErrNone;
        emit this->error(QMediaPlayer::NoError, QString());
        return;
    }

    // If error does not change and m_error is reseted without forceReset flag
    if (error == m_error || 
        (m_error != KErrNone && error == KErrNone))
        return;
    
    m_error = error;
    QMediaPlayer::Error mediaError = fromSymbianErrorToMultimediaError(m_error);
    QString symbianError = QString(errorString);

    if (mediaError != QMediaPlayer::NoError) {
        // TODO: fix to user friendly string at some point
        // These error string are only dev usable
        symbianError.append("Symbian:");
        symbianError.append(QString::number(m_error));
    }
     
    emit this->error(mediaError, symbianError);
    
    switch(mediaError){
        case QMediaPlayer::ResourceError:
        case QMediaPlayer::NetworkError:
        case QMediaPlayer::AccessDeniedError:
        case QMediaPlayer::ServiceMissingError:
            m_play_requested = false;
            setMediaStatus(QMediaPlayer::InvalidMedia);
            stop();
            break;
    }
}

void S60MediaPlayerSession::tick()
{
    emit positionChanged(position());

    if (bufferStatus() < 100)
        emit bufferStatusChanged(bufferStatus());
}

void S60MediaPlayerSession::startProgressTimer()
{
    m_progressTimer->start(500);
}

void S60MediaPlayerSession::stopProgressTimer()
{
    m_progressTimer->stop();
}

void S60MediaPlayerSession::startStalledTimer()
{
    m_stalledTimer->start(30000);
}

void S60MediaPlayerSession::stopStalledTimer()
{
    m_stalledTimer->stop();
}
QString S60MediaPlayerSession::TDesC2QString(const TDesC& aDescriptor)
{
    return QString::fromUtf16(aDescriptor.Ptr(), aDescriptor.Length());
}
TPtrC S60MediaPlayerSession::QString2TPtrC( const QString& string )
{
	// Returned TPtrC is valid as long as the given parameter is valid and unmodified
    return TPtrC16(static_cast<const TUint16*>(string.utf16()), string.length());
}
QRect S60MediaPlayerSession::TRect2QRect(const TRect& tr)
{
    return QRect(tr.iTl.iX, tr.iTl.iY, tr.Width(), tr.Height());
}
TRect S60MediaPlayerSession::QRect2TRect(const QRect& qr)
{
    return TRect(TPoint(qr.left(), qr.top()), TSize(qr.width(), qr.height()));
}
