
#ifdef ENABLE_PHONON

#include "engine_phonon.h"
#include "utilities.h"
#include "settings.h"
#include "debug.h"

#include <cmath>

#include <QFileInfo>
#include <QList>
#include <QtPlugin>

#include <phonon/backendcapabilities.h>
#include <phonon/effect.h>
#include <phonon/effectparameter.h>


EnginePhonon::EnginePhonon() : EngineBase("phonon") {
    m_type = ENGINE::PHONON;

    m_mediaObject = new Phonon::MediaObject(this);
    m_audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);

    /* ----- by default tick every 1 second ----- */
    m_mediaObject->setTickInterval(100);
    Debug::debug() << "[EnginePhonon] -> tick Interval (actual): " << m_mediaObject->tickInterval();


    /* ----- get the next track when there is 2 seconds left on the current one ----- */
    /*       in case of playing track from track view */
    m_mediaObject->setPrefinishMark( 2000 );
    m_mediaObject->setTransitionTime(100);  /* GAPLESS/CROSSFADE */

    connect(m_mediaObject,SIGNAL(finished()),this,SLOT(slot_on_media_finished()));
    connect(m_mediaObject,SIGNAL(aboutToFinish()),this,SLOT(slot_on_media_about_to_finish()));

    connect(m_mediaObject,SIGNAL(stateChanged(Phonon::State,Phonon::State)),this,SLOT(slot_on_phonon_state_changed(Phonon::State,Phonon::State)),Qt::DirectConnection);

    connect(m_mediaObject,SIGNAL(tick(qint64)),this,SLOT(slot_on_time_change(qint64)));
    connect(m_mediaObject,SIGNAL(totalTimeChanged(qint64)),this,SLOT(slot_on_duration_change(qint64)));

    connect(m_mediaObject,SIGNAL(currentSourceChanged( const Phonon::MediaSource & )),this,SLOT(slot_on_media_change()));

    connect(m_mediaObject, SIGNAL(metaDataChanged()), this,SLOT(slot_on_metadata_change()));

    /* needed for Mplayer2 backend */
    connect( m_audioOutput, SIGNAL( volumeChanged( qreal ) ),this, SIGNAL( volumeChanged() ) );
    connect( m_audioOutput, SIGNAL( mutedChanged( bool ) ), this,  SIGNAL( muteStateChanged() ) );

    m_phononPath  = Phonon::createPath(m_mediaObject,m_audioOutput);

    /* ----- only create pre-amp if we have replaygain on, VolumeFaderEffect can cause phonon issues */
    m_preamp = 0;
    if( SETTINGS()->_replaygain != SETTING::ReplayGainOff )
    {
      m_preamp = new Phonon::VolumeFaderEffect( this );
      m_phononPath.insertEffect( m_preamp );
    }

    /* ----- add an equalizer effect if available */
    m_equalizer   = 0;
    QList<Phonon::EffectDescription> mEffectDescriptions = Phonon::BackendCapabilities::availableAudioEffects();
    foreach ( const Phonon::EffectDescription &mDescr, mEffectDescriptions )
    {
      if ( mDescr.name() == QLatin1String( "KEqualizer" ) )
      {
          m_equalizer = new Phonon::Effect( mDescr, this );

          if( SETTINGS()->_enableEq )
          {
              addEqualizer();
              loadEqualizerSettings();
          }
      }
    }

    /* ----- initial volume setup ----- */
    int restoredVolume = SETTINGS()->_volumeLevel > this->maxVolume() ? this->maxVolume() : SETTINGS()->_volumeLevel;
    setVolume( restoredVolume );

    m_current_state    = ENGINE::STOPPED;
    m_old_state        = ENGINE::STOPPED;

    m_version = QString("4Qt6");
}

EnginePhonon::~EnginePhonon() {
    m_mediaObject->stop();
    delete m_mediaObject;
    delete m_audioOutput;
}

void EnginePhonon::play() {
    m_mediaObject->play();
}

void EnginePhonon::pause() {
    m_mediaObject->pause();
}

void EnginePhonon::stop() {
   Debug::debug() << "[EnginePhonon] -> stop";

   m_mediaObject->blockSignals(true);
   m_mediaObject->stop();
   m_mediaObject->clearQueue();
   m_mediaObject->blockSignals(false);

   EngineBase::stop();
}


