/*
 * Cantata
 *
 * Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
 *
 * ----
 *
 * 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "storedplaylistspage.h"
#include "gui/customactions.h"
#include "gui/settings.h"
#include "gui/stdactions.h"
#include "models/playlistsmodel.h"
#include "mpd-interface/mpdconnection.h"
#include "support/actioncollection.h"
#include "support/configuration.h"
#include "support/inputdialog.h"
#include "support/messagebox.h"
#include "widgets/icons.h"
#include "widgets/menubutton.h"
#include "widgets/spacerwidget.h"
#include "widgets/tableview.h"
#include <QMenu>
#include <QStyle>

class PlaylistTableView : public TableView {
public:
	PlaylistTableView(QWidget* p)
		: TableView(QLatin1String("playlist"), p)
	{
		setUseSimpleDelegate();
		setIndentation(fontMetrics().horizontalAdvance(QLatin1String("XX")));
	}

	~PlaylistTableView() override {}
};

StoredPlaylistsPage::StoredPlaylistsPage(QWidget* p)
	: SinglePageWidget(p)
{
	renamePlaylistAction = new Action(Icons::self()->editIcon, tr("Rename"), this);
	removeDuplicatesAction = new Action(tr("Remove Duplicates"), this);
	removeDuplicatesAction->setEnabled(false);
	removeInvalidAction = new Action(tr("Remove Invalid Tracks"), this);
	removeInvalidAction->setEnabled(false);

	view->allowGroupedView();
	view->allowTableView(new PlaylistTableView(view));
	view->setAcceptDrops(true);
	view->setDragDropOverwriteMode(false);
	view->setDragDropMode(QAbstractItemView::DragDrop);

	proxy.setSourceModel(PlaylistsModel::self());
	view->setModel(&proxy);
	view->setDeleteAction(StdActions::self()->removeAction);
	view->alwaysShowHeader();
	connect(view, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(itemDoubleClicked(const QModelIndex&)));
	connect(view, SIGNAL(itemsSelected(bool)), SLOT(controlActions()));
	connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int)));
	connect(this, SIGNAL(loadPlaylist(const QString&, bool)), MPDConnection::self(), SLOT(loadPlaylist(const QString&, bool)));
	connect(this, SIGNAL(removePlaylist(const QString&)), MPDConnection::self(), SLOT(removePlaylist(const QString&)));
	connect(this, SIGNAL(savePlaylist(const QString&, bool)), MPDConnection::self(), SLOT(savePlaylist(const QString&, bool)));
	connect(this, SIGNAL(renamePlaylist(const QString&, const QString&)), MPDConnection::self(), SLOT(renamePlaylist(const QString&, const QString&)));
	connect(this, SIGNAL(removeFromPlaylist(const QString&, const QList<quint32>&)), MPDConnection::self(), SLOT(removeFromPlaylist(const QString&, const QList<quint32>&)));
	connect(StdActions::self()->savePlayQueueAction, SIGNAL(triggered()), this, SLOT(savePlaylist()));
	connect(renamePlaylistAction, SIGNAL(triggered()), this, SLOT(renamePlaylist()));
	connect(removeDuplicatesAction, SIGNAL(triggered()), this, SLOT(removeDuplicates()));
	connect(removeInvalidAction, SIGNAL(triggered()), this, SLOT(removeInvalid()));
	connect(PlaylistsModel::self(), SIGNAL(updated(const QModelIndex&)), this, SLOT(updated(const QModelIndex&)));
	connect(PlaylistsModel::self(), SIGNAL(playlistRemoved(quint32)), view, SLOT(collectionRemoved(quint32)));
	intitiallyCollapseAction = new Action(tr("Initially Collapse Albums"), this);
	intitiallyCollapseAction->setCheckable(true);
	Configuration config(metaObject()->className());
	view->setMode(ItemView::Mode_DetailedTree);
	view->load(config);
	intitiallyCollapseAction->setChecked(view->isStartClosed());
	connect(intitiallyCollapseAction, SIGNAL(toggled(bool)), SLOT(setStartClosed(bool)));
	MenuButton* menu = new MenuButton(this);
	menu->addAction(createViewMenu(QList<ItemView::Mode>() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree
	                                                       << ItemView::Mode_DetailedTree << ItemView::Mode_List
	                                                       << ItemView::Mode_GroupedTree << ItemView::Mode_Table));
	menu->addAction(intitiallyCollapseAction);
	init(ReplacePlayQueue | AppendToPlayQueue, QList<QWidget*>() << menu);

	view->addAction(StdActions::self()->addToStoredPlaylistAction);
	view->addAction(CustomActions::self());
#ifdef ENABLE_DEVICES_SUPPORT
	view->addAction(StdActions::self()->copyToDeviceAction);
#endif
	view->addAction(renamePlaylistAction);
	view->addAction(StdActions::self()->removeAction);
	view->addAction(removeDuplicatesAction);
	view->addAction(removeInvalidAction);
	connect(view, SIGNAL(updateToPlayQueue(QModelIndex, bool)), this, SLOT(updateToPlayQueue(QModelIndex, bool)));
	view->setInfoText(tr("Either save the play queue, or use the context menu in the library, to create new playlists."));
}

StoredPlaylistsPage::~StoredPlaylistsPage()
{
	Configuration config(metaObject()->className());
	view->save(config);
}

void StoredPlaylistsPage::updateRows()
{
	view->updateRows();
}

void StoredPlaylistsPage::clear()
{
	PlaylistsModel::self()->clear();
}

//QStringList StoredPlaylistsPage::selectedFiles() const
//{
//    QModelIndexList indexes=view->selectedIndexes();
//    if (indexes.isEmpty()) {
//        return QStringList();
//    }
//
//    QModelIndexList mapped;
//    for (const QModelIndex &idx: indexes) {
//        mapped.append(proxy.mapToSource(idx));
//    }
//
//    return PlaylistsModel::self()->filenames(mapped, true);
//}

void StoredPlaylistsPage::addSelectionToPlaylist(const QString& name, int action, quint8 priority, bool decreasePriority)
{
	addItemsToPlayList(view->selectedIndexes(), name, action, priority, decreasePriority);
}

void StoredPlaylistsPage::setView(int mode)
{
	PlaylistsModel::self()->setMultiColumn(ItemView::Mode_Table == mode);
	SinglePageWidget::setView(mode);
	intitiallyCollapseAction->setEnabled(ItemView::Mode_GroupedTree == mode);
}

void StoredPlaylistsPage::removeItems()
{
	QSet<QString> remPlaylists;
	QMap<QString, QList<quint32>> remSongs;
	QModelIndexList selected = view->selectedIndexes();

	for (const QModelIndex& index : selected) {
		if (index.isValid()) {
			QModelIndex realIndex(proxy.mapToSource(index));

			if (realIndex.isValid()) {
				PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(realIndex.internalPointer());
				if (item->isPlaylist()) {
					PlaylistsModel::PlaylistItem* pl = static_cast<PlaylistsModel::PlaylistItem*>(item);
					if (!pl->isSmartPlaylist) {
						remPlaylists.insert(pl->name);
					}
					if (remSongs.contains(pl->name)) {
						remSongs.remove(pl->name);
					}
				}
				else {
					PlaylistsModel::SongItem* song = static_cast<PlaylistsModel::SongItem*>(item);
					if (!song->parent->isSmartPlaylist && !remPlaylists.contains(song->parent->name)) {
						remSongs[song->parent->name].append(song->parent->songs.indexOf(song));
					}
				}
			}
		}
	}

	// Should not happen!
	if (remPlaylists.isEmpty() && remSongs.isEmpty()) {
		return;
	}

	if (remPlaylists.count() && MessageBox::No == MessageBox::warningYesNo(this, tr("Are you sure you wish to remove the selected playlists?\n\nThis cannot be undone."), tr("Remove Playlists"), StdGuiItem::remove(), StdGuiItem::cancel())) {
		return;
	}

	for (const QString& pl : remPlaylists) {
		emit removePlaylist(pl);
	}
	QMap<QString, QList<quint32>>::ConstIterator it = remSongs.constBegin();
	QMap<QString, QList<quint32>>::ConstIterator end = remSongs.constEnd();
	for (; it != end; ++it) {
		emit removeFromPlaylist(it.key(), it.value());
	}
}

void StoredPlaylistsPage::savePlaylist()
{
	QStringList existing;
	for (int i = 0; i < proxy.rowCount(); ++i) {
		existing.append(proxy.data(proxy.index(i, 0, QModelIndex())).toString());
	}

	QString name = InputDialog::getText(tr("Playlist Name"), tr("Enter a name for the playlist:"), lastPlaylist, existing, nullptr, this);
	if (!name.isEmpty()) {
		lastPlaylist = name;
		emit savePlaylist(name, true);
	}
}

void StoredPlaylistsPage::renamePlaylist()
{
	const QModelIndexList items = view->selectedIndexes(false);// Dont need sorted selection here...

	if (1 == items.size()) {
		QModelIndex index = proxy.mapToSource(items.first());
		PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(index.internalPointer());
		if (!item->isPlaylist()) {
			return;
		}
		QString name = static_cast<PlaylistsModel::PlaylistItem*>(item)->name;
		QString newName = InputDialog::getText(tr("Rename Playlist"), tr("Enter new name for playlist:"), name, nullptr, this);

		if (!newName.isEmpty() && name != newName) {
			if (PlaylistsModel::self()->exists(newName)) {
				if (MessageBox::No == MessageBox::warningYesNo(this, tr("A playlist named '%1' already exists!\n\nOverwrite?").arg(newName), tr("Overwrite Playlist"), StdGuiItem::overwrite(), StdGuiItem::cancel())) {
					return;
				}
				else {
					emit removePlaylist(newName);
				}
			}
			emit renamePlaylist(name, newName);
		}
	}
}

void StoredPlaylistsPage::removeDuplicates()
{
	const QModelIndexList items = view->selectedIndexes(false);// Dont need sorted selection here...

	if (1 == items.size()) {
		QModelIndex index = proxy.mapToSource(items.first());
		PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(index.internalPointer());
		if (!item->isPlaylist()) {
			return;
		}

		PlaylistsModel::PlaylistItem* pl = static_cast<PlaylistsModel::PlaylistItem*>(item);
		QMap<QString, QList<Song>> map;
		for (int i = 0; i < pl->songs.count(); ++i) {
			Song song = *(pl->songs.at(i));
			song.id = i;
			map[song.albumKey() + "-" + song.artistSong() + "-" + song.track].append(song);
		}

		QList<quint32> toRemove;
		for (const QString& key : map.keys()) {
			QList<Song> values = map.value(key);
			if (values.size() > 1) {
				Song::sortViaType(values);
				for (int i = 1; i < values.count(); ++i) {
					toRemove.append(values.at(i).id);
				}
			}
		}
		if (!toRemove.isEmpty()) {
			emit removeFromPlaylist(pl->name, toRemove);
		}
	}
}

void StoredPlaylistsPage::removeInvalid()
{
	const QModelIndexList items = view->selectedIndexes(false);// Dont need sorted selection here...

	if (1 == items.size()) {
		QModelIndex index = proxy.mapToSource(items.first());
		PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(index.internalPointer());
		if (!item->isPlaylist()) {
			return;
		}

		PlaylistsModel::PlaylistItem* pl = static_cast<PlaylistsModel::PlaylistItem*>(item);
		QList<quint32> toRemove;
		for (quint32 i = 0; i < (quint32)pl->songs.count(); ++i) {
			const auto song = pl->songs.at(i);
			if (song->isInvalid()) {
				toRemove.append(i);
			}
		}
		if (!toRemove.isEmpty()) {
			emit removeFromPlaylist(pl->name, toRemove);
		}
	}
}

void StoredPlaylistsPage::itemDoubleClicked(const QModelIndex& index)
{
	if (index.isValid() && (style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this) || !static_cast<PlaylistsModel::Item*>(proxy.mapToSource(index).internalPointer())->isPlaylist())) {
		QModelIndexList indexes;
		indexes.append(index);
		addItemsToPlayList(indexes, QString(), MPDConnection::Append);
	}
}

void StoredPlaylistsPage::addItemsToPlayList(const QModelIndexList& indexes, const QString& name, int action, quint8 priority, bool decreasePriority)
{
	if (indexes.isEmpty()) {
		return;
	}

	// If we only have 1 item selected, see if it is a playlist. If so, we might be able to
	// just ask MPD to load it...
	if (name.isEmpty() && 1 == indexes.count() && 0 == priority && !proxy.enabled() && MPDConnection::Append == action) {
		QModelIndex idx = proxy.mapToSource(*(indexes.begin()));
		PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(idx.internalPointer());

		if (item->isPlaylist()) {
			lastPlaylist = static_cast<PlaylistsModel::PlaylistItem*>(item)->name;
			emit loadPlaylist(lastPlaylist, false);
			return;
		}
	}

	if (!name.isEmpty()) {
		lastPlaylist.clear();
		for (const QModelIndex& idx : indexes) {
			QModelIndex m = proxy.mapToSource(idx);
			PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(m.internalPointer());
			if ((item->isPlaylist() && static_cast<PlaylistsModel::PlaylistItem*>(item)->name == name) || (!item->isPlaylist() && static_cast<PlaylistsModel::SongItem*>(item)->parent->name == name)) {
				MessageBox::error(this, tr("Cannot add songs from '%1' to '%2'").arg(name).arg(name));
				return;
			}
		}
	}

	QStringList files = PlaylistsModel::self()->filenames(proxy.mapToSource(indexes));
	if (!files.isEmpty()) {
		if (name.isEmpty()) {
			emit add(files, action, priority, decreasePriority);
		}
		else {
			emit addSongsToPlaylist(name, files);
		}
		view->clearSelection();
	}
}

#ifdef ENABLE_DEVICES_SUPPORT
QList<Song> StoredPlaylistsPage::selectedSongs(bool allowPlaylists) const
{
	Q_UNUSED(allowPlaylists)
	QModelIndexList selected = view->selectedIndexes();
	if (selected.isEmpty()) {
		return QList<Song>();
	}
	return PlaylistsModel::self()->songs(proxy.mapToSource(selected));
}

void StoredPlaylistsPage::addSelectionToDevice(const QString& udi)
{
	QList<Song> songs = selectedSongs();
	if (!songs.isEmpty()) {
		emit addToDevice(QString(), udi, songs);
		view->clearSelection();
	}
}
#endif

void StoredPlaylistsPage::controlActions()
{
	QModelIndexList selected = view->selectedIndexes(false);// Dont need sorted selection here...
	bool enableActions = selected.count() > 0;
	bool allSmartPlaylists = false;
	bool canRename = false;

	if (1 == selected.count()) {
		QModelIndex index = proxy.mapToSource(selected.first());
		PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(index.internalPointer());
		if (item) {
			if (item->isPlaylist()) {
				if (static_cast<PlaylistsModel::PlaylistItem*>(item)->isSmartPlaylist) {
					allSmartPlaylists = true;
				}
				else {
					canRename = true;
				}
			}
			else if (static_cast<PlaylistsModel::SongItem*>(item)->parent->isSmartPlaylist) {
				allSmartPlaylists = true;
			}
		}
	}
	else if (selected.count() <= 200) {
		allSmartPlaylists = true;
		for (const QModelIndex& index : selected) {
			PlaylistsModel::Item* item = static_cast<PlaylistsModel::Item*>(proxy.mapToSource(index).internalPointer());
			if (item && !(item->isPlaylist() ? static_cast<PlaylistsModel::PlaylistItem*>(item)->isSmartPlaylist : static_cast<PlaylistsModel::SongItem*>(item)->parent->isSmartPlaylist)) {
				allSmartPlaylists = false;
				break;
			}
		}
	}

	renamePlaylistAction->setEnabled(canRename);
	removeDuplicatesAction->setEnabled(enableActions && !allSmartPlaylists);
	removeInvalidAction->setEnabled(enableActions && !allSmartPlaylists);
	StdActions::self()->removeAction->setEnabled(enableActions && !allSmartPlaylists);
	StdActions::self()->enableAddToPlayQueue(enableActions);
	StdActions::self()->addToStoredPlaylistAction->setEnabled(enableActions);
#ifdef ENABLE_DEVICES_SUPPORT
	StdActions::self()->copyToDeviceAction->setEnabled(enableActions && !allSmartPlaylists);
#endif
}

void StoredPlaylistsPage::doSearch()
{
	QString text = view->searchText().trimmed();
	bool updated = proxy.update(text);
	if (proxy.enabled() && !proxy.filterText().isEmpty()) {
		view->expandAll();
	}
	if (updated) {
		view->updateRows();
	}
}

void StoredPlaylistsPage::updated(const QModelIndex& index)
{
	view->updateRows(proxy.mapFromSource(index));
}

void StoredPlaylistsPage::headerClicked(int level)
{
	if (0 == level) {
		emit close();
	}
}

void StoredPlaylistsPage::setStartClosed(bool sc)
{
	view->setStartClosed(sc);
}

void StoredPlaylistsPage::updateToPlayQueue(const QModelIndex& idx, bool replace)
{
	QStringList files = PlaylistsModel::self()->filenames(proxy.mapToSource(QModelIndexList() << idx, false));
	if (!files.isEmpty()) {
		emit add(files, replace ? MPDConnection::ReplaceAndplay : MPDConnection::Append, 0, false);
	}
}

#include "moc_storedplaylistspage.cpp"
