/****************************************************************************************
 * Copyright (c) 2010-2012 Leo Franchi <lfranchi@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) any later           *
 * version.                                                                             *
 *                                                                                      *
 * 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 "Playlist.h"
#include "Playlist_p.h"
#include "Parsing_p.h"

#include <QNetworkReply>

Echonest::DynamicPlaylist::DynamicPlaylist()
    : d( new DynamicPlaylistData )
{

}

Echonest::DynamicPlaylist::DynamicPlaylist(const Echonest::DynamicPlaylist& other)
    : d( other.d )
{

}

Echonest::DynamicPlaylist::~DynamicPlaylist()
{

}


Echonest::DynamicPlaylist& Echonest::DynamicPlaylist::operator=(const Echonest::DynamicPlaylist& playlist)
{
    d = playlist.d;
    return *this;
}

QNetworkReply* Echonest::DynamicPlaylist::create(const Echonest::DynamicPlaylist::PlaylistParams& params) const
{
    // params are the same, if user passes in format parsing will throw, but it should be expected..
    return generateInternal( params, "dynamic/create" );
}

void Echonest::DynamicPlaylist::parseCreate(QNetworkReply* reply) throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );
    QByteArray data = reply->readAll();
//     qDebug() << data;
    QXmlStreamReader xml( data );

    Echonest::Parser::readStatus( xml );
    d->sessionId = Echonest::Parser::parsePlaylistSessionId( xml );


    Q_ASSERT( !d->sessionId.isEmpty() );
}

QNetworkReply* Echonest::DynamicPlaylist::restart(const Echonest::DynamicPlaylist::PlaylistParams& params) const
{
    return generateInternal( params, "dynamic/restart" );
}

QByteArray Echonest::DynamicPlaylist::sessionId() const
{
    return d->sessionId;
}

void Echonest::DynamicPlaylist::setSessionId(const QByteArray& id)
{
    d->sessionId = id;
}

Echonest::Song Echonest::DynamicPlaylist::currentSong() const
{
    return d->currentSong;
}

void Echonest::DynamicPlaylist::setCurrentSong(const Echonest::Song& song)
{
    d->currentSong = song;
}

QNetworkReply* Echonest::DynamicPlaylist::next( int results, int lookahead ) const
{
    QUrl url = Echonest::baseGetQuery( "playlist/dynamic", "next" );

    urlAddQueryItem( url,  QLatin1String( "session_id" ), QString::fromLatin1( d->sessionId ) );
    urlAddQueryItem( url,  QLatin1String( "results" ), QString::number( results ) );
    urlAddQueryItem( url,  QLatin1String( "lookahead" ), QString::number( lookahead ) );

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}

QNetworkReply* Echonest::DynamicPlaylist::fetchInfo() const
{
    QUrl url = Echonest::baseGetQuery( "playlist/dynamic", "info" );

    urlAddQueryItem( url, QLatin1String( "session_id" ), QString::fromLatin1( d->sessionId ) );

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}


Echonest::DynamicPlaylist::FetchPair Echonest::DynamicPlaylist::parseNext(QNetworkReply* reply) throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );

//     const QByteArray data = reply->readAll();
//     qDebug() << data;
    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    Echonest::SongList lookahead = Echonest::Parser::parseDynamicLookahead( xml );
    Echonest::SongList results = Echonest::Parser::parseSongList( xml );

    reply->deleteLater();

    return qMakePair(results, lookahead);
}

QNetworkReply* Echonest::DynamicPlaylist::feedback(const Echonest::DynamicPlaylist::DynamicFeedback& feedback) const
{
    QUrl url = Echonest::baseGetQuery( "playlist/dynamic", "feedback" );

    urlAddQueryItem( url, QLatin1String( "session_id" ), QString::fromLatin1( d->sessionId ) );

    foreach( const Echonest::DynamicPlaylist::DynamicFeedbackParamData& param, feedback ) {
        urlAddQueryItem( url, QString::fromLatin1( dynamicFeedbackToString(param.first) ), QString::fromLatin1( param.second ) );
    }

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}

void Echonest::DynamicPlaylist::parseFeedback(QNetworkReply* reply) const throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );

    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    reply->deleteLater();
}

QNetworkReply* Echonest::DynamicPlaylist::steer(const Echonest::DynamicPlaylist::PlaylistParams& steerParams) const
{
    QUrl url = Echonest::baseGetQuery( "playlist/dynamic", "steer" );

    urlAddQueryItem( url, QLatin1String( "session_id" ), QString::fromLatin1( d->sessionId ) );

    foreach( const Echonest::DynamicPlaylist::PlaylistParamData& param, steerParams ) {
        // HACK ARG min/max functions for steering are min_foo_bar, but params for static/initial seeds are foo_min_bar. can't reuse :(
        QByteArray str;
        switch ( param.first )
        {
            case Echonest::DynamicPlaylist::ArtistMinFamiliarity:
                str = "min_artist_familiarity";
                break;
            case Echonest::DynamicPlaylist::ArtistMaxFamiliarity:
                str = "max_artist_familiarity";
                break;
            case Echonest::DynamicPlaylist::ArtistMinHotttnesss:
                str = "min_artist_hotttnesss";
                break;
            case Echonest::DynamicPlaylist::ArtistMaxHotttnesss:
                str = "max_artist_hotttnesss";
                break;
            case Echonest::DynamicPlaylist::SongMinHotttnesss:
                str = "min_song_hotttnesss";
                break;
            case Echonest::DynamicPlaylist::SongMaxHotttnesss:
                str = "max_song_hotttnesss";
                break;
            case Echonest::DynamicPlaylist::MinEnergy:
                str = "min_energy";
                break;
            case Echonest::DynamicPlaylist::MaxEnergy:
                str = "max_energy";
                break;
            case Echonest::DynamicPlaylist::MinDanceability:
                str = "min_danceability";
                break;
            case Echonest::DynamicPlaylist::MaxDanceability:
                str = "max_danceability";
                break;
            case Echonest::DynamicPlaylist::MinLoudness:
                str = "min_loudness";
                break;
            case Echonest::DynamicPlaylist::MaxLoudness:
                str = "max_loudness";
                break;
            case Echonest::DynamicPlaylist::MinTempo:
                str = "min_tempo";
                break;
            case Echonest::DynamicPlaylist::MaxTempo:
                str = "max_tempo";
                break;
            default:
                str = playlistParamToString( param.first );
        }
        urlAddQueryItem( url, QString::fromLatin1( str ), param.second.toString());
    }

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}

void Echonest::DynamicPlaylist::parseSteer(QNetworkReply* reply) const throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );

    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    reply->deleteLater();
}

Echonest::SessionInfo Echonest::DynamicPlaylist::parseInfo(QNetworkReply* reply) throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );

    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    reply->deleteLater();
    return Echonest::Parser::parseSessionInfo( xml );

}

QNetworkReply* Echonest::DynamicPlaylist::deleteSession() const
{
    QUrl url = Echonest::baseGetQuery( "playlist/dynamic", "delete" );

    urlAddQueryItem( url, QLatin1String( "session_id" ), QString::fromLatin1( d->sessionId ) );

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}

void Echonest::DynamicPlaylist::parseDeleteSession(QNetworkReply* reply)
{
    Echonest::Parser::checkForErrors( reply );

    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    d->sessionId.clear();

    reply->deleteLater();
}

QNetworkReply* Echonest::DynamicPlaylist::staticPlaylist(const Echonest::DynamicPlaylist::PlaylistParams& params)
{
    return Echonest::DynamicPlaylist::generateInternal( params, "static" );
}

Echonest::SongList Echonest::DynamicPlaylist::parseStaticPlaylist(QNetworkReply* reply) throw( Echonest::ParseError )
{
    Echonest::Parser::checkForErrors( reply );

    QXmlStreamReader xml( reply->readAll() );

    Echonest::Parser::readStatus( xml );

    Echonest::SongList songs = Echonest::Parser::parseSongList( xml );
    reply->deleteLater();
    return songs;
}

QByteArray Echonest::DynamicPlaylist::parseXSPFPlaylist(QNetworkReply* reply) throw( Echonest::ParseError )
{
    QByteArray data = reply->readAll();
    Echonest::Parser::checkForErrors( reply );

    reply->deleteLater();
    return data;
}

QNetworkReply* Echonest::DynamicPlaylist::generateInternal(const Echonest::DynamicPlaylist::PlaylistParams& params, const QByteArray& type)
{
    QUrl url = Echonest::baseGetQuery( "playlist", type );


    Echonest::DynamicPlaylist::PlaylistParams::const_iterator iter = params.constBegin();
    for( ; iter < params.constEnd(); ++iter ) {
        if( iter->first == Format ) // If it's a format, we have to remove the xml format we automatically specify
            urlRemoveQueryItem( url, QString::fromLatin1( "format" ) );

        if( iter->first == Type ) { // convert type enum to string
            switch( static_cast<Echonest::DynamicPlaylist::ArtistTypeEnum>( iter->second.toInt() ) )
            {
            case ArtistType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "artist" ) );
                break;
            case ArtistRadioType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "artist-radio" ) );
                break;
            case ArtistDescriptionType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "artist-description" ) );
                break;
            case CatalogType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "catalog" ) );
                break;
            case CatalogRadioType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "catalog-radio" ) );
                break;
            case SongRadioType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "song-radio" ) );
                break;
            case GenreRadioType:
                urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QLatin1String( "genre-radio" ) );
                break;
            }

        } else if( iter->first == Sort ) {
            urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QString::fromLatin1( playlistSortToString( static_cast<Echonest::DynamicPlaylist::SortingType>( iter->second.toInt() ) ) ) );
        } else if( iter->first == Pick ) {
            urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QString::fromLatin1( playlistArtistPickToString( static_cast<Echonest::DynamicPlaylist::ArtistPick>( iter->second.toInt() ) ) ) );
        } else if( iter->first == SongInformation ) {
            Echonest::Song::addQueryInformation( url, Echonest::SongInformation( iter->second.value< Echonest::SongInformation >() ) );
        } else if ( iter->first == GenrePreset ) {
            urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QString::fromLatin1( playlistGenrePresetToString( static_cast<Echonest::DynamicPlaylist::GenrePresetParam>( iter->second.toInt() ) ) ) );
        } else {
            urlAddQueryItem( url, QString::fromLatin1( playlistParamToString( iter->first ) ), QString::fromLatin1( Echonest::escapeSpacesAndPluses( iter->second.toString() ) ) );
        }
    }

    return Echonest::Config::instance()->nam()->get( QNetworkRequest( url ) );
}


QByteArray Echonest::DynamicPlaylist::playlistParamToString(Echonest::DynamicPlaylist::PlaylistParam param)
{
    switch( param )
    {
        case Echonest::DynamicPlaylist::Type :
            return "type";
        case Echonest::DynamicPlaylist::Format :
            return "format";
        case Echonest::DynamicPlaylist::Pick:
            return "artist_pick";
        case Echonest::DynamicPlaylist::Variety :
            return "variety";
        case Echonest::DynamicPlaylist::ArtistId :
            return "artist_id";
        case Echonest::DynamicPlaylist::Artist :
            return "artist";
        case Echonest::DynamicPlaylist::ArtistSeedCatalog :
            return "artist_seed_catalog";
        case Echonest::DynamicPlaylist::SourceCatalog :
            return "seed_catalog";
        case Echonest::DynamicPlaylist::SongId :
            return "song_id";
        case Echonest::DynamicPlaylist::Description :
            return "description";
        case Echonest::DynamicPlaylist::Results :
            return "results";
        case Echonest::DynamicPlaylist::MaxTempo :
            return "max_tempo";
        case Echonest::DynamicPlaylist::MinTempo :
            return "min_tempo";
        case Echonest::DynamicPlaylist::MaxDuration :
            return "max_duration";
        case Echonest::DynamicPlaylist::MinDuration :
            return "min_duration";
        case Echonest::DynamicPlaylist::MaxLoudness :
            return "max_loudness";
        case Echonest::DynamicPlaylist::MinLoudness :
            return "min_loudness";
        case Echonest::DynamicPlaylist::ArtistMaxFamiliarity :
            return "artist_max_familiarity";
        case Echonest::DynamicPlaylist::ArtistMinFamiliarity :
            return "artist_min_familiarity";
        case Echonest::DynamicPlaylist::MinDanceability :
            return "min_danceability";
        case Echonest::DynamicPlaylist::MaxDanceability :
            return "max_danceability";
        case Echonest::DynamicPlaylist::MinEnergy :
            return "min_energy";
        case Echonest::DynamicPlaylist::MaxEnergy :
            return "max_energy";
        case Echonest::DynamicPlaylist::ArtistMaxHotttnesss :
            return "artist_max_hotttnesss";
        case Echonest::DynamicPlaylist::ArtistMinHotttnesss :
            return "artist_min_hotttnesss";
        case Echonest::DynamicPlaylist::SongMaxHotttnesss :
            return "song_max_hotttnesss";
        case Echonest::DynamicPlaylist::SongMinHotttnesss :
            return "song_min_hotttnesss";
        case Echonest::DynamicPlaylist::ArtistMinLongitude :
            return "min_longitude";
        case Echonest::DynamicPlaylist::ArtistMaxLongitude :
            return "max_longitude";
        case Echonest::DynamicPlaylist::ArtistMinLatitude  :
            return "min_latitude";
        case Echonest::DynamicPlaylist::ArtistMaxLatitude :
            return "max_latitude";
        case Echonest::DynamicPlaylist::Mode :
            return "mode";
        case Echonest::DynamicPlaylist::Key :
            return "key";
        case Echonest::DynamicPlaylist::SongInformation:
            return "bucket";
        case Echonest::DynamicPlaylist::Sort :
            return "sort";
        case Echonest::DynamicPlaylist::Limit :
            return "limit";
        case Echonest::DynamicPlaylist::Audio :
            return "audio";
        case Echonest::DynamicPlaylist::DMCA :
            return "dmca";
        case Echonest::DynamicPlaylist::ChainXSPF :
            return "chain_xspf";
        case Echonest::DynamicPlaylist::Mood :
            return "mood";
        case Echonest::DynamicPlaylist::Style :
            return "style";
        case Echonest::DynamicPlaylist::Adventurousness :
            return "adventurousness";
        case Echonest::DynamicPlaylist::MoreLikeThis :
            return "more_like_this";
        case Echonest::DynamicPlaylist::LessLikeThis :
            return "less_like_this";
        case Echonest::DynamicPlaylist::TargetTempo :
            return "target_tempo";
        case Echonest::DynamicPlaylist::TargetDanceability :
            return "target_danceability";
        case Echonest::DynamicPlaylist::TargetEnergy :
            return "target_energy";
        case Echonest::DynamicPlaylist::TargetLoudness :
            return "target_loudness";
        case Echonest::DynamicPlaylist::TargetArtistFamiliarity :
            return "target_artist_familiarity";
        case Echonest::DynamicPlaylist::TargetArtistHotttnesss :
            return "target_artist_hotttnesss";
        case Echonest::DynamicPlaylist::TargetSongHotttnesss :
            return "target_song_hotttnesss";
        case Echonest::DynamicPlaylist::SongType :
            return "song_type";
        case Echonest::DynamicPlaylist::Genre :
            return "genre";
        case Echonest::DynamicPlaylist::ArtistStartYearBefore:
            return "artist_start_year_before";
        case Echonest::DynamicPlaylist::ArtistStartYearAfter:
            return "artist_start_year_after";
        case Echonest::DynamicPlaylist::ArtistEndYearBefore:
            return "artist_end_year_before";
        case Echonest::DynamicPlaylist::ArtistEndYearAfter:
            return "artist_end_year_after";
        case Echonest::DynamicPlaylist::MaxAcousticness:
            return "max_accousticness";
        case Echonest::DynamicPlaylist::MinAcousticness:
            return "min_accousticness";
        case Echonest::DynamicPlaylist::MaxSpeechiness:
            return "max_speechiness";
        case Echonest::DynamicPlaylist::MinSpeechiness:
            return "min_speechiness";
        case Echonest::DynamicPlaylist::MaxLiveness:
            return "max_liveness";
        case Echonest::DynamicPlaylist::MinLiveness:
            return "min_liveness";
        case Echonest::DynamicPlaylist::MaxValence:
            return "max_valence";
        case Echonest::DynamicPlaylist::MinValence:
            return "min_valence";
        case Echonest::DynamicPlaylist::Distribution:
            return "distribution";
        case Echonest::DynamicPlaylist::GenrePreset:
            return "genre_preset";
    }
    return QByteArray();
}

QByteArray Echonest::DynamicPlaylist::playlistArtistPickToString(Echonest::DynamicPlaylist::ArtistPick pick)
{
    switch( pick )
    {
        case PickSongHotttnesssAscending:
            return "song_hotttnesss-asc";
        case PickTempoAscending:
            return "tempo-asc";
        case PickDurationAscending:
            return "duration-asc";
        case PickLoudnessAscending:
            return "loudness-asc";
        case PickModeAscending:
            return "mode-asc";
        case PickKeyAscending:
            return "key-asc";
        case PickSongHotttnesssDescending:
            return "song_hotttnesss-desc";
        case PickTempoDescending:
            return "tempo-desc";
        case PickDurationDescending:
            return "duration-desc";
        case PickLoudnessDescending:
            return "loudness-desc";
        case PickModeDescending:
            return "mode-desc";
        case PickKeyDescending:
            return "key-desc";
    }
    return QByteArray();
}

QByteArray Echonest::DynamicPlaylist::playlistSortToString(Echonest::DynamicPlaylist::SortingType sorting)
{
    switch( sorting )
    {
        case SortTempoAscending:
            return "tempo-asc";
        case SortTempoDescending:
            return "tempo-desc";
        case SortDurationAscending:
            return "duration-asc";
        case SortDurationDescending:
            return "duration-desc";
        case SortLoudnessAscending:
            return "loudness-asc";
        case SortLoudnessDescending:
            return "loudness-desc";
        case SortArtistFamiliarityAscending:
            return "artist_familiarity-asc";
        case SortArtistFamiliarityDescending:
            return "artist_familiarity-desc";
        case SortArtistHotttnessAscending:
            return "artist_hotttnesss-asc";
        case SortArtistHotttnessDescending:
            return "artist_hotttnesss-desc";
        case SortSongHotttnesssAscending:
            return "song_hotttnesss-asc";
        case SortSongHotttnesssDescending:
            return "song_hotttnesss-desc";
        case SortLatitudeAscending:
            return "latitude-asc";
        case SortLatitudeDescending:
            return "latitude-desc";
        case SortLongitudeAscending:
            return "longitude-asc";
        case SortLongitudeDescending:
            return "longitude-desc";
        case SortModeAscending:
            return "mode-asc";
        case SortModeDescending:
            return "mode-desc";
        case SortKeyAscending:
            return "key-asc";
        case SortKeyDescending:
            return "key-desc";
        case SortEnergyAscending:
            return "energy-asc";
        case SortEnergyDescending:
            return "energy-desc";
        case SortDanceabilityAscending:
            return "danceability-asc";
        case SortDanceabilityDescending:
            return "danceability-desc";
        case SortAcousticnessAscending:
            return "acousticness-asc";
        case SortAcousticnessDescending:
            return "acousticness-desc";
        case SortSpeechinessAscending:
            return "speechiness-asc";
        case SortSpeechinessDescending:
            return "speechiness-desc";
        case SortLivenessAscending:
            return "liveness-asc";
        case SortLivenessDescending:
            return "liveness-desc";
        case SortValenceAscending:
            return "valence-asc";
        case SortValenceDescending:
            return "valence-desc";
    }
    return QByteArray();
}

QByteArray Echonest::DynamicPlaylist::dynamicFeedbackToString(Echonest::DynamicPlaylist::DynamicFeedbackParam param)
{
    switch( param )
    {
        case BanArtist:
            return "ban_artist";
        case FavoriteArtist:
            return "favorite_artist";
        case BanSong:
            return "ban_song";
        case FavoriteSong:
            return "favorite_song";
        case SkipSong:
            return "skip_song";
        case PlaySong:
            return "play_song";
        case UnplaySong:
            return "unplay_song";
        case RateSong:
            return "rate_song";
        default:
            Q_ASSERT(false);
            return "";
    }
}

QByteArray Echonest::DynamicPlaylist::playlistGenrePresetToString(Echonest::DynamicPlaylist::GenrePresetParam param)
{
    switch( param )
    {
        case CoreBest:
            return "core-best";
        case CoreShuffled:
            return "core-shuffled";
        case EmergingBest:
            return "emerging-best";
        case EmerginShuffled:
            return "emerging-shuffled";
        default:
            Q_ASSERT(false);
            return "";
    }
}


QDebug Echonest::operator<<(QDebug d, const Echonest::DynamicPlaylist& playlist)
{
    d << QString::fromLatin1( "DynamicPlaylist(%1, %2)" ).arg( QLatin1String( playlist.sessionId() ), playlist.currentSong().toString() );
    return d.maybeSpace();
}
