/*
 * 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 "devicepropertieswidget.h"
#include "filenameschemedialog.h"
#include "gui/covers.h"
#include "support/utils.h"
#include "widgets/icons.h"
#include <QTabWidget>
#include <QTimer>
#include <QValidator>

class CoverNameValidator : public QValidator {
public:
	CoverNameValidator(QObject* parent) : QValidator(parent) {}

	State validate(QString& input, int&) const override
	{
		int dotCount(0);

		for (int i = 0; i < input.length(); ++i) {
			if (QChar('.') == input[i]) {
				if (++dotCount > 1) {
					return Invalid;
				}
			}
			else if (!input[i].isLetterOrNumber() || input[i].isSpace()) {
				return Invalid;
			}
		}
		if (input.endsWith('.')) {
			return Intermediate;
		}

		return Acceptable;
	}

	//     void fixup(QString &input) const
	//     {
	//         QString out;
	//         int dotCount(0);
	//         for (int i=0; i<input.length(); ++i) {
	//             if (input[i].isLetterOrNumber() && !input[i].isSpace()) {
	//                 out+=input[i];
	//             } else if (QChar('.')==input[i] && ++dotCount<1) {
	//                 out+=input[i];
	//             }
	//         }
	//
	//         if (!out.endsWith(".jpg") && !out.endsWith(".png")) {
	//             out.replace(".", "");
	//             out+=".jpg";
	//         }
	//         input=out;
	//     }
};

DevicePropertiesWidget::DevicePropertiesWidget(QWidget* parent)
	: QWidget(parent), schemeDlg(nullptr), noCoverText(tr("Don't copy covers")), embedCoverText(tr("Embed cover within each file")), modified(false), saveable(false)
{
	setupUi(this);
	configFilename->setIcon(Icons::self()->configureIcon);
	coverMaxSize->insertItems(0, QStringList() << tr("No maximum size") << tr("400 pixels") << tr("300 pixels") << tr("200 pixels") << tr("100 pixels"));
	fixVariousArtists->setToolTip(tr("<p>When copying tracks to a device, and the 'Album Artist' is set to 'Various Artists', "
	                                 "then Cantata will set the 'Artist' tag of all tracks to 'Various Artists' and the "
	                                 "track 'Title' tag to 'TrackArtist - TrackTitle'.<hr/> When copying from a device, Cantata "
	                                 "will check if 'Album Artist' and 'Artist' are both set to 'Various Artists'. If so, it "
	                                 "will attempt to extract the real artist from the 'Title' tag, and remove the artist name "
	                                 "from the 'Title' tag.</p>"));

	useCache->setToolTip(tr("<p>If you enable this, then Cantata will create a cache of the device's music library. "
	                        "This will help to speed up subsequent library scans (as the cache file will be used instead of "
	                        "having to read the tags of each file.)<hr/><b>NOTE:</b> If you use another application to update "
	                        "the device's library, then this cache will become out-of-date. To rectify this, simply "
	                        "click on the 'refresh' icon in the device list. This will cause the cache file to be removed, and "
	                        "the contents of the device re-scanned.</p>"));

	if (qobject_cast<QTabWidget*>(parent)) {
		verticalLayout->setContentsMargins(4, 4, 4, 4);
	}
}

#define REMOVE(w)         \
	w->setVisible(false); \
	w->deleteLater();     \
	w = 0;

void DevicePropertiesWidget::update(const QString& path, const DeviceOptions& opts, const QList<DeviceStorage>& storage, int props, int disabledProps)
{
	bool allowCovers = (props & Prop_CoversAll) || (props & Prop_CoversBasic);
	albumCovers->clear();
	if (allowCovers) {
		if (props & Prop_CoversAll) {
			albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText << Covers::standardNames());
		}
		else {
			albumCovers->insertItems(0, QStringList() << noCoverText << embedCoverText);
		}
	}
	if (props & Prop_Name) {
		name->setText(opts.name);
		connect(name, SIGNAL(textChanged(const QString&)), SLOT(checkSaveable()));
	}
	else {
		REMOVE(name)
		REMOVE(nameLabel)
	}
	if (props & Prop_FileName) {
		filenameScheme->setText(opts.scheme);
		vfatSafe->setChecked(opts.vfatSafe);
		asciiOnly->setChecked(opts.asciiOnly);
		ignoreThe->setChecked(opts.ignoreThe);
		replaceSpaces->setChecked(opts.replaceSpaces);
	}
	else {
		REMOVE(filenamesGroupBox)
		filenameScheme = nullptr;
		vfatSafe = nullptr;
		asciiOnly = nullptr;
		ignoreThe = nullptr;
		replaceSpaces = nullptr;
		configFilename = nullptr;
	}
	origOpts = opts;

	if (props & Prop_Folder) {
		musicFolder->setText(Utils::convertPathForDisplay(path));
		connect(musicFolder, SIGNAL(textChanged(const QString&)), this, SLOT(checkSaveable()));
		if (disabledProps & Prop_Folder) {
			musicFolder->setDisabled(true);
		}
	}
	else {
		REMOVE(musicFolder);
		REMOVE(musicFolderLabel);
	}
	if (allowCovers) {
		albumCovers->setEditable(props & Prop_CoversAll);
		if (origOpts.coverName == Device::constNoCover) {
			origOpts.coverName = noCoverText;
			albumCovers->setCurrentIndex(0);
		}
		if (origOpts.coverName == Device::constEmbedCover) {
			origOpts.coverName = embedCoverText;
			albumCovers->setCurrentIndex(1);
		}
		else {
			albumCovers->setCurrentIndex(0);
			for (int i = 1; i < albumCovers->count(); ++i) {
				if (albumCovers->itemText(i) == origOpts.coverName) {
					albumCovers->setCurrentIndex(i);
					break;
				}
			}
		}
		if (0 != origOpts.coverMaxSize) {
			int coverMax = origOpts.coverMaxSize / 100;
			if (coverMax < 0 || coverMax >= coverMaxSize->count()) {
				coverMax = 0;
			}
			coverMaxSize->setCurrentIndex(0 == coverMax ? 0 : (coverMaxSize->count() - coverMax));
		}
		else {
			coverMaxSize->setCurrentIndex(0);
		}
		albumCovers->setValidator(new CoverNameValidator(this));
		connect(albumCovers, SIGNAL(editTextChanged(const QString&)), this, SLOT(albumCoversChanged()));
		connect(coverMaxSize, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
	}
	else {
		REMOVE(albumCovers);
		REMOVE(albumCoversLabel);
		REMOVE(coverMaxSize);
		REMOVE(coverMaxSizeLabel);
	}
	if (props & Prop_Va) {
		fixVariousArtists->setChecked(opts.fixVariousArtists);
		connect(fixVariousArtists, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
	}
	else {
		REMOVE(fixVariousArtists);
	}
	if (props & Prop_Cache) {
		useCache->setChecked(opts.useCache);
		connect(useCache, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
	}
	else {
		REMOVE(useCache);
	}
	if (props & Prop_AutoScan) {
		autoScan->setChecked(opts.autoScan);
		connect(autoScan, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
	}
	else {
		REMOVE(autoScan);
	}

	if (props & Prop_Transcoder || props & Prop_Encoder) {
		bool transcode = props & Prop_Transcoder;
		transcoderName->clear();
		if (transcode) {
			transcoderName->addItem(tr("Do not transcode"), QString());
			transcoderName->setCurrentIndex(0);
			transcoderValue->setVisible(false);
			transcoderWhen->addItem(tr("Always transcode"), DeviceOptions::TW_Always);
			transcoderWhen->addItem(tr("Only transcode if source file is of a different format"), DeviceOptions::TW_IfDifferent);
			transcoderWhen->addItem(tr("Only transcode if source is FLAC/WAV"), DeviceOptions::TW_IfLossess);
			transcoderWhen->setVisible(false);
			transcoderWhen->setCurrentIndex(0);
			for (int i = 0; i < transcoderWhen->count(); ++i) {
				if (transcoderWhen->itemData(i).toInt() == opts.transcoderWhen) {
					transcoderWhen->setCurrentIndex(i);
					break;
				}
			}
			connect(transcoderWhen, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
		}
		else {
			transcoderFrame->setTitle(tr("Encoder"));
			REMOVE(transcoderWhen);
		}

		QList<Encoders::Encoder> encs = Encoders::getAvailable();

		if (encs.isEmpty()) {
			transcoderFrame->setVisible(false);
		}
		else {
			for (const Encoders::Encoder& e : encs) {
				if (!transcode || e.transcoder) {
					QString name = e.name;
					if (transcode && name.endsWith(QLatin1String(" (ffmpeg)"))) {
						name = name.left(name.length() - 9);
					}
					transcoderName->addItem(transcode ? tr("Transcode to %1").arg(name) : name, e.codec);
				}
			}

			if (opts.transcoderCodec.isEmpty()) {
				transcoderChanged();
			}
			else {
				Encoders::Encoder enc = Encoders::getEncoder(opts.transcoderCodec);
				if (!enc.isNull()) {
					for (int i = 1; i < transcoderName->count(); ++i) {
						if (transcoderName->itemData(i).toString() == opts.transcoderCodec) {
							transcoderName->setCurrentIndex(i);
							transcoderChanged();
							transcoderValue->setValue(opts.transcoderValue);
							break;
						}
					}
				}
			}
		}
		connect(transcoderName, SIGNAL(currentIndexChanged(int)), this, SLOT(transcoderChanged()));
		connect(transcoderValue, SIGNAL(valueChanged(int)), this, SLOT(checkSaveable()));
	}
	else {
		REMOVE(transcoderFrame);
	}

	if (storage.count() < 2) {
		REMOVE(defaultVolume);
		REMOVE(defaultVolumeLabel);
	}
	else {
		for (const DeviceStorage& ds : storage) {
			defaultVolume->addItem(tr("%1 (%2 free)", "name (size free)")
			                               .arg(ds.description)
			                               .arg(Utils::formatByteSize(ds.size - ds.used)),
			                       ds.volumeIdentifier);
		}

		for (int i = 0; i < defaultVolume->count(); ++i) {
			if (defaultVolume->itemData(i).toString() == opts.volumeId) {
				defaultVolume->setCurrentIndex(i);
				break;
			}
		}
		connect(defaultVolume, SIGNAL(currentIndexChanged(int)), this, SLOT(checkSaveable()));
	}

	origMusicFolder = Utils::fixPath(path);
	if (props & Prop_FileName) {
		connect(configFilename, SIGNAL(clicked()), SLOT(configureFilenameScheme()));
		connect(filenameScheme, SIGNAL(textChanged(const QString&)), this, SLOT(checkSaveable()));
		connect(vfatSafe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
		connect(asciiOnly, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
		connect(ignoreThe, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
		connect(replaceSpaces, SIGNAL(stateChanged(int)), this, SLOT(checkSaveable()));
	}

	if (albumCovers) {
		albumCoversChanged();
	}
	QTimer::singleShot(0, this, SLOT(setSize()));
}

void DevicePropertiesWidget::transcoderChanged()
{
	QString codec = transcoderName->itemData(transcoderName->currentIndex()).toString();
	if (codec.isEmpty()) {
		transcoderName->setToolTip(QString());
		transcoderValue->setVisible(false);
		if (transcoderWhen) {
			transcoderWhen->setVisible(false);
		}
	}
	else {
		Encoders::Encoder enc = Encoders::getEncoder(codec);
		transcoderName->setToolTip(enc.description);
		if (transcoderWhen) {
			transcoderWhen->setVisible(true);
		}
		if (enc.values.count()) {
			transcoderValue->setValues(enc);
			transcoderValue->setVisible(true);
		}
		else {
			transcoderValue->setVisible(false);
		}
	}

	Utils::resizeWindow(this, true, false);
	checkSaveable();
}

void DevicePropertiesWidget::albumCoversChanged()
{
	if (coverMaxSize) {
		bool enableSize = albumCovers->currentText() != noCoverText;
		coverMaxSize->setEnabled(enableSize);
		coverMaxSizeLabel->setEnabled(enableSize);
	}
	checkSaveable();
}

void DevicePropertiesWidget::checkSaveable()
{
	DeviceOptions opts = settings();
	bool checkFolder = musicFolder ? musicFolder->isEnabled() : false;

	modified = opts != origOpts;
	if (!modified && checkFolder) {
		modified = music() != origMusicFolder;
	}
	saveable = !opts.scheme.isEmpty() && (!checkFolder || !music().isEmpty()) && !opts.coverName.isEmpty();
	if (saveable && ((-1 != opts.coverName.indexOf(noCoverText) && opts.coverName != noCoverText) || (-1 != opts.coverName.indexOf(embedCoverText) && opts.coverName != embedCoverText))) {
		saveable = false;
	}
	emit updated();
}

void DevicePropertiesWidget::configureFilenameScheme()
{
	if (!schemeDlg) {
		schemeDlg = new FilenameSchemeDialog(this);
		connect(schemeDlg, SIGNAL(scheme(const QString&)), filenameScheme, SLOT(setText(const QString&)));
	}
	schemeDlg->show(settings());
}

DeviceOptions DevicePropertiesWidget::settings()
{
	DeviceOptions opts;
	if (name && name->isEnabled()) {
		opts.name = name->text().trimmed();
	}
	if (filenameScheme) {
		opts.scheme = filenameScheme->text().trimmed();
	}
	if (vfatSafe) {
		opts.vfatSafe = vfatSafe->isChecked();
	}
	if (asciiOnly) {
		opts.asciiOnly = asciiOnly->isChecked();
	}
	if (ignoreThe) {
		opts.ignoreThe = ignoreThe->isChecked();
	}
	if (replaceSpaces) {
		opts.replaceSpaces = replaceSpaces->isChecked();
	}
	opts.fixVariousArtists = fixVariousArtists ? fixVariousArtists->isChecked() : false;
	opts.useCache = useCache ? useCache->isChecked() : false;
	opts.autoScan = autoScan ? autoScan->isChecked() : false;
	opts.transcoderCodec = QString();
	opts.transcoderValue = 0;
	opts.transcoderWhen = DeviceOptions::TW_Always;
	opts.coverName = cover();
	opts.coverMaxSize = (!coverMaxSize || 0 == coverMaxSize->currentIndex()) ? 0 : ((coverMaxSize->count() - coverMaxSize->currentIndex()) * 100);
	opts.volumeId = defaultVolume && defaultVolume->isVisible() ? defaultVolume->itemData(defaultVolume->currentIndex()).toString() : QString();
	if (transcoderFrame && transcoderFrame->isVisible()) {
		opts.transcoderCodec = transcoderName->itemData(transcoderName->currentIndex()).toString();

		if (!opts.transcoderCodec.isEmpty()) {
			Encoders::Encoder enc = Encoders::getEncoder(opts.transcoderCodec);

			if (transcoderWhen) {
				opts.transcoderWhen = (DeviceOptions::TranscodeWhen)transcoderWhen->itemData(transcoderWhen->currentIndex()).toUInt();
			}
			if (!enc.isNull() && transcoderValue->value() < enc.values.count()) {
				opts.transcoderValue = enc.values.at(transcoderValue->value()).value;
			}
		}
	}
	return opts;
}

QString DevicePropertiesWidget::cover() const
{
	QString coverName = albumCovers ? albumCovers->currentText().trimmed() : noCoverText;
	return coverName == noCoverText
			? Device::constNoCover
			: coverName == embedCoverText
			? Device::constEmbedCover
			: coverName;
}

void DevicePropertiesWidget::setSize()
{
	Utils::resizeWindow(this, true, false);
}

#include "moc_devicepropertieswidget.cpp"
