/***************************************************************************
 *   Copyright(C) 2006-2022 by Ilya Kotov                                  *
 *   forkotov02@ya.ru                                                      *
 *                                                                         *
 *   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.         *
 ***************************************************************************/
#include <QTextStream>
#include <algorithm>
#include <qmmp/metadatamanager.h>
#include "metadatahelper_p.h"
#include "playlistparser.h"
#include "playlistformat.h"
#include "playlistcontainer_p.h"
#include "groupedcontainer_p.h"
#include "normalcontainer_p.h"
#include "playlisttask_p.h"
#include "fileloader_p.h"
#include "playstate_p.h"
#include "detailsdialog.h"
#include "qmmpuisettings.h"
#include "playlistmodel.h"

#define INVALID_INDEX -1

PlayListModel::PlayListModel(const QString &name, QObject *parent)
    : QObject(parent) , m_name(name)
{
    m_ui_settings = QmmpUiSettings::instance();

    m_loader = new FileLoader(this);
    m_task = new PlayListTask(this);
    if(m_ui_settings->isGroupsEnabled())
        m_container = new GroupedContainer;
    else
        m_container = new NormalContainer;
    if(m_ui_settings->isShuffle())
        m_play_state = new ShufflePlayState(this);
    else
        m_play_state = new NormalPlayState(this);
    connect(m_ui_settings, SIGNAL(groupsChanged(bool)), SLOT(prepareGroups(bool)));
    connect(m_ui_settings, SIGNAL(shuffleChanged(bool)), SLOT(prepareForShufflePlaying(bool)));
    connect(m_loader, SIGNAL(newTracksToInsert(PlayListItem*, QList<PlayListTrack*>)),
            SLOT(insert(PlayListItem*, QList<PlayListTrack*>)), Qt::QueuedConnection);
    connect(m_loader, SIGNAL(finished()), SLOT(preparePlayState()));
    connect(m_loader, SIGNAL(finished()), SIGNAL(loaderFinished()));
    connect(m_task, SIGNAL(finished()), SLOT(onTaskFinished()));
}

PlayListModel::~PlayListModel()
{
    blockSignals(true);
    m_loader->finish();
    clear();
    delete m_play_state;
    delete m_container;
}

QString PlayListModel::name() const
{
    return m_name;
}

void PlayListModel::setName(const QString &name)
{
    if(m_name != name)
    {
        m_name = name;
        emit nameChanged(name);
    }
}

void PlayListModel::add(PlayListTrack *track)
{
    m_container->addTrack(track);
    m_total_duration += track->duration();
    int flags = 0;

    if(m_container->trackCount() == 1)
    {
        m_current_track = track;
        m_current = m_container->indexOf(track);
        flags |= CURRENT;
    }
    else if(m_ui_settings->isGroupsEnabled())
    {
        //update current index for grouped container only
        m_current = m_container->indexOf(m_current_track);
    }
    flags |= STRUCTURE;
    emit listChanged(flags);
}

void PlayListModel::add(const QList<PlayListTrack *> &tracks)
{
    if(tracks.isEmpty())
        return;

    int flags = 0;

    m_container->addTracks(tracks);

    if(m_container->trackCount() == tracks.count())
    {
        m_current_track = tracks.first();
        m_current = m_container->indexOf(m_current_track);
        flags |= CURRENT;
    }
    else if(m_ui_settings->isGroupsEnabled())
    {
        //update current index for grouped container only
        m_current = m_container->indexOf(m_current_track);
    }

    for(PlayListTrack *track : qAsConst(tracks))
    {
        m_total_duration += track->duration();
        emit trackAdded(track);
    }
    preparePlayState();
    flags |= STRUCTURE;
    emit listChanged(flags);
}

void PlayListModel::add(const QString &path)
{
    m_loader->add(path);
}

void PlayListModel::add(const QStringList &paths)
{
    m_loader->add(paths);
}

void PlayListModel::insert(int index, PlayListTrack *track)
{
    m_container->insertTrack(index, track);
    m_total_duration += track->duration();

    int flags = 0;

    if(m_container->trackCount() == 1)
    {
        m_current_track = track;
        m_current = m_container->indexOf(track);
        flags |= CURRENT;
    }
    else
    {
        //update current index
        m_current = m_container->indexOf(m_current_track);
    }
    emit trackAdded(track);
    flags |= STRUCTURE;
    emit listChanged(flags);
}

