/*****************************************************************************
 * Copyright (C) 2019 VLC authors and VideoLAN
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "playlist_controller.hpp"
#include "playlist_controller_p.hpp"
#include <vlc_player.h>
#include <vlc_url.h>
#include "util/shared_input_item.hpp"
#include <algorithm>
#include <QVariant>
#include <QDesktopServices>

namespace vlc {
  namespace playlist {

static QVector<PlaylistItem> toVec(vlc_playlist_item_t *const items[],
                                   size_t len)
{
    QVector<PlaylistItem> vec;
    for (size_t i = 0; i < len; ++i)
        vec.push_back(items[i]);
    return vec;
}

template <typename RAW, typename WRAPPER>
static QVector<RAW> toRaw(const QVector<WRAPPER> &items)
{
    QVector<RAW> vec;
    int count = items.size();
    vec.reserve(count);
    for (int i = 0; i < count; ++i)
        vec.push_back(items[i].raw());
    return vec;
}

QVector<Media> toMediaList(const QVariantList &sources)
{
    QVector<Media> mediaList;
    std::transform(sources.begin(), sources.end(),
                   std::back_inserter(mediaList), [](const QVariant& value) {
        const auto typeId = value.typeId();
        if ((typeId == QMetaType::QUrl) || (typeId == QMetaType::QString))
        {
            std::optional<QString> input;
            QUrl mrl;
            if (typeId == QMetaType::QString)
            {
                input = value.value<QString>().trimmed(); // akin to `UrlValidator::fixup()`
                mrl = *input; // plain text to QUrl, tolerant mode
            }
            else if (typeId == QMetaType::QUrl)
            {
                mrl = value.value<QUrl>(); // use the provided QUrl as is
            }
            else
                Q_UNREACHABLE();

            if (!mrl.isValid())
                return Media(); // the input is broken (beyond repair of tolerant mode if plain text)

            if ((typeId == QMetaType::QString) && mrl.scheme().isEmpty())
            {
                // Assume input is a path (only if the input is plain text):
                assert(input);
                mrl = QString::fromUtf8(vlc::wrap_cptr(vlc_path2uri(input->toUtf8().cbegin(), nullptr)).get()); // tolerant mode

                if (!mrl.isValid())
                    return Media();
            }

            if (mrl.scheme().isEmpty()) // akin to `UrlValidator::validate()`
                return Media();

            return Media(mrl.toString(QUrl::FullyEncoded), mrl.fileName());
        }
        else if (value.canConvert<SharedInputItem>())
        {
            return Media(value.value<SharedInputItem>().get());
        }
        else if (value.canConvert<PlaylistItem>())
        {
            return Media(value.value<PlaylistItem>().inputItem());
        }
        return Media{};
    });

    // Clear invalid media:
    mediaList.removeAll(Media());

    return mediaList;
}


extern "C" { // for C callbacks

static void
on_playlist_items_reset(vlc_playlist_t *playlist,
                        vlc_playlist_item_t *const items[],
                        size_t len, void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    auto vec = toVec(items, len);
    size_t totalCount = vlc_playlist_Count(playlist);

    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        PlaylistController* q = that->q_func();
        bool empty = vec.size() == 0;
        if (that->m_empty != empty)
        {
            that->m_empty = empty;
            emit q->isEmptyChanged(empty);
        }
        emit q->itemsReset(vec);

        if (totalCount != that->m_count)
        {
            that->m_count = totalCount;
            emit q->countChanged(totalCount);
        }
    });
}

static void
on_playlist_items_added(vlc_playlist_t *playlist, size_t index,
                        vlc_playlist_item_t *const items[], size_t len,
                        void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    auto vec = toVec(items, len);
    size_t totalCount = vlc_playlist_Count(playlist);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        PlaylistController* q = that->q_func();
        if (that->m_empty && vec.size() > 0)
        {
            that->m_empty = false;
            emit q->isEmptyChanged(that->m_empty);
        }
        emit q->itemsAdded(index, vec);

        if (totalCount != that->m_count)
        {
            that->m_count = totalCount;
            emit q->countChanged(totalCount);
        }
    });
}

static void
on_playlist_items_moved(vlc_playlist_t *playlist, size_t index, size_t count,
                        size_t target, void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        emit that->q_func()->itemsMoved(index, count, target);
    });
}

static void
on_playlist_items_removed(vlc_playlist_t *playlist, size_t index, size_t count,
                          void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    size_t totalCount = vlc_playlist_Count(playlist);
    bool empty = (totalCount == 0);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        PlaylistController* q = that->q_func();
        if (that->m_empty != empty)
        {
            that->m_empty = empty;
            emit q->isEmptyChanged(empty);
        }
        emit q->itemsRemoved(index, count);
        if (totalCount != that->m_count)
        {
            that->m_count = totalCount;
            emit q->countChanged(totalCount);
        }
    });
}

static void
on_playlist_items_updated(vlc_playlist_t *playlist, size_t index,
                          vlc_playlist_item_t *const items[], size_t len,
                          void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    auto vec = toVec(items, len);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        emit that->q_func()->itemsUpdated(index, vec);
        if (that->m_currentIndex != -1) {
            size_t currentIndex = static_cast<size_t>(that->m_currentIndex);
            if (currentIndex >= index && currentIndex < index + len)
            {
                const auto newCurrentItem = vec[currentIndex - index];
                const auto currentItem = that->m_currentItem.raw();
                // Even if vlc_playlist_item_t is the same, assign unconditionally to
                // update the data:
                that->m_currentItem = newCurrentItem;
                if (currentItem != newCurrentItem.raw())
                    emit that->q_func()->currentItemChanged();
            }
        }
    });
}

static void
on_playlist_playback_repeat_changed(vlc_playlist_t *playlist,
                                    enum vlc_playlist_playback_repeat repeat,
                                    void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        PlaylistController::PlaybackRepeat repeatMode = static_cast<PlaylistController::PlaybackRepeat>(repeat);
        if (that->m_repeat != repeatMode )
        {
            that->m_repeat = repeatMode;
            emit that->q_func()->repeatModeChanged(repeatMode);
        }
    });
}

static void
on_playlist_playback_order_changed(vlc_playlist_t *playlist,
                                   enum vlc_playlist_playback_order order,
                                   void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        bool isRandom = order == VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM;
        if (that->m_random != isRandom)
        {
            that->m_random = isRandom;
            emit that->q_func()->randomChanged(isRandom);
        }
    });
}

static void
on_playlist_current_index_changed(vlc_playlist_t *playlist, ssize_t index,
                                 void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);


    vlc_playlist_item_t* playlistItem = nullptr;
    if (index >= 0)
        playlistItem = vlc_playlist_Get(playlist, index);
    PlaylistItem newItem{ playlistItem };

    that->callAsync([=](){
        PlaylistController* q = that->q_func();

        if (that->m_playlist != playlist)
            return;
        if (that->m_currentIndex != index)
        {
            that->m_currentIndex = index;
            emit q->currentIndexChanged(that->m_currentIndex);
        }

        // Unlike items updated callback, we should not need to unconditionally
        // set the current item to update data because this is only a current
        // index changed callback.
        if (that->m_currentItem.raw() != newItem.raw())
        {
            that->m_currentItem = newItem;
            emit q->currentItemChanged();
        }
    });
}

static void
on_playlist_has_prev_changed(vlc_playlist_t *playlist, bool has_prev,
                             void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        if (that->m_hasPrev != has_prev)
        {
            that->m_hasPrev = has_prev;
            emit that->q_func()->hasPrevChanged(has_prev);
        }
    });
}

static void
on_playlist_has_next_changed(vlc_playlist_t *playlist, bool has_next,
                             void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        if (that->m_playlist != playlist)
            return;
        if (that->m_hasNext != has_next)
        {
            that->m_hasNext = has_next;
            emit that->q_func()->hasNextChanged(has_next);
        }
    });
}

static void
on_media_stopped_action_changed(vlc_playlist_t *playlist,
                          enum vlc_playlist_media_stopped_action new_action,
                          void *userdata)
{
    PlaylistControllerPrivate *that = static_cast<PlaylistControllerPrivate *>(userdata);
    that->callAsync([=](){
        auto action = static_cast<PlaylistController::MediaStopAction>(new_action);
        if (that->m_playlist != playlist)
            return;
        if (that->m_mediaStopAction == action)
            return;
        that->m_mediaStopAction = action;
        emit that->q_func()->mediaStopActionChanged(action);
    });
}

} // extern "C"

static const struct vlc_playlist_callbacks playlist_callbacks = []{
    struct vlc_playlist_callbacks cbs {};
    cbs.on_items_reset = on_playlist_items_reset;
    cbs.on_items_added = on_playlist_items_added;
    cbs.on_items_moved = on_playlist_items_moved;
    cbs.on_items_removed = on_playlist_items_removed;
    cbs.on_items_updated = on_playlist_items_updated;
    cbs.on_playback_repeat_changed = on_playlist_playback_repeat_changed;
    cbs.on_playback_order_changed= on_playlist_playback_order_changed;
    cbs.on_current_index_changed = on_playlist_current_index_changed;
    cbs.on_has_next_changed = on_playlist_has_next_changed;
    cbs.on_has_prev_changed = on_playlist_has_prev_changed;
    cbs.on_media_stopped_action_changed = on_media_stopped_action_changed;
    return cbs;
}();


//private API

PlaylistControllerPrivate::PlaylistControllerPrivate(PlaylistController* playlistController, vlc_playlist_t *playlist)
    : q_ptr(playlistController)
    , m_playlist(playlist)
{
    fillSortKeyTitleList();
}

PlaylistControllerPrivate::~PlaylistControllerPrivate()
{
    if (m_playlist && m_listener)
    {
        vlc_playlist_locker lock(m_playlist);
        vlc_playlist_RemoveListener(m_playlist, m_listener);
    }
}

//public API

PlaylistController::PlaylistController(vlc_playlist_t *playlist, QObject *parent)
    : QObject(parent)
    , d_ptr( new PlaylistControllerPrivate(this, playlist) )
{
    Q_D(PlaylistController);
    assert(playlist);

    connect(this, &PlaylistController::itemsMoved, this, &PlaylistController::resetSortKey);
    connect(this, &PlaylistController::itemsAdded, this, &PlaylistController::resetSortKey);
    connect(this, &PlaylistController::isEmptyChanged, [this](bool isEmpty) {if (isEmpty) emit resetSortKey();});

    {
        vlc_playlist_locker locker(d->m_playlist);
        d->m_listener = vlc_playlist_AddListener(d->m_playlist, &playlist_callbacks, d, true);
    }
    /*
     * Queue a playlistInitialized to be sent after the initial state callbacks
     * vlc_playlist_AddListener will synchronously call each callback in
     * playlist_callbacks, which will in turn queue an async call on the Qt
     * main thread
     */
    d->callAsync([this, d](){
        d->m_initialized = true;
        emit initializedChanged();
    });
}