void EnginePhonon::setMediaItem(MEDIA::TrackPtr track) {
    Debug::debug() << "[EnginePhonon] -> setMediaItem";

    m_mediaObject->blockSignals(true);
    m_mediaObject->stop();
    m_mediaObject->clearQueue();
    m_mediaObject->blockSignals(false);

    if(m_currentMediaItem)
    {
      MEDIA::registerTrackPlaying(m_currentMediaItem, false);
    }

    m_currentMediaItem = MEDIA::TrackPtr(track);
    //Debug::debug() << "[EnginePhonon] -> setMediaItem mi url:" << mi->data.url;

    if(m_nextMediaItem)
    {
      m_nextMediaItem    = MEDIA::TrackPtr(0);
    }

    /*  BEGIN crash on broken path with vlc backend*/
    if( m_currentMediaItem->isBroken )
    {
      Debug::warning() << "[EnginePhonon] Track path seems to be broken:" << m_currentMediaItem->url;
      m_mediaObject->blockSignals(false);
      //stop(); no stop FOR VLC BACKEND
      return;
    }
    /* END */


    /* get replay gain info */
    if ( (m_currentMediaItem->type() == TYPE_TRACK) &&
         (SETTINGS()->_replaygain != SETTING::ReplayGainOff ) )
    {
        MEDIA::ReplayGainFromDataBase(m_currentMediaItem);
    }

    //const QString path = MEDIA::Track::path(track->url);
    if( MEDIA::isLocal(track->url) )
      m_mediaObject->setCurrentSource( QUrl::fromLocalFile(QFileInfo(track->url).canonicalFilePath()) );
    else
      m_mediaObject->setCurrentSource( QUrl(track->url) );


    m_mediaObject->play();
}

void EnginePhonon::setNextMediaItem(MEDIA::TrackPtr track) {
    //Debug::debug() << "[EnginePhonon] ->setNextMediaItem";
    m_nextMediaItem = MEDIA::TrackPtr(track);

    if( MEDIA::isLocal(track->url) )
      m_mediaObject->enqueue( QUrl::fromLocalFile(QFileInfo(track->url).canonicalFilePath()) );
    else
      m_mediaObject->enqueue( QUrl(track->url) );

    /* // DEBUG check queue
    foreach (Phonon::MediaSource source, m_mediaObject->queue() )
      Debug::debug() << "[EnginePhonon] -> #queue -> url :" << source.url() ;*/

    /* get replay gain info */
    if ( (m_nextMediaItem->type() == TYPE_TRACK) &&
         (SETTINGS()->_replaygain != SETTING::ReplayGainOff ) )
    {
        MEDIA::ReplayGainFromDataBase(m_nextMediaItem);
    }
}

int EnginePhonon::volume() const {
    /* return volume in percent */
    int volume_in_percent = m_audioOutput->volume()*100;
    volume_in_percent = qBound<qreal>( 0, volume_in_percent, 100 );

    return volume_in_percent;
}

void EnginePhonon::setVolume(const int& percent) {
    Debug::debug() << "[EnginePhonon] -> set percent : " << percent;
    int vp = qBound<qreal>( 0, percent, 100 );

    const qreal volume =  vp / 100.0;

    if ( m_audioOutput->volume() != volume )
    {
      m_audioOutput->setVolume( volume );
      emit volumeChanged();
    }
}

bool EnginePhonon:: isMuted() const {
    return m_audioOutput->isMuted();
}


void EnginePhonon::setMuted( bool mute ) {
    bool ismuted = m_audioOutput->isMuted();

    if(mute != ismuted) {
      m_audioOutput->setMuted( mute );
      emit muteStateChanged();
    }
}

void EnginePhonon::volumeMute( ) {
    //Debug::debug() << "[EnginePhonon] -> volumeMute";
    setMuted( !isMuted() );
};

void EnginePhonon::volumeInc( ) {
    int percent = volume() < 100 ? volume() + 1 : 100;
    setVolume(percent);
};

void EnginePhonon::volumeDec( ) {
    int percent = volume() > 0 ? volume() -1 : 0;
    setVolume(percent);
};

void EnginePhonon::slot_on_phonon_state_changed(Phonon::State newState, Phonon::State oldState) {
    //Debug::warning() << "[EnginePhonon] -> slot_on_phonon_state_changed : " << newState ;
    if(newState == oldState)
      return;

    switch (newState) {
      case Phonon::LoadingState:  m_current_state = ENGINE::STOPPED; break;

      case Phonon::StoppedState:  m_current_state = ENGINE::STOPPED; break;

      case Phonon::PlayingState:  m_current_state = ENGINE::PLAYING; break;

      case Phonon::BufferingState:  break;

      case Phonon::PausedState:   m_current_state = ENGINE::PAUSED;  break;

      case Phonon::ErrorState:    m_current_state = ENGINE::ERROR;   break;

      default:break;
    }

    if(m_old_state != m_current_state)
    {
      if(m_current_state != ENGINE::STOPPED)
      {
        //Debug::debug() << "[EnginePhonon] -> engineStateChanged :" << stateToString(m_current_state);
        emit engineStateChanged();
      }
      else if(!m_currentMediaItem)
      {
        //Debug::debug() << "[EnginePhonon] -> engineStateChanged :" << stateToString(m_current_state);
        emit engineStateChanged();
      }

      m_old_state = m_current_state;
    }
}