void PlayListModel::insert(PlayListItem *before, PlayListTrack *track)
{
     if(before)
         insert(m_container->indexOf(before), track);
     else
         add(track);
}

void PlayListModel::insert(int index, const QList<PlayListTrack *> &tracks)
{
    if(tracks.isEmpty())
        return;

    int flags = 0;

    for(PlayListTrack *track : qAsConst(tracks))
    {
        index = m_container->insertTrack(index, track) + 1;
        m_total_duration += track->duration();
        if(m_container->trackCount() == 1)
        {
            m_current_track = track;
            m_current = m_container->indexOf(track);
            flags |= CURRENT;
        }
        emit trackAdded(track);
    }
    //update current index
    m_current = m_container->indexOf(m_current_track);
    preparePlayState();
    flags |= STRUCTURE;
    emit listChanged(flags);
}

void PlayListModel::insert(int index, const QByteArray &json)
{
    insert(index, PlayListParser::deserialize(json));
}

void PlayListModel::insert(PlayListItem *before, const QList<PlayListTrack *> &tracks)
{
    if(m_ui_settings->skipExistingTracks() && sender() == m_loader)
    {
        if(m_paths.isEmpty())
        {
            m_paths.reserve(m_container->trackCount());
            for(const PlayListItem *item : qAsConst(m_container->items()))
            {
                if(!item->isGroup())
                    m_paths.insert(static_cast<const PlayListTrack *>(item)->path());
            }
        }

        QList<PlayListTrack *> uniqueTracks;
        for(PlayListTrack *track : qAsConst(tracks))
        {
            if(!m_paths.contains(track->path()))
            {
                m_paths.insert(track->path());
                uniqueTracks << track;
            }
        }

        if(before)
            insert(m_container->indexOf(before), uniqueTracks);
        else
            add(uniqueTracks);
    }
    else
    {
        if(before)
            insert(m_container->indexOf(before), tracks);
        else
            add(tracks);
    }
}

void PlayListModel::insert(int index, const QString &path)
{
    insert(index, QStringList() << path);
}

void PlayListModel::insert(int index, const QStringList &paths)
{
    if(index < 0 || index >= m_container->count())
        add(paths);
    else
    {
        PlayListItem *before = m_container->item(index);
        m_loader->insert(before, paths);
    }
}

void PlayListModel::insert(int index, const QList<QUrl> &urls)
{
    QStringList paths;
    for(const QUrl &url : qAsConst(urls))
    {
        if(url.scheme() == "file")
            paths.append(QFileInfo(url.toLocalFile()).canonicalFilePath());
        else
            paths.append(url.toString());
    }
    insert(index, paths);
}

int PlayListModel::count() const
{
    return m_container->count();
}

int PlayListModel::trackCount() const
{
    return m_container->trackCount();
}

bool PlayListModel::isEmpty() const
{
    return m_container->isEmpty();
}

int PlayListModel::columnCount() const
{
    return MetaDataHelper::instance()->columnCount();
}

PlayListTrack* PlayListModel::currentTrack() const
{
    return m_container->isEmpty() ? nullptr : m_current_track;
}

PlayListTrack *PlayListModel::nextTrack() const
{
    if(m_container->isEmpty() || !m_play_state)
        return nullptr;
    if(m_stop_track && m_stop_track == currentTrack())
        return nullptr;
    if(!isEmptyQueue())
        return m_container->queuedTracks().first();
    int index = m_play_state->nextIndex();
    if(index < 0 || (index + 1 > m_container->count()))
        return nullptr;
    return m_container->track(index);
}

int PlayListModel::indexOf(PlayListItem* item) const
{
    return m_container->indexOf(item);
}

PlayListItem* PlayListModel::item(int index) const
{
    return m_container->item(index);
}

PlayListTrack* PlayListModel::track(int index) const
{
    return m_container->track(index);
}

PlayListGroup* PlayListModel::group(int index) const
{
    return m_container->group(index);
}

int PlayListModel::currentIndex() const
{
    return m_current;
}

bool PlayListModel::setCurrent(int index)
{
    if(index > count()-1 || index < 0)
        return false;
    PlayListItem *item = m_container->item(index);
    if(item->isGroup())
    {
        index++;
        item = m_container->item(index);
    }
    m_current = index;
    m_current_track = dynamic_cast<PlayListTrack*> (item);
    emit listChanged(CURRENT);
    return true;
}