PlaylistController::~PlaylistController()
{
}

PlaylistItem PlaylistController::getCurrentItem() const
{
    Q_D(const PlaylistController);
    return d->m_currentItem;
}

void PlaylistController::append(const QVariantList& sourceList, bool startPlaying)
{
    append(toMediaList(sourceList), startPlaying);
}

void PlaylistController::append(const QVariant &source, bool startPlaying)
{
    append(QVariantList{source}, startPlaying);
}

void PlaylistController::insert(unsigned index, const QVariantList& sourceList, bool startPlaying)
{
    insert(index, toMediaList(sourceList), startPlaying);
}

void
PlaylistController::append(const QVector<Media> &media, bool startPlaying)
{
    Q_D(PlaylistController);

    if (Q_UNLIKELY(media.isEmpty()))
        return;

    vlc_playlist_locker locker(d->m_playlist);

    auto rawMedia = toRaw<input_item_t *>(media);
    /* We can't append an empty media. */
    assert(rawMedia.size() > 0);

    int ret = vlc_playlist_Append(d->m_playlist,
                                  rawMedia.constData(), rawMedia.size());
    if (ret != VLC_SUCCESS)
        throw std::bad_alloc();
    if (startPlaying)
    {
        ssize_t playIndex = (ssize_t)vlc_playlist_Count( d->m_playlist ) - rawMedia.size();
        ret = vlc_playlist_GoTo(d->m_playlist, playIndex);
        if (ret != VLC_SUCCESS)
            return;
        vlc_playlist_Start(d->m_playlist);
    }
}

