/*
 * Cantata
 *
 * Copyright (c) 2017-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 "browsemodel.h"
#include "gui/settings.h"
#include "mpd-interface/mpdconnection.h"
#include "mpd-interface/mpdstats.h"
#include "playqueuemodel.h"
#include "roles.h"
#include "support/utils.h"
#include "widgets/icons.h"
#include <QMimeData>

void BrowseModel::FolderItem::add(Item* i)
{
	i->setRow(children.count());
	children.append(i);
}

QStringList BrowseModel::FolderItem::allEntries(bool allowPlaylists) const
{
	QStringList entries;
	if (children.isEmpty()) {
		entries << MPDConnection::constDirPrefix + path;
	}
	else {
		for (Item* i : children) {
			if (i->isFolder()) {
				entries += static_cast<FolderItem*>(i)->allEntries(allowPlaylists);
			}
			else if (allowPlaylists || Song::Playlist != static_cast<TrackItem*>(i)->getSong().type) {
				entries += static_cast<TrackItem*>(i)->getSong().file;
			}
		}
	}
	return entries;
}

BrowseModel::BrowseModel(QObject* p)
	: ActionModel(p), root(new FolderItem("/", nullptr)), enabled(false), dbVersion(0)
{
	icn = Icon::fa(fa::fa_solid, fa::fa_server);
	connect(this, SIGNAL(listFolder(QString)), MPDConnection::self(), SLOT(listFolder(QString)));
	folderIndex.insert(root->getPath(), root);
}

QString BrowseModel::name() const
{
	return QLatin1String("mpdbrowse");
}

QString BrowseModel::title() const
{
	return tr("Server Folders");
}

QString BrowseModel::descr() const
{
	return tr("MPD virtual file-system");
}

void BrowseModel::clear()
{
	beginResetModel();
	root->clear();
	folderIndex.clear();
	folderIndex.insert(root->getPath(), root);
	endResetModel();
}

void BrowseModel::load()
{
	if (!enabled || (root && (root->getChildCount() || root->isFetching()))) {
		return;
	}
	root->setState(FolderItem::State_Fetching);
	emit listFolder(root->getPath());
}

void BrowseModel::setEnabled(bool e)
{
	if (e == enabled) {
		return;
	}
	enabled = e;

	if (enabled) {
		connect(MPDConnection::self(), SIGNAL(folderContents(QString, QStringList, QList<Song>)), this, SLOT(folderContents(QString, QStringList, QList<Song>)));
		connect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged()));
		connect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues)));
	}
	else {
		disconnect(MPDConnection::self(), SIGNAL(folderContents(QString, QStringList, QList<Song>)), this, SLOT(folderContents(QString, QStringList, QList<Song>)));
		disconnect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged()));
		disconnect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues)));
		clear();
	}
}

Qt::ItemFlags BrowseModel::flags(const QModelIndex& index) const
{
	if (index.isValid()) {
		return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled;
	}
	return Qt::ItemIsDropEnabled;
}

QModelIndex BrowseModel::index(int row, int column, const QModelIndex& parent) const
{
	if (!hasIndex(row, column, parent)) {
		return QModelIndex();
	}
	const FolderItem* p = parent.isValid() ? static_cast<FolderItem*>(parent.internalPointer()) : root;
	const Item* c = row < p->getChildCount() ? p->getChildren().at(row) : nullptr;
	return c ? createIndex(row, column, (void*)c) : QModelIndex();
}

QModelIndex BrowseModel::parent(const QModelIndex& child) const
{
	if (!child.isValid()) {
		return QModelIndex();
	}

	const Item* const item = static_cast<Item*>(child.internalPointer());
	Item* const parentItem = item->getParent();
	if (parentItem == root || nullptr == parentItem) {
		return QModelIndex();
	}

	return createIndex(parentItem->getRow(), 0, parentItem);
}

int BrowseModel::rowCount(const QModelIndex& parent) const
{
	if (parent.column() > 0) {
		return 0;
	}

	const FolderItem* parentItem = parent.isValid() ? static_cast<FolderItem*>(parent.internalPointer()) : root;
	return parentItem ? parentItem->getChildCount() : 0;
}

int BrowseModel::columnCount(const QModelIndex& parent) const
{
	Q_UNUSED(parent)
	return 1;
}

bool BrowseModel::hasChildren(const QModelIndex& index) const
{
	Item* item = toItem(index);
	return item && item->isFolder();
}

bool BrowseModel::canFetchMore(const QModelIndex& index) const
{
	if (index.isValid()) {
		Item* item = toItem(index);
		return item && item->isFolder() && static_cast<FolderItem*>(item)->canFetchMore();
	}
	else {
		return false;
	}
}

void BrowseModel::fetchMore(const QModelIndex& index)
{
	if (!index.isValid()) {
		return;
	}

	FolderItem* item = static_cast<FolderItem*>(toItem(index));
	if (!item->isFetching()) {
		item->setState(FolderItem::State_Fetching);
		emit listFolder(item->getPath());
	}
}

QVariant BrowseModel::data(const QModelIndex& index, int role) const
{
	if (!index.isValid()) {
		switch (role) {
		case Cantata::Role_TitleText:
			return title();
		case Cantata::Role_SubText:
			return descr();
		case Qt::DecorationRole:
			return icon();
		}
		return QVariant();
	}

	Item* item = static_cast<Item*>(index.internalPointer());

	switch (role) {
	case Qt::DecorationRole:
		if (item->isFolder()) {
			return Icons::self()->folderListIcon;
		}
		else {
			TrackItem* track = static_cast<TrackItem*>(item);
			return Song::Playlist == track->getSong().type ? Icons::self()->playlistListIcon : Icons::self()->audioListIcon;
		}
		break;
	case Cantata::Role_BriefMainText:
	case Cantata::Role_MainText:
	case Qt::DisplayRole:
		if (!item->isFolder()) {
			TrackItem* track = static_cast<TrackItem*>(item);
			if (Song::Playlist == track->getSong().type) {
				return Utils::getFile(track->getSong().file);
			}
		}
		return item->getText();
	case Qt::ToolTipRole:
		if (!Settings::self()->infoTooltips()) {
			return QVariant();
		}
		if (item->isFolder() || Song::Playlist == static_cast<TrackItem*>(item)->getSong().type) {
			return static_cast<FolderItem*>(item)->getPath();
		}
		return static_cast<TrackItem*>(item)->getSong().toolTip();
	case Cantata::Role_SubText:
		if (!item->isFolder()) {
			TrackItem* track = static_cast<TrackItem*>(item);
			if (Song::Playlist == track->getSong().type) {
				return track->getSong().isCueFile() ? tr("Cue Sheet") : tr("Playlist");
			}
		}
		return item->getSubText();
	case Cantata::Role_TitleText:
		return item->getText();
	case Cantata::Role_TitleActions:
		return item->isFolder();
	default:
		break;
	}
	return ActionModel::data(index, role);
}

QMimeData* BrowseModel::mimeData(const QModelIndexList& indexes) const
{
	QMimeData* mimeData = new QMimeData();
	QStringList files;
	for (const QModelIndex& idx : indexes) {
		Item* item = toItem(idx);
		if (item) {
			if (item->isFolder()) {
				files += static_cast<FolderItem*>(item)->allEntries(false);
			}
			else {
				files.append(static_cast<TrackItem*>(item)->getSong().file);
			}
		}
	}

	PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files);
	return mimeData;
}

QList<Song> BrowseModel::songs(const QModelIndexList& indexes, bool allowPlaylists) const
{
	QList<Song> songList;
	for (const QModelIndex& idx : indexes) {
		Item* item = toItem(idx);
		if (item && !item->isFolder() && (allowPlaylists || Song::Standard == static_cast<TrackItem*>(item)->getSong().type)) {
			songList.append(static_cast<TrackItem*>(item)->getSong());
		}
	}
	return songList;
}

void BrowseModel::connectionChanged()
{
	clear();
	if (!root->isFetching() && MPDConnection::self()->isConnected()) {
		dbVersion = 0;
		root->setState(FolderItem::State_Fetching);
		emit listFolder(root->getPath());
	}
}

void BrowseModel::statsUpdated(const MPDStatsValues& stats)
{
	if (stats.dbUpdate != dbVersion) {
		if (0 != dbVersion) {
			connectionChanged();
		}
		dbVersion = stats.dbUpdate;
	}
}

void BrowseModel::folderContents(const QString& path, const QStringList& folders, const QList<Song>& songs)
{
	QMap<QString, FolderItem*>::Iterator it = folderIndex.find(path);
	if (it == folderIndex.end() || 0 != it.value()->getChildCount()) {
		return;
	}

	if (folders.count() + songs.count()) {
		QModelIndex idx = it.value() == root ? QModelIndex() : createIndex(it.value()->getRow(), 0, it.value());

		beginInsertRows(idx, 0, folders.count() + songs.count() - 1);
		for (const QString& folder : folders) {
			FolderItem* item = new FolderItem(folder.split("/", CANTATA_SKIP_EMPTY).last(), folder, it.value());
			it.value()->add(item);
			folderIndex.insert(folder, item);
		}
		for (const Song& song : songs) {
			it.value()->add(new TrackItem(song, it.value()));
		}
		it.value()->setState(FolderItem::State_Fetched);
		endInsertRows();
	}
}

#include "moc_browsemodel.cpp"