bool PlayListModel::setCurrent(PlayListTrack *track)
{
    if(!m_container->contains(track))
        return false;
    return setCurrent(m_container->indexOf(track));
}

bool PlayListModel::isTrack(int index) const
{
    if(index > count()-1 || index < 0)
        return false;
    return !m_container->item(index)->isGroup();
}

bool PlayListModel::isGroup(int index) const
{
    if(index > count()-1 || index < 0)
        return false;
    return m_container->item(index)->isGroup();
}

bool PlayListModel::next()
{
    if(m_stop_track == currentTrack())
    {
        m_stop_track = nullptr;
        emit listChanged(STOP_AFTER);
        return false;
    }
    if(!isEmptyQueue())
    {
        m_current_track = m_container->dequeue();
        m_current = m_container->indexOf(m_current_track);
        emit listChanged(CURRENT | QUEUE);
        return true;
    }

    if(m_loader->isRunning())
        m_play_state->prepare();
    return m_play_state->next();
}

bool PlayListModel::previous()
{
    if(m_loader->isRunning())
        m_play_state->prepare();
    return m_play_state->previous();
}

void PlayListModel::clear()
{
    m_loader->finish();
    m_current = 0;
    m_stop_track = nullptr;
    m_container->clear();
    m_total_duration = 0;
    m_play_state->resetState();
    emit listChanged(STRUCTURE | QUEUE | STOP_AFTER | CURRENT | SELECTION);
}

void PlayListModel::clearSelection()
{
    m_container->clearSelection();
    emit listChanged(SELECTION);
}

QList<PlayListItem *> PlayListModel::mid(int pos, int count) const
{
    return m_container->mid(pos, count);
}

bool PlayListModel::isSelected(int index) const
{
    return m_container->isSelected(index);
}

bool PlayListModel::contains(const QString &url)
{
    for(int i = 0; i < m_container->count(); ++i)
    {
        PlayListTrack *t = track(i);
        if(!t)
            continue;
        if(t->path() == url)
            return true;
    }
    return false;
}

int PlayListModel::indexOfTrack(int index) const
{
    return m_container->indexOfTrack(index);
}

PlayListTrack *PlayListModel::findTrack(int track_index) const
{
    return m_container->findTrack(track_index);
}

QList<PlayListItem *> PlayListModel::findTracks(const QString &str) const
{
    QList<PlayListItem *> items;
    PlayListItem *item = nullptr;
    if(str.isEmpty())
        return items;

    for(int i = 0; i < m_container->count(); ++i)
    {
        item = m_container->item(i);
        if(item->isGroup())
            continue;

        if(!item->formattedTitles().filter(str, Qt::CaseInsensitive).isEmpty())
            items.append(item);
    }
    return items;
}

void PlayListModel::setSelected(int index, bool selected)
{
    m_container->setSelected(index, selected);
    emit listChanged(SELECTION);
}

void PlayListModel::setSelected(const QList<PlayListTrack *> &tracks, bool selected)
{
    for(PlayListTrack *t : qAsConst(tracks))
        t->setSelected(selected);
    emit listChanged(SELECTION);
}

void PlayListModel::setSelected(const QList<PlayListItem *> &items, bool selected)
{
    for(PlayListItem *i : qAsConst(items))
        i->setSelected(selected);
    emit listChanged(SELECTION);
}

void PlayListModel::setSelected(int first, int last, bool selected)
{
    if(first > last)
    {
        setSelected(last, first, selected);
        return;
    }
    for(int index = first; index <= last; ++index)
    {
        PlayListItem *i = item(index);
        if(!i)
            continue;
        i->setSelected(selected);
    }
    emit listChanged(SELECTION);
}

void PlayListModel::setSelected(const QList<int> &indexes, bool selected)
{
    for(const int &idx : qAsConst(indexes))
        m_container->setSelected(idx, selected);
    emit listChanged(SELECTION);
}

void PlayListModel::removeSelected()
{
    removeSelection(false);
}

void PlayListModel::removeUnselected()
{
    removeSelection(true);
}

void PlayListModel::removeTrack (int i)
{
    int flags = removeTrackInternal(i);
    if(flags)
        emit listChanged(flags);
}