void
PlaylistController::insert(size_t index, const QVector<Media> &media, bool startPlaying)
{
    Q_D(PlaylistController);

    if (Q_UNLIKELY(media.isEmpty()))
        return;

    vlc_playlist_locker locker(d->m_playlist);

    auto rawMedia = toRaw<input_item_t *>(media);
    /* We can't insert an empty media. */
    assert(rawMedia.size() > 0);

    int ret = vlc_playlist_RequestInsert(d->m_playlist, index,
                                         rawMedia.constData(), rawMedia.size());
    if (ret != VLC_SUCCESS)
        throw std::bad_alloc();
    if (startPlaying)
    {
        ret = vlc_playlist_GoTo(d->m_playlist, index);
        if (ret != VLC_SUCCESS)
            return;
        vlc_playlist_Start(d->m_playlist);
    }
}

void
PlaylistController::move(const QVector<PlaylistItem> &items, size_t target,
               ssize_t indexHint)
{
    Q_D(PlaylistController);
    vlc_playlist_locker locker(d->m_playlist);

    auto rawItems = toRaw<vlc_playlist_item_t *>(items);
    int ret = vlc_playlist_RequestMove(d->m_playlist, rawItems.constData(),
                                       rawItems.size(), target, indexHint);
    if (ret != VLC_SUCCESS)
        throw std::bad_alloc();
}