void  EnginePhonon::slot_on_duration_change(qint64 total_time_ms) {
Q_UNUSED(total_time_ms)
    //Debug::debug() << "[EnginePhonon] -> slot_on_duration_change" ;

    /* As Amarok note: don't rely on m_currentTrack here. At least some Phonon backends first emit
       totalTimeChanged(), then metaDataChanged() and only then currentSourceChanged()
      which currently sets correct m_currentTrack. */
    if(m_nextMediaItem) {
       /* totalTimeChanged has been sent before currentSourceChanged */
       return;
    }

    update_total_time();
}

void EnginePhonon::update_total_time() {
    if(m_currentMediaItem && m_currentMediaItem->type() == TYPE_TRACK)
    {
      if( m_currentMediaItem->duration > 0 )
      {
        m_totalTime  = m_currentMediaItem->duration * 1000;
      }
      else
      {
        m_totalTime  = m_mediaObject->totalTime();
      }
    }
    else
    {
      m_totalTime  = m_mediaObject->totalTime();
    }

    emit mediaTotalTimeChanged(m_totalTime);
}

static const qreal log10over20 = 0.1151292546497022842; // ln(10) / 20

void EnginePhonon::slot_on_media_change() {
    Debug::debug() << "[EnginePhonon] -> slot_on_media_change";

    if( m_nextMediaItem )
    {
      Debug::debug() << "[EnginePhonon] -> slot_on_media_change: next media item from queue";
      MEDIA::registerTrackPlaying(m_currentMediaItem, false);
      m_currentMediaItem = MEDIA::TrackPtr(m_nextMediaItem);

      m_nextMediaItem    = MEDIA::TrackPtr(0);
    }

    if(!m_currentMediaItem)
    {
        Debug::error() << "[EnginePhonon] -> no media set";
        // note phonon gestreamer need a sto pin this case
        // don't remember if it's a probleme for phonon vlc
        stop();
        return;
    }
    else if ( (m_currentMediaItem->type() == TYPE_TRACK) &&
              (SETTINGS()->_replaygain != SETTING::ReplayGainOff ) )
    {
        if( !m_preamp )  {
            m_preamp = new Phonon::VolumeFaderEffect( this );
            m_phononPath.insertEffect( m_preamp );
        }

        qreal gain, peak;
        if ( SETTINGS()->_replaygain != SETTING::AlbumReplayGain ) {
          gain =  m_currentMediaItem->trackGain;
          peak =  m_currentMediaItem->trackPeak;
        }
        else {
          gain =  m_currentMediaItem->albumGain;
          peak =  m_currentMediaItem->albumPeak;
        }

        if ( gain + peak > 0.0 )
        {
            Debug::debug() << "[EnginePhonon] -> slot_on_media_change Gain of" << gain << "would clip at absolute peak of" << gain + peak;
            gain -= gain + peak;
        }
        Debug::debug() << "[EnginePhonon] -> slot_on_media_change Using gain of" << gain << "with relative peak of" << peak;
        // we calculate the volume change ourselves, because m_preamp->setVolumeDecibel is
        // a little confused about minus signs
        m_preamp->setVolume( exp( gain * log10over20 ) );
        m_preamp->fadeTo( exp( gain * log10over20 ), 0 );
    }
    else if( m_preamp )
    {
        //Debug::debug() << "[EnginePhonon] reset preamp";
        m_preamp->setVolume( 1.0 );
        m_preamp->fadeTo( 1.0, 0 );
    }

    /* register track change */
    update_total_time();

    MEDIA::registerTrackPlaying(m_currentMediaItem, true);

    emit mediaChanged();
}

void EnginePhonon::slot_on_metadata_change() {
    Debug::debug() << "[EnginePhonon] -> slot_on_metadata_change";
    if(!m_currentMediaItem || m_currentMediaItem->type() != TYPE_STREAM) return;

    const QMultiMap<QString, QString> &metaData = m_mediaObject->metaData();

    if( !metaData.value("TITLE").isEmpty() && metaData.value("TITLE") != "Streaming Data" )
      m_currentMediaItem->title = metaData.value("TITLE");

    if( !metaData.value("ALBUM").isEmpty() && metaData.value("ALBUM") != "Streaming Data" )
      m_currentMediaItem->album = metaData.value("ALBUM");

    if( !metaData.value("ARTIST").isEmpty() && metaData.value("ARTIST") != "Streaming Data" )
      m_currentMediaItem->artist = metaData.value("ARTIST");

    if(metaData.value("TITLE").contains("-"))
    {
      QStringList list = metaData.value("TITLE").split(" - ");
      m_currentMediaItem->artist = list.first();
      m_currentMediaItem->title = list.last();
    }

    // No support in phonon for bitrate/samplerate/format meta data

#ifdef TEST_FLAG
    foreach(QString key, metaData.keys())
      Debug::debug() << "[EnginePhonon] -> on_metadata_change " << key << ":" << metaData[key];
#endif

    emit mediaMetaDataChanged();
}