void PlayListModel::removeTrack (PlayListItem *track)
{
    if(m_container->contains(track))
        removeTrack (m_container->indexOf(track));
}

void PlayListModel::removeTracks(const QList<PlayListItem *> &items)
{
    int i = 0;
    int select_after_delete = -1;
    int flags = 0;

    while (!m_container->isEmpty() && i < m_container->count())
    {
        PlayListItem *item = m_container->item(i);
        if(!item->isGroup() && items.contains(item))
        {
            flags |= removeTrackInternal(i);

            if(m_container->isEmpty())
                continue;

            select_after_delete = i;
        }
        else
            i++;
    }

    select_after_delete = qMin(select_after_delete, m_container->count() - 1);

    if(select_after_delete >= 0)
    {
        m_container->setSelected(select_after_delete, true);
        flags |= SELECTION;
    }

    m_play_state->prepare();

    if(flags)
        emit listChanged(flags);
}

void PlayListModel::removeTracks(const QList<PlayListTrack *> &tracks)
{
    QList<PlayListItem *> items;
    for(PlayListTrack *track : tracks)
        items << static_cast<PlayListItem *>(track);

    removeTracks(items);
}

void PlayListModel::removeSelection(bool inverted)
{
    int i = 0;
    int select_after_delete = -1;
    int flags = 0;

    while (!m_container->isEmpty() && i < m_container->count())
    {
        PlayListItem *item = m_container->item(i);
        if(!item->isGroup() && item->isSelected() ^ inverted)
        {
            flags |= removeTrackInternal(i);

            if(m_container->isEmpty())
                continue;

            select_after_delete = i;
        }
        else
            i++;
    }

    select_after_delete = qMin(select_after_delete, m_container->count() - 1);

    if(select_after_delete >= 0)
    {
        m_container->setSelected(select_after_delete, true);
        flags |= SELECTION;
    }

    m_play_state->prepare();

    if(flags)
        emit listChanged(flags);
}

int PlayListModel::removeTrackInternal(int i)
{
    if((i < 0) || (i >= count()))
        return 0;

    int flags = 0;
    PlayListTrack* track = m_container->track(i);
    if(!track)
        return flags;
    if(track->isQueued())
        flags |= QUEUE;
    m_container->removeTrack(track);
    if(m_stop_track == track)
    {
        flags |= STOP_AFTER;
        m_stop_track = nullptr;
    }
    if(track->isSelected())
        flags |= SELECTION;

    m_total_duration -= track->duration();
    m_total_duration = qMax(Q_INT64_C(0), m_total_duration);

    if(m_current_track == track)
    {
        flags |= CURRENT;
        if(m_container->isEmpty())
            m_current_track = nullptr;
        else
        {
            m_current = i > 0 ? qMin(i - 1, m_container->count() - 1) : 0;
            if(!(m_current_track = m_container->track(m_current)))
            {
                m_current_track = m_current > 0 ? m_container->track(m_current - 1) :
                                                  m_container->track(1);
            }
        }
    }

    if(track->isUsed())
        track->deleteLater();
    else
        delete track;

    m_current = m_current_track ? m_container->indexOf(m_current_track) : -1;
    m_play_state->prepare();

    flags |= STRUCTURE;
    return flags;
}

void PlayListModel::invertSelection()
{
    for(int i = 0; i < m_container->count(); ++i)
        m_container->setSelected(i, !m_container->isSelected(i));
    emit listChanged(SELECTION);
}

void PlayListModel::selectAll()
{
    for(int i = 0; i < m_container->count(); ++i)
        m_container->setSelected(i, true);
    emit listChanged(SELECTION);
}

void PlayListModel::showDetails(QWidget *parent)
{
    QList<PlayListTrack *> selected_tracks;

    for(int i = 0; i < m_container->count(); ++i)
    {
        if(!m_container->isSelected(i))
            continue;
        PlayListTrack *track = m_container->track(i);
        if(track)
            selected_tracks.append(track);
    }

    if(!selected_tracks.isEmpty())
    {
        DetailsDialog *d = new DetailsDialog(selected_tracks, parent);
        d->setAttribute(Qt::WA_DeleteOnClose, true);
        connect(d, SIGNAL(metaDataChanged(QStringList)), SLOT(updateMetaData(QStringList)));
        d->show();
    }
}