void
PlaylistController::remove(const QVector<PlaylistItem> &items, ssize_t indexHint)
{
    Q_D(PlaylistController);
    vlc_playlist_locker locker(d->m_playlist);
    auto rawItems = toRaw<vlc_playlist_item_t *>(items);
    int ret = vlc_playlist_RequestRemove(d->m_playlist, rawItems.constData(),
                                         rawItems.size(), indexHint);
    if (ret != VLC_SUCCESS)
        throw std::bad_alloc();
}

void
PlaylistController::shuffle()
{
    Q_D(PlaylistController);
    vlc_playlist_locker locker(d->m_playlist);
    vlc_playlist_Shuffle(d->m_playlist);
}

void
PlaylistController::sort(const QVector<vlc_playlist_sort_criterion> &criteria)
{
    Q_D(PlaylistController);
    vlc_playlist_locker locker(d->m_playlist);
    vlc_playlist_Sort(d->m_playlist, criteria.constData(), criteria.size());
}

void PlaylistController::sort(PlaylistController::SortKey key, PlaylistController::SortOrder order)
{
    if(key != SortKey::SORT_KEY_NONE)
        setSortKey(key);
    setSortOrder(order);

    sort();
}

void PlaylistController::sort(PlaylistController::SortKey key)
{
    if (key == SortKey::SORT_KEY_NONE)
        return;

    if (getSortKey() != key)
    {
        setSortOrder(SortOrder::SORT_ORDER_ASC);
        setSortKey(key);
    }
    else
    {
        switchSortOrder();
    }

    sort();
}

void PlaylistController::sort(void)
{
    Q_D(PlaylistController);

    if (d->m_sortKey == SortKey::SORT_KEY_NONE)
        return;

    vlc_playlist_sort_criterion crit = {
        static_cast<vlc_playlist_sort_key>(d->m_sortKey),
        static_cast<vlc_playlist_sort_order>(d->m_sortOrder)
    };
    QVector<vlc_playlist_sort_criterion> criteria { crit };
    sort( criteria );
}