void EnginePhonon::slot_on_media_about_to_finish() {
    Debug::debug() << "[EnginePhonon] -> slot_on_media_about_to_finish";

    if( m_currentMediaItem && !m_currentMediaItem->isStopAfter )
    {
        /* needed to set next media item */
        if(!m_nextMediaItem)
          emit mediaAboutToFinish();
    }
}

void  EnginePhonon::slot_on_media_finished() {
    //Debug::debug() << "[EnginePhonon] slot_on_media_finished";
    emit mediaFinished();

    if(m_nextMediaItem)
    {
      Debug::debug() << "[EnginePhonon] -> slot_on_media_finished next mediaitem present !!";
      setMediaItem(m_nextMediaItem);
    }
    else if( m_mediaObject->queue().isEmpty())
    {
      Debug::debug() << "[EnginePhonon] -> slot_on_media_finished QUEUE IS EMPTY";
      emit engineRequestStop();
    }
}

void EnginePhonon::seek( qint64 milliseconds ) {
    if( m_mediaObject->isSeekable() )
    {
        Debug::debug() << "[EnginePhonon] -> seek to: " << milliseconds;

        m_mediaObject->seek( milliseconds );
        emit mediaTick( milliseconds );
        m_lastTick = milliseconds;
    }
    else
        Debug::warning() << "[EnginePhonon] -> not seekable media";
}

void EnginePhonon::slot_on_time_change(qint64 ms) {
    m_lastTick = ms;
    emit mediaTick(ms);

}

bool EnginePhonon::isEqualizerAvailable() {
    QList<Phonon::EffectDescription> effects = Phonon::BackendCapabilities::availableAudioEffects();

    foreach (Phonon::EffectDescription effect, effects) {
      if(effect.name()=="KEqualizer")
        return true;
     }
     return false;
}

void EnginePhonon::addEqualizer() {
    if(!m_equalizer) {
      Debug::debug() << "[EnginePhonon] -> addEqualizer : no equalizer available !";
      return;
    }

    // Insert effect into path if needed
    if( m_phononPath.effects().indexOf( m_equalizer ) == -1 )
    {
      if( !m_phononPath.effects().isEmpty() )
      {
        m_phononPath.insertEffect( m_equalizer, m_phononPath.effects().first() );
      }
      else
      {
        m_phononPath.insertEffect( m_equalizer );
      }
    }
}

void EnginePhonon::removeEqualizer() {
    //Debug::debug() << "[EnginePhonon] -> removeEqualizer !";
    if( m_phononPath.effects().indexOf( m_equalizer ) != -1 ) {
      m_phononPath.removeEffect( m_equalizer );
    }
}

void EnginePhonon::applyEqualizer(QList<int> gains) {
    if(!m_equalizer) {
      Debug::warning() << "[EnginePhonon] -> no equalizer initialized !!";
      return;
    }

    QList<Phonon::EffectParameter> effect_param_list = m_equalizer->parameters();

    QListIterator<int> it_effect_param( gains );

    /*Scaled value to set from universal -100 - 100 range to plugin scale */
    double scaled_value;
    foreach( const Phonon::EffectParameter &effect_param, effect_param_list )
    {
       scaled_value = it_effect_param.hasNext() ? it_effect_param.next() : 0;
       scaled_value *= ( qAbs(effect_param.maximumValue().toDouble() ) +  qAbs( effect_param.minimumValue().toDouble() ) );
       scaled_value /= 200.0;
       //Debug::debug() << "[EnginePhonon] set equalizer value :" << scaled_value;
       m_equalizer->setParameterValue( effect_param, scaled_value );
    }
}

void EnginePhonon::loadEqualizerSettings() {
    const QString preset_name = SETTINGS()->_currentPreset;

    if( SETTINGS()->_presetEq.keys().contains(preset_name) ) {
      QList<int> gains;

      gains << SETTINGS()->_presetEq[preset_name].preamp;
      for (int i=0 ; i<Equalizer::kBands ; ++i)
        gains << SETTINGS()->_presetEq[preset_name].gain[i];

      applyEqualizer(gains);
    }
}

#endif // ENABLE_PHONON