void PlayListModel::showDetailsForCurrent(QWidget *parent)
{
    if(m_current_track)
    {
        QDialog *d = new DetailsDialog(QList<PlayListTrack *>() << m_current_track, parent);
        d->setAttribute(Qt::WA_DeleteOnClose, true);
        connect(d, SIGNAL(metaDataChanged(QStringList)), SLOT(updateMetaData(QStringList)));
        d->show();
    }
}

int PlayListModel::firstSelectedUpper(int row)
{
    for(int i = row - 1;i >= 0;i--)
    {
        if(isSelected(i))
            return i;
    }
    return -1;
}

int PlayListModel::firstSelectedLower(int row)
{
    for(int i = row + 1;i < count() ;i++)
    {
        if(isSelected(i))
            return i;
    }
    return -1;
}

qint64 PlayListModel::totalDuration() const
{
    return m_total_duration;
}

void PlayListModel::moveItems(int from, int to)
{
    // Get rid of useless work
    if(from == to)
        return;

    QList<int> selected_indexes = selectedIndexes();

    if(selected_indexes.isEmpty())
        return;

    if(std::any_of(selected_indexes.cbegin(), selected_indexes.cend(), [this](int i){ return !isTrack(i); }))
        return;

    if(bottommostInSelection(from) == INVALID_INDEX ||
            from == INVALID_INDEX ||
            topmostInSelection(from) == INVALID_INDEX)
        return;

    if(m_container->move(selected_indexes, from, to))
    {
        m_current = m_container->indexOf(m_current_track);
        emit listChanged(STRUCTURE);
    }
}

int PlayListModel::topmostInSelection(int row)
{
    if(row == 0)
        return 0;

    for(int i = row - 1;i >= 0;i--)
    {
        if(isSelected(i))
            continue;
        else
            return i + 1;
    }
    return 0;
}

int PlayListModel::bottommostInSelection(int row)
{
    if(row >= count() - 1)
        return row;

    for(int i = row + 1; i < count(); i++)
    {
        if(isSelected(i))
            continue;
        else
            return i - 1;
    }
    return count() - 1;
}

const SimpleSelection& PlayListModel::getSelection(int row)
{
    m_selection.m_top = topmostInSelection(row);
    m_selection.m_bottom = bottommostInSelection(row);
    m_selection.m_selected_indexes = selectedIndexes();
    return m_selection;
}

QList<int> PlayListModel::selectedIndexes() const
{
    QList<int> selected_rows;
    for(int i = 0; i < m_container->count(); i++)
    {
        if(m_container->item(i)->isSelected())
        {
            selected_rows.append(i);
        }
    }
    return selected_rows;
}

QList<PlayListTrack *> PlayListModel::selectedTracks() const
{
    QList<PlayListTrack*> selected_tracks;
    for(PlayListItem *item : m_container->items())
    {
        if(!item->isGroup() && item->isSelected())
            selected_tracks.append(static_cast<PlayListTrack *>(item));
    }
    return selected_tracks;
}

QList<PlayListItem *> PlayListModel::items() const
{
    return m_container->items();
}

void PlayListModel::addToQueue()
{
    const QList<PlayListTrack*> selected_tracks = selectedTracks();
    blockSignals(true);
    for(PlayListTrack *track : qAsConst(selected_tracks))
        setQueued(track);
    blockSignals(false);
    emit listChanged(QUEUE);
}

void PlayListModel::setQueued(PlayListTrack *t)
{
    if(t->isQueued())
        m_container->removeFromQueue(t);
    else
        m_container->enqueue(t);
    emit listChanged(QUEUE);
}

const QList<PlayListTrack *> &PlayListModel::queuedTracks() const
{
    return m_container->queuedTracks();
}

bool PlayListModel::isEmptyQueue() const
{
    return m_container->queuedTracks().isEmpty();
}

int PlayListModel::queueSize() const
{
    return m_container->queuedTracks().count();
}

bool PlayListModel::isStopAfter(const PlayListItem *item) const
{
    return m_stop_track == item;
}

void PlayListModel::randomizeList()
{
    if(m_container->isEmpty())
        return;
    m_container->randomizeList();
    m_current = m_container->indexOf(m_current_track);
    emit listChanged(STRUCTURE);
}

void PlayListModel::reverseList()
{
    if(m_container->isEmpty())
        return;
    m_container->reverseList();
    m_current = m_container->indexOf(m_current_track);
    emit listChanged(STRUCTURE);
}

