/*
 * 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 "device.h"
#include "config.h"
#include "gui/covers.h"
#include "mpd-interface/mpdconnection.h"
#include "support/utils.h"
#include <QBuffer>
#include <QDir>
#include <QTemporaryFile>
#include <QTimer>
#ifdef ENABLE_DEVICES_SUPPORT
#include "models/devicesmodel.h"
#include "umsdevice.h"
#ifdef MTP_FOUND
#include "mtpdevice.h"
#endif// MTP_FOUND
#if defined CDDB_FOUND || defined MusicBrainz5_FOUND
#include "audiocddevice.h"
#endif// defined CDDB_FOUND || defined MusicBrainz5_FOUND
#include "encoders.h"
#include "models/musiclibraryitemalbum.h"
#include "models/musiclibraryitemartist.h"
#include "models/musiclibraryitemsong.h"
#include "models/musiclibrarymodel.h"
#include "mpd-interface/mpdparseutils.h"
#include "mpd-interface/song.h"
#include "solid-lite/genericinterface.h"
#include "solid-lite/opticaldisc.h"
#include "solid-lite/portablemediaplayer.h"
#include "solid-lite/storageaccess.h"
#include "solid-lite/storagedrive.h"
#include "tags/tags.h"
#include "widgets/icons.h"
#endif// ENABLE_DEVICES_SUPPORT

#include <QDebug>
#define DBUG_CLASS(CLASS) \
	if (DevicesModel::debugEnabled()) qWarning() << CLASS << __FUNCTION__
#define DBUG DBUG_CLASS(metaObject()->className())

void Device::moveDir(const QString& from, const QString& to, const QString& base, const QString& coverFile)
{
	QDir d(from);
	if (d.exists()) {
		QFileInfoList entries = d.entryInfoList(QDir::Files | QDir::NoSymLinks | QDir::Dirs | QDir::NoDotAndDotDot);
		QSet<QString> fileTypes = QSet<QString>() << "jpg"
												  << "png"
												  << "pamp"
												  << "txt"
												  << "lyrics";

		for (const QFileInfo& info : entries) {
			if (!info.isDir() && (MPDConnection::isPlaylist(info.fileName()) || fileTypes.contains(info.suffix().toLower()))) {
				QFile::rename(from + '/' + info.fileName(), to + '/' + info.fileName());
			}
		}
		cleanDir(from, base, coverFile);
	}
}

void Device::cleanDir(const QString& dir, const QString& base, const QString& coverFile, int level)
{
	QDir d(dir);
	if (d.exists()) {
		QFileInfoList entries = d.entryInfoList(QDir::Files | QDir::NoSymLinks | QDir::Dirs | QDir::NoDotAndDotDot);
		QList<QString> extraFiles;
		QSet<QString> others = Utils::listToSet(Covers::standardNames());
		others << coverFile << "albumart.pamp";

		for (const QFileInfo& info : entries) {
			if (info.isDir()) {
				return;
			}
			if (!others.contains(info.fileName())) {
				return;
			}
			extraFiles.append(info.absoluteFilePath());
		}

		for (const QString& cf : extraFiles) {
			if (!QFile::remove(cf)) {
				return;
			}
		}

		if (Utils::fixPath(dir) == Utils::fixPath(base)) {
			return;
		}
		QString dirName = d.dirName();
		if (dirName.isEmpty()) {
			return;
		}
		d.cdUp();
		if (!d.rmdir(dirName)) {
			return;
		}
		if (level >= 3) {
			return;
		}
		QString upDir = d.absolutePath();
		if (Utils::fixPath(upDir) != Utils::fixPath(base)) {
			cleanDir(upDir, base, coverFile, level + 1);
		}
	}
}

Song Device::fixPath(const Song& orig, bool fullPath) const
{
	Song s = orig;
	if (fullPath) {
		QString p = path();
		if (!p.isEmpty()) {
			s.file = p + s.file;
		}
	}
	return s;
}

#ifdef ENABLE_DEVICES_SUPPORT

#include <unistd.h>

const QLatin1String Device::constNoCover("-");
const QLatin1String Device::constEmbedCover("+");

Device* Device::create(MusicLibraryModel* m, const QString& udi)
{
	Solid::Device device = Solid::Device(udi);

	DBUG_CLASS("Device") << udi;
	if (device.is<Solid::OpticalDisc>()) {
#if defined CDDB_FOUND || defined MusicBrainz5_FOUND
		DBUG_CLASS("Device") << "AudioCD";
		return Encoders::getAvailable().isEmpty() ? 0 : new AudioCdDevice(m, device);
#endif
	}
	else if (device.is<Solid::PortableMediaPlayer>()) {
#ifdef MTP_FOUND
		Solid::PortableMediaPlayer* pmp = device.as<Solid::PortableMediaPlayer>();

		if (pmp->supportedProtocols().contains(QLatin1String("mtp"))) {

			Solid::GenericInterface* iface = device.as<Solid::GenericInterface>();

			if (iface) {
				QMap<QString, QVariant> properties = iface->allProperties();
				QMap<QString, QVariant>::ConstIterator bus = properties.find(QLatin1String("BUSNUM"));
				QMap<QString, QVariant>::ConstIterator dev = properties.find(QLatin1String("DEVNUM"));

				DBUG_CLASS("Device") << "MTP";
				return properties.constEnd() == bus || properties.end() == dev
						? nullptr
						: new MtpDevice(m, device, bus.value().toUInt(), dev.value().toUInt());
			}
		}
#endif
	}
	else if (device.is<Solid::StorageAccess>()) {
		DBUG_CLASS("Device") << "Storage";

		if ((!device.parent().as<Solid::StorageDrive>() || Solid::StorageDrive::Usb != device.parent().as<Solid::StorageDrive>()->bus()) && (!device.as<Solid::StorageDrive>() || Solid::StorageDrive::Usb != device.as<Solid::StorageDrive>()->bus())) {
			DBUG_CLASS("Device") << "Not drive / usb ?";
			return nullptr;
		}

		DBUG_CLASS("Device") << "UMS, Vendor:" << device.vendor();
		return new UmsDevice(m, device);
	}
	return nullptr;
}

bool Device::fixVariousArtists(const QString& file, Song& song, bool applyFix)
{
	Song orig = song;
	if (!file.isEmpty() && song.albumartist.isEmpty()) {
		song = Tags::read(file);
	}

	if (song.artist.isEmpty() || song.albumartist.isEmpty() || !Song::isVariousArtists(song.albumartist)) {
		song = orig;
		return false;
	}

	bool needsUpdating = false;

	if (!applyFix) {// Then real artist is embedded in track title...
		needsUpdating = song.revertVariousArtists();
	}
	else if (applyFix) {// We must be copying to device, and need to place song artist into title...
		needsUpdating = song.fixVariousArtists();
	}

	if (needsUpdating && (file.isEmpty() || Tags::Update_Modified == Tags::updateArtistAndTitle(file, song))) {
		return true;
	}
	song = orig;
	return false;
}

static QByteArray save(const QImage& img)
{
	QByteArray ba;
	QBuffer buffer(&ba);
	buffer.open(QIODevice::WriteOnly);
	img.save(&buffer, "JPG");
	buffer.close();
	return ba;
}

void Device::embedCover(const QString& file, Song& song, unsigned int coverMaxSize)
{
	if (Tags::readImage(file).isNull()) {
		Covers::Image coverImage = Covers::self()->getImage(song);
		if (!coverImage.img.isNull()) {
			QByteArray imgData;
			if (coverMaxSize && (coverImage.img.width() > (int)coverMaxSize || coverImage.img.height() > (int)coverMaxSize)) {
				imgData = save(coverImage.img.scaled(QSize(coverMaxSize, coverMaxSize), Qt::KeepAspectRatio, Qt::SmoothTransformation));
			}
			else if (!coverImage.fileName.endsWith(".jpg", Qt::CaseInsensitive) || !QFile::exists(coverImage.fileName)) {
				imgData = save(coverImage.img);
			}
			else {
				QFile f(coverImage.fileName);
				if (f.open(QIODevice::ReadOnly)) {
					imgData = f.readAll();
				}
				else {
					imgData = save(coverImage.img);
				}
			}
			Tags::embedImage(file, imgData);
		}
	}
}

QTemporaryFile* Device::copySongToTemp(Song& song)
{
	QTemporaryFile* temp = new QTemporaryFile();

	int index = song.file.lastIndexOf('.');
	if (index > 0) {
		temp = new QTemporaryFile("cantata_XXXXXX" + song.file.mid(index));
	}
	else {
		temp = new QTemporaryFile("cantata_XXXXXX");
	}

	temp->setAutoRemove(false);
	if (temp->open()) {
		temp->close();
		if (QFile::exists(temp->fileName())) {
			QFile::remove(temp->fileName());// Copy will *not* overwrite file!
		}
		if (!QFile::copy(song.file, temp->fileName())) {
			temp->remove();
			delete temp;
			temp = nullptr;
		}
	}
	return temp;
}

bool Device::isLossless(const QString& file)
{
	static QStringList lossless = QStringList() << ".flac"
												<< ".wav";// TODO: ALAC?? Its in .m4a!!!
	QString lower = file.toLower();
	for (const auto& e : lossless) {
		if (lower.endsWith(e)) {
			return true;
		}
	}
	return false;
}

Device::Device(MusicLibraryModel* m, Solid::Device& dev, bool albumArtistSupport, bool flat)
	: MusicLibraryItemRoot(dev.product().startsWith(dev.vendor()) ? dev.product() : (dev.vendor() + QChar(' ') + dev.product()), albumArtistSupport, flat), configured(false), solidDev(dev), deviceId(dev.udi()), update(nullptr), needToFixVa(false), jobAbortRequested(false), transcoding(false)
{
	m_model = m;
	icn = Icons::self()->folderListIcon;
	m_itemData[0] = m_itemData[0].toUpper();
}

Device::Device(MusicLibraryModel* m, const QString& name, const QString& id)
	: MusicLibraryItemRoot(name), configured(false), deviceId(id), update(nullptr), needToFixVa(false), jobAbortRequested(false), transcoding(false)
{
	m_model = m;
	icn = solidDev.isValid() ? Icon::get(solidDev.icon()) : Icons::self()->folderListIcon;
}

void Device::saveCache()
{
	QTimer::singleShot(0, this, SIGNAL(cacheSaved()));
}

void Device::applyUpdate()
{
	if (!update) {
		return;
	}
	/*if (m_childItems.count() && update && update->childCount()) {
        QSet<Song> currentSongs=allSongs();
        QSet<Song> updateSongs=update->allSongs();
        QSet<Song> removed=currentSongs-updateSongs;
        QSet<Song> added=updateSongs-currentSongs;

        for (const Song &s: removed) {
            removeSongFromList(s);
        }
        for (const Song &s: added) {
            addSongToList(s);
        }
        delete update;
    } else*/
	{
		int oldCount = childCount();
		if (oldCount > 0) {
			m_model->beginRemoveRows(index(), 0, oldCount - 1);
			clearItems();
			m_model->endRemoveRows();
		}
		int newCount = newRows();
		if (newCount > 0) {
			m_model->beginInsertRows(index(), 0, newCount - 1);
			foreach (MusicLibraryItem* item, update->childItems()) {
				item->setParent(this);
			}
			if (AudioCd != devType()) {
				refreshIndexes();
			}
			m_model->endInsertRows();
		}
	}
	delete update;
	update = nullptr;
}

QModelIndex Device::index() const
{
	return m_model->createIndex(m_model->row((void*)this), 0, (void*)this);
}

void Device::setStatusMessage(const QString& msg)
{
	statusMsg = msg;
	updateStatus();
}

void Device::updateStatus()
{
	QModelIndex modelIndex = index();
	emit m_model->dataChanged(modelIndex, modelIndex);
}

void Device::songCount(int c)
{
	setStatusMessage(tr("Updating (%1)...").arg(c));
}

void Device::updatePercentage(int pc)
{
	setStatusMessage(tr("Updating (%1%)...").arg(pc));
}

#endif// ENABLE_DEVICES_SUPPORT

#include "moc_device.cpp"