void PlaylistController::explore(const PlaylistItem& pItem)
{
    vlc_playlist_item_t * const playlistItem = pItem.raw();
    if( playlistItem )
    {
        input_item_t * const p_input = vlc_playlist_item_GetMedia(playlistItem);
        auto uri = vlc::wrap_cptr( input_item_GetURI(p_input) );

        if( uri && uri.get()[0] != '\0')
        {
            auto path = vlc::wrap_cptr( vlc_uri2path( uri.get() ) );

            if( !path )
                return;

            QString containingDir = QFileInfo( path.get() ).absolutePath();
            if( !QFileInfo( containingDir ).isDir() )
                return;

            QUrl file = QUrl::fromLocalFile( containingDir );
            if( !file.isLocalFile() )
                return;

            QDesktopServices::openUrl( file );
        }
    }
}

int PlaylistController::serialize(const QString &fileName)
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{d->m_playlist};
    return vlc_playlist_Export(d->m_playlist, qtu(fileName), "export-m3u8");
}

int PlaylistController::currentIndex() const
{
    Q_D(const PlaylistController);
    return d->m_currentIndex;
}

void PlaylistController::play()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Start( d->m_playlist );
}

void PlaylistController::pause()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Pause( d->m_playlist );
}

void PlaylistController::stop()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Stop( d->m_playlist );
}

void PlaylistController::next()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Next( d->m_playlist );
}

void PlaylistController::prev()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Prev( d->m_playlist );
}

void PlaylistController::prevOrReset()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    bool seek = false;
    vlc_player_t* player = vlc_playlist_GetPlayer( d->m_playlist );
    Q_ASSERT(player);
    if( !vlc_player_IsStarted(player) || vlc_player_GetTime(player) < VLC_TICK_FROM_MS(10) )
    {
        int ret = vlc_playlist_Prev( d->m_playlist );
        if (ret == VLC_SUCCESS)
            vlc_playlist_Start( d->m_playlist );
    }
    else
        seek = true;
    if (seek)
        vlc_player_JumpPos( player, 0.f );
}

void PlaylistController::togglePlayPause()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_player_t* player = vlc_playlist_GetPlayer( d->m_playlist );
    if ( vlc_player_IsStarted(player) )
        vlc_player_TogglePause( player );
    else
        vlc_playlist_Start( d->m_playlist );
}

void PlaylistController::toggleRandom()
{
    Q_D(PlaylistController);
    vlc_playlist_playback_order new_order;
    vlc_playlist_locker lock{ d->m_playlist };
    if ( d->m_random )
        new_order = VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL;
    else
        new_order = VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM;
    vlc_playlist_SetPlaybackOrder( d->m_playlist, new_order );
    config_PutInt( "random", new_order );
}

void PlaylistController::toggleRepeatMode()
{
    Q_D(PlaylistController);
    vlc_playlist_playback_repeat new_repeat;
    /* Toggle Normal -> Loop -> Repeat -> Normal ... */
    switch ( d->m_repeat ) {
    case PlaybackRepeat::PLAYBACK_REPEAT_NONE:
        new_repeat = VLC_PLAYLIST_PLAYBACK_REPEAT_ALL;
        break;
    case PlaybackRepeat::PLAYBACK_REPEAT_ALL:
        new_repeat = VLC_PLAYLIST_PLAYBACK_REPEAT_CURRENT;
        break;
    case PlaybackRepeat::PLAYBACK_REPEAT_CURRENT:
    default:
        new_repeat = VLC_PLAYLIST_PLAYBACK_REPEAT_NONE;
        break;
    }
    {
        vlc_playlist_locker lock{ d->m_playlist };
        vlc_playlist_SetPlaybackRepeat( d->m_playlist, new_repeat );
    }
    config_PutInt( "repeat", new_repeat );
}

void PlaylistController::clear()
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_Clear( d->m_playlist );
}