void PlayListModel::sortSelection(SortMode mode)
{
    if(m_container->isEmpty())
        return;

    m_task->sortSelection(m_container->tracks(), mode);
}

void PlayListModel::sort(SortMode mode)
{
    if(m_container->isEmpty())
        return;

    m_task->sort(m_container->tracks(), mode);
}

void PlayListModel::sortByColumn(int column)
{
    if(m_container->isEmpty())
        return;

    if(column < 0 || column >= columnCount())
        return;

    m_task->sortByColumn(m_container->tracks(), column);
}

void PlayListModel::prepareForShufflePlaying(bool val)
{
    if(m_play_state)
        delete m_play_state;

    if(val)
        m_play_state = new ShufflePlayState(this);
    else
        m_play_state = new NormalPlayState(this);
}

void PlayListModel::prepareGroups(bool enabled)
{
    PlayListContainer *container = nullptr;
    if(enabled)
        container = new GroupedContainer;
    else
        container = new NormalContainer;
    container->addTracks(m_container->takeAllTracks());
    delete m_container;
    m_container = container;
    if(!m_container->isEmpty())
        m_current = m_container->indexOf(m_current_track);
    emit listChanged(STRUCTURE);
}

void PlayListModel::updateMetaData()
{
    emit listChanged(METADATA);
}

void PlayListModel::onTaskFinished()
{
    if(m_task->isChanged(m_container)) //update unchanged container only
    {
        m_task->clear();
        return;
    }

    QList<PlayListTrack *> queuedTracks = m_container->queuedTracks();

    if(m_task->type() == PlayListTask::SORT || m_task->type() == PlayListTask::SORT_SELECTION)
    {
        m_container->replaceTracks(m_task->takeResults(&m_current_track));
        m_container->restoreQueue(queuedTracks);
        m_current = m_container->indexOf(m_current_track);
        emit listChanged(STRUCTURE);
    }
    else if(m_task->type() == PlayListTask::SORT_BY_COLUMN)
    {
        m_container->replaceTracks(m_task->takeResults(&m_current_track));
        m_container->restoreQueue(queuedTracks);
        m_current = m_container->indexOf(m_current_track);
        emit listChanged(STRUCTURE);
        emit sortingByColumnFinished(m_task->column(), m_task->isReverted());
    }
    else if(m_task->type() == PlayListTask::REMOVE_INVALID
            || m_task->type() == PlayListTask::REMOVE_DUPLICATES
            || m_task->type() == PlayListTask::REFRESH)
    {
        PlayListTrack *prev_current_track = m_current_track;
        int prev_count = m_container->count();

        m_container->replaceTracks(m_task->takeResults(&m_current_track));

        int flags = METADATA;
        if(prev_count != m_container->count())
        {
            flags = STRUCTURE;
            m_current = m_container->indexOf(m_current_track);
            if(prev_current_track != m_current_track)
                flags |= CURRENT;

            if(m_stop_track && !m_container->contains(m_stop_track))
            {
                m_stop_track = nullptr;
                flags |= STOP_AFTER;
            }

            //remove deleted tracks from queue
            QList<PlayListTrack *>::iterator it = queuedTracks.begin();
            while(it != queuedTracks.end())
            {
                if(!m_container->contains(*it))
                {
                    flags |= QUEUE;
                    it = queuedTracks.erase(it);
                }
                else
                {
                    ++it;
                }
            }
        }
        m_container->restoreQueue(queuedTracks);
        emit listChanged(flags);
    }
}

void PlayListModel::updateMetaData(const QStringList &paths)
{
    if(m_container->isEmpty())
        return;

    QList<PlayListTrack *> tracksToRemove;
    QList<PlayListTrack *> tracksToAdd;

    QHash<QString, TrackInfo *> cache; //cache for tracks
    QSet<QString> multiTrackFiles; //files with multiple tracks

    for(const QString &path : qAsConst(paths))
    {
        bool missing = false; //track is missing

        //update cache
        if(!cache.contains(path))
        {
            //is it track of local file?
            if(path.contains("://") && path.contains("#") && !cache.contains(path))
            {
                QString filePath = path;
                filePath.remove(QRegularExpression("#\\d+$"));
                filePath.remove(QRegularExpression("^\\D+://"));
                if(multiTrackFiles.contains(filePath)) //looks like local file has been already scanned, but has not this track
                {
                    missing = true;
                }
                else if(QFileInfo(filePath).isFile())
                {
                    const QList<TrackInfo *> list = MetaDataManager::instance()->createPlayList(filePath);
                    for(TrackInfo *info : qAsConst(list))
                        cache.insert(info->path(), info);

                    multiTrackFiles << path;
                }
            }
            else if(QFileInfo(path).isFile()) //is it local file?
            {
                const QList<TrackInfo *> list = MetaDataManager::instance()->createPlayList(path);
                for(TrackInfo *info : qAsConst(list))
                    cache.insert(info->path(), info);
            }
        }

        QList<TrackInfo *> list;
        if(cache.contains(path)) //using TrackInfo object from cache
            list << cache.value(path);
        else if(!missing)
            list << MetaDataManager::instance()->createPlayList(path);

        for(int i = 0; i < m_container->count(); ++i)
        {
            PlayListTrack *track = m_container->track(i);
            if(!track)
                continue;

            if(track->path() == path)
            {
                if(list.isEmpty()) //track is not available
                    tracksToRemove << track;
                else if(list.count() == 1)
                {
                    track->updateMetaData(list.first()); //update single track
                }
                else //replace single track by multiple tracks
                {
                    track->updateMetaData(list.first()); //update existing track
                    delete list.takeFirst();
                    for(const TrackInfo *info : qAsConst(list)) //add remaining tracks
                        tracksToAdd << new PlayListTrack(info);
                }
            }
        }

        if(!cache.contains(path))
            qDeleteAll(list);
    }

    qDeleteAll(cache.values());
    cache.clear();

    if(!tracksToRemove.isEmpty())
        removeTracks(tracksToRemove);
    if(!tracksToAdd.isEmpty())
        add(tracksToAdd);

    updateMetaData();
}

void PlayListModel::doCurrentVisibleRequest()
{
    if(!m_container->isEmpty() && m_current >= 0)
        emit scrollToRequest(currentIndex());
}

void PlayListModel::scrollTo(int index)
{
    if(index >= 0 && index < m_container->count())
        emit scrollToRequest(index);
}

void PlayListModel::loadPlaylist(const QString &f_name)
{
    m_loader->add(f_name);
}

void PlayListModel::loadPlaylist(const QString &fmt, const QByteArray &data)
{
    m_loader->addPlayList(fmt, data);
}

void PlayListModel::savePlaylist(const QString &f_name)
{
    QList <PlayListTrack *> songs;
    for(int i = 0; i < m_container->count(); ++i)
    {
        if(isTrack(i))
            songs << m_container->track(i);
    }
    PlayListParser::savePlayList(songs, f_name);
}

bool PlayListModel::isLoaderRunning() const
{
    return m_loader->isRunning();
}

void PlayListModel::preparePlayState()
{
    m_play_state->prepare();
    m_paths.clear();
    m_paths.squeeze();
}

void PlayListModel::removeInvalidTracks()
{
    m_task->removeInvalidTracks(m_container->tracks(), m_current_track);
}

void PlayListModel::removeDuplicates()
{
    m_task->removeDuplicates(m_container->tracks(), m_current_track);
}

void PlayListModel::refresh()
{
    m_task->refresh(m_container->tracks(), m_current_track);
}

void PlayListModel::clearQueue()
{
     m_container->clearQueue();
     m_stop_track = nullptr;
     emit listChanged(QUEUE);
}

void PlayListModel::stopAfterSelected()
{
    QList<PlayListTrack*> selected_tracks = selectedTracks();

    if(!isEmptyQueue())
    {
        m_stop_track = m_stop_track != m_container->queuedTracks().last() ? m_container->queuedTracks().last() : nullptr;
        emit listChanged(STOP_AFTER);
    }
    else if(selected_tracks.count() == 1)
    {
        m_stop_track = m_stop_track != selected_tracks.first() ? selected_tracks.first() : nullptr;
        emit listChanged(STOP_AFTER);
    }
    else if(selected_tracks.count() > 1)
    {
        blockSignals(true);
        addToQueue();
        blockSignals(false);
        m_stop_track = m_container->queuedTracks().last();
        emit listChanged(STOP_AFTER | QUEUE);
    }
}

void PlayListModel::rebuildGroups()
{
    if(m_ui_settings->isGroupsEnabled())
        prepareGroups(true);
}