void PlaylistController::goTo(uint index, bool startPlaying)
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    size_t count = vlc_playlist_Count( d->m_playlist );
    if (count == 0 || index > count-1)
        return;
    vlc_playlist_GoTo( d->m_playlist, index );
    if (startPlaying)
        vlc_playlist_Start( d->m_playlist );
}

bool PlaylistController::isRandom() const
{
    Q_D(const PlaylistController);
    return d->m_random;
}

void PlaylistController::setRandom(bool random)
{
    Q_D(const PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_SetPlaybackOrder( d->m_playlist, random ? VLC_PLAYLIST_PLAYBACK_ORDER_RANDOM : VLC_PLAYLIST_PLAYBACK_ORDER_NORMAL );
}

Playlist PlaylistController::getPlaylist() const
{
    Q_D(const PlaylistController);
    return Playlist(d->m_playlist);
}

void PlaylistController::resetSortKey()
{
    Q_D(PlaylistController);
    d->m_sortKey = SortKey::SORT_KEY_NONE;
    emit sortKeyChanged();
}

PlaylistController::PlaybackRepeat PlaylistController::getRepeatMode() const
{
    Q_D(const PlaylistController);
    return d->m_repeat;
}

void PlaylistController::setRepeatMode(PlaylistController::PlaybackRepeat mode)
{
    Q_D(const PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_SetPlaybackRepeat( d->m_playlist, static_cast<vlc_playlist_playback_repeat>(mode) );
}

PlaylistController::MediaStopAction PlaylistController::getMediaStopAction() const
{
    Q_D(const PlaylistController);
    return d->m_mediaStopAction;
}

void PlaylistController::setMediaStopAction(PlaylistController::MediaStopAction action)
{
    Q_D(PlaylistController);
    vlc_playlist_locker lock{ d->m_playlist };
    vlc_playlist_SetMediaStoppedAction(d->m_playlist, static_cast<vlc_playlist_media_stopped_action>(action) );
}

bool PlaylistController::isEmpty() const
{
    Q_D(const PlaylistController);
    return d->m_empty;
}

int PlaylistController::count() const
{
    Q_D(const PlaylistController);
    assert(d->m_count <= (size_t)INT_MAX);
    return d->m_count;
}

void PlaylistController::setSortKey(SortKey sortKey)
{
    Q_D(PlaylistController);

    if (sortKey == d->m_sortKey)
        return;

    d->m_sortKey = sortKey;
    emit sortKeyChanged();
}

void PlaylistController::setSortOrder(SortOrder sortOrder)
{
    Q_D(PlaylistController);

    SortOrder order = d->m_sortOrder;
    if(sortOrder == order)
        return;

    d->m_sortOrder = sortOrder;
    emit sortOrderChanged();
}

void PlaylistController::switchSortOrder()
{
    Q_D(PlaylistController);

    SortOrder order = d->m_sortOrder;
    if (order == SortOrder::SORT_ORDER_ASC)
        order = SortOrder::SORT_ORDER_DESC;
    else if (order == SortOrder::SORT_ORDER_DESC)
        order = SortOrder::SORT_ORDER_ASC;
    else
        return;

    d->m_sortOrder = order;
    emit sortOrderChanged();
}

PlaylistController::SortKey PlaylistController::getSortKey() const
{
    Q_D(const PlaylistController);
    return d->m_sortKey;
}

PlaylistController::SortOrder PlaylistController::getSortOrder() const
{
    Q_D(const PlaylistController);
    return d->m_sortOrder;
}

bool PlaylistController::hasNext() const
{
    Q_D(const PlaylistController);
    return d->m_hasNext;
}

bool PlaylistController::hasPrev() const
{
    Q_D(const PlaylistController);
    return d->m_hasPrev;
}

QVariantList PlaylistController::getSortKeyTitleList() const
{
    Q_D(const PlaylistController);

    return d->sortKeyTitleList;
}

bool PlaylistController::isInitialized() const
{
    Q_D(const PlaylistController);
    return d->m_initialized;
}

  } // namespace playlist
} // namespace vlc
