/* GUI_LocalLibrary.cpp */

/* Copyright (C) 2011-2024 Michael Lugmair (Lucio Carreras)
 *
 * This file is part of sayonara player
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

/*
 * GUI_LocalLibrary.cpp
 *
 *  Created on: Apr 24, 2011
 *      Author: Michael Lugmair (Lucio Carreras)
 */

#include "GUI_LocalLibrary.h"
#include "GUI_ImportDialog.h"

#include "Gui/Library/Utils/GUI_DeleteDialog.h"
#include "Gui/Library/ui_GUI_LocalLibrary.h"

#include "Gui/Library/CoverView/GUI_CoverView.h"
#include "Gui/Library/Utils/GUI_ReloadLibraryDialog.h"
#include "Gui/Library/Utils/GUI_LibraryInfoBox.h"
#include "Gui/Library/Utils/LocalLibraryMenu.h"

#include "Gui/Utils/Icons.h"
#include "Gui/Utils/Style.h"
#include "Gui/Utils/PreferenceAction.h"
#include "Gui/Utils/Shortcuts/ShortcutHandler.h"
#include "Gui/Utils/Shortcuts/Shortcut.h"
#include "Gui/Utils/Widgets/DirectoryChooser.h"

#include "Components/Library/LocalLibrary.h"
#include "Components/LibraryManagement/LibraryManager.h"

#include "Utils/Utils.h"
#include "Utils/FileUtils.h"
#include "Utils/Set.h"
#include "Utils/Language/Language.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Library/LibraryInfo.h"
#include "Utils/Library/Filter.h"
#include "Utils/MetaData/MetaDataList.h"

#include <QDir>
#include <QFileDialog>
#include <QFileSystemWatcher>

#include "Utils/EventFilter.h"
#include "Utils/Message/Message.h"

using namespace Library;

namespace
{
	enum class ReloadWidgetIndex : uint8_t
	{
		TableView = 0,
		ReloadView = 1,
		NoDirView = 2
	};

	ReloadWidgetIndex getReloadWidgetIndex(const Info& libraryInfo, const bool isLibraryEmpty)
	{
		if (const auto pathExists = Util::File::exists(libraryInfo.path()); !pathExists)
		{
			return ReloadWidgetIndex::NoDirView;
		}

		return isLibraryEmpty ? ReloadWidgetIndex::ReloadView : ReloadWidgetIndex::TableView;
	}

	void applyFontSetting(QAbstractItemView* view)
	{
		auto font = view->font();
		const auto isBold = GetSetting(Set::Lib_FontBold);
		font.setBold(isBold);
		view->setFont(font);
		view->repaint();
	}

	void initSearchBar(SearchBar* leSearch, const QList<Filter::Mode>& searchOptions)
	{
		if (leSearch)
		{
			leSearch->setModes(searchOptions);
			leSearch->setCurrentMode(Filter::Fulltext);
		}
	}

	Filter createFilter(const SearchBar* searchBar)
	{
		auto filter = Filter();
		filter.setMode(searchBar->currentMode());
		filter.setFiltertext(searchBar->text());
		return filter;
	}

	QList<Filter::Mode> searchOptions()
	{
		return {Filter::Fulltext, Filter::Filename, Filter::Genre, Filter::InvalidGenre};
	}

	void selectNextViewType()
	{
		auto viewType = static_cast<int>(GetSetting(Set::Lib_ViewType));
		viewType = (viewType + 1) % 3;
		SetSetting(Set::Lib_ViewType, static_cast<ViewType>(viewType));
	}
} // namespace

struct GUI_LocalLibrary::Private
{
	LocalLibrary* library;
	LocalLibraryMenu* libraryMenu;
	QFileSystemWatcher* fileSystemWatcher{nullptr};

	Private(LocalLibrary* localLibrary, GUI_LocalLibrary* parent) :
		library{localLibrary},
		libraryMenu{new LocalLibraryMenu(library->info().name(), library->info().path(), parent)}
	{
	}
};

GUI_LocalLibrary::GUI_LocalLibrary(const LibraryId id, Manager* libraryManager, QWidget* parent) :
	Widget(parent),
	m{Pimpl::make<Private>(libraryManager->libraryInstance(id), this)},
	ui{std::make_unique<Ui::GUI_LocalLibrary>()}
{
	ui->setupUi(this);
	setFocusProxy(ui->leSearch);

	initWidgets(id, libraryManager);
	initConnections();
	initShortcuts();
	initFileSystemWatcher();
}

GUI_LocalLibrary::~GUI_LocalLibrary() = default;

void GUI_LocalLibrary::initWidgets(const LibraryId id, Manager* libraryManager) const
{
	auto playActionEventHandler = PlayActionEventHandler::create(m->library->playlistInteractor(), m->library);

	ui->tvTracks->init(playActionEventHandler, m->library);
	ui->tvAlbums->init(playActionEventHandler, m->library);
	ui->tvArtists->init(playActionEventHandler, m->library);

	ui->directoryView->init(libraryManager, id);

	ui->extensionBar->init(m->library);
	ui->lvGenres->init(m->library);
	ui->btnView->setOverrideText(true);

	initSearchBar(ui->leSearch, searchOptions());
}

void GUI_LocalLibrary::initConnections()
{
	connect(m->library, &LocalLibrary::sigDeleteAnswer, this, &GUI_LocalLibrary::showDeleteAnswer);

	connect(ui->tvArtists, &ItemView::sigDeleteClicked, this, &GUI_LocalLibrary::itemDeleteClicked);
	connect(ui->tvAlbums, &ItemView::sigDeleteClicked, this, &GUI_LocalLibrary::itemDeleteClicked);
	connect(ui->tvTracks, &ItemView::sigDeleteClicked, this, &GUI_LocalLibrary::tracksDeleteClicked);

	connect(ui->leSearch, &SearchBar::sigCurrentModeChanged, this, &GUI_LocalLibrary::queryLibrary);
	connect(ui->leSearch, &QLineEdit::returnPressed, this, &GUI_LocalLibrary::searchTriggered);
	connect(ui->leSearch, &QLineEdit::editingFinished, this, &GUI_LocalLibrary::searchTriggered);

	connect(m->library, &LocalLibrary::sigReloadingLibrary, this, &GUI_LocalLibrary::progressChanged);
	connect(m->library, &LocalLibrary::sigReloadingLibraryFinished, this, &GUI_LocalLibrary::reloadFinished);
	connect(m->library, &LocalLibrary::sigReloadingLibraryFinished, ui->lvGenres, &GenreView::reloadGenres);
	connect(m->library, &LocalLibrary::sigAllTracksLoaded, this, &GUI_LocalLibrary::tracksLoaded);
	connect(m->library, &LocalLibrary::sigImportDialogRequested, this, &GUI_LocalLibrary::importDialogRequested);
	connect(m->library, &LocalLibrary::sigRenamed, this, &GUI_LocalLibrary::nameChanged);
	connect(m->library, &LocalLibrary::sigPathChanged, this, &GUI_LocalLibrary::pathChanged);

	connect(ui->tvAlbums, &AlbumView::sigDiscPressed, m->library, &LocalLibrary::changeCurrentDisc);
	connect(ui->lvGenres, &GenreView::sigSelectedChanged, this, &GUI_LocalLibrary::genreSelectionChanged);
	connect(ui->lvGenres, &GenreView::sigInvalidGenreSelected, this, &GUI_LocalLibrary::invalidGenreSelected);
	connect(ui->lvGenres, &GenreView::sigProgress, this, &GUI_LocalLibrary::progressChanged);

	connect(m->libraryMenu, &LocalLibraryMenu::sigPathChanged, m->library, &LocalLibrary::setLibraryPath);
	connect(m->libraryMenu, &LocalLibraryMenu::sigNameChanged, m->library, &LocalLibrary::setLibraryName);
	connect(m->libraryMenu, &LocalLibraryMenu::sigImportFile, this, &GUI_LocalLibrary::importFilesRequested);
	connect(m->libraryMenu, &LocalLibraryMenu::sigImportFolder, this, &GUI_LocalLibrary::importDirsRequested);
	connect(m->libraryMenu, &LocalLibraryMenu::sigInfo, this, &GUI_LocalLibrary::showInfoBox);
	connect(m->libraryMenu, &LocalLibraryMenu::sigReloadLibrary, this, &GUI_LocalLibrary::reloadLibraryRequested);

	connect(ui->btnScanForFiles, &QPushButton::clicked, this, &GUI_LocalLibrary::reloadLibraryDeepRequested);
	connect(ui->btnImportDirectories, &QPushButton::clicked, this, &GUI_LocalLibrary::importDirsRequested);

	connect(ui->splitterArtistAlbum, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterArtistMoved);
	connect(ui->splitterTracks, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterTracksMoved);
	connect(ui->splitterGenre, &QSplitter::splitterMoved, this, &GUI_LocalLibrary::splitterGenreMoved);

	connect(ui->tvAlbums, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested);
	connect(ui->tvArtists, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested);
	connect(ui->tvTracks, &ItemView::sigReloadClicked, this, &GUI_LocalLibrary::reloadLibraryRequested);

	ListenSetting(Set::Lib_LiveSearch, GUI_LocalLibrary::liveSearchChanged);
	ListenSetting(Set::Lib_ShowFilterExtBar, GUI_LocalLibrary::tracksLoaded);
	ListenSetting(Set::Lib_ViewType, GUI_LocalLibrary::switchViewType);
	ListenSettingNoCall(Set::Lib_FontBold, GUI_LocalLibrary::boldFontChanged);
}

void GUI_LocalLibrary::initShortcuts()
{
	auto* keyPressFilter = new Gui::KeyPressFilter(Qt::Key_Escape, Qt::NoModifier, this);
	installEventFilter(keyPressFilter);
	connect(keyPressFilter, &Gui::KeyPressFilter::sigKeyPressed, this, &GUI_LocalLibrary::escapePressed);

	const auto* shortcutHandler = ShortcutHandler::instance();
	auto shortcut = shortcutHandler->shortcut(ShortcutIdentifier::CoverView);
	shortcut.connect(this, &selectNextViewType);
}

void GUI_LocalLibrary::initFileSystemWatcher()
{
	const auto libraryPath = m->library->info().path();
	const auto paths = QStringList()
	                   << libraryPath
	                   << Util::File::getParentDirectory(libraryPath);

	auto* action = new Gui::LibraryPreferenceAction(this);
	connect(ui->btnLibraryPreferences, &QPushButton::clicked, action, &QAction::trigger);

	m->fileSystemWatcher = new QFileSystemWatcher(this);
	m->fileSystemWatcher->addPaths(paths); // old qt versions don't have the new constructor
	connect(m->fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this,
	        [this](const auto& /*path*/) {
		        checkMainSplitterStatus();
	        });
}

void GUI_LocalLibrary::checkViewState()
{
	if (isVisible())
	{
		checkMainSplitterStatus();

		if (!m->library->isReloading())
		{
			checkFileExtensionBar();
		}
	}
}

void GUI_LocalLibrary::checkFileExtensionBar()
{
	ui->extensionBar->refresh();
	ui->extensionBar->setVisible(
		GetSetting(Set::Lib_ShowFilterExtBar) &&
		ui->extensionBar->hasExtensions());
}

void GUI_LocalLibrary::tracksLoaded()
{
	checkViewState();

	const auto info = m->library->info();

	ui->labLibraryName->setText(info.name());
	ui->labPath->setText(Util::createLink(info.path(), Style::isDark()));

	ui->btnScanForFiles->setIcon(Gui::icon(Gui::Refresh));
	ui->btnImportDirectories->setIcon(Gui::icon(Gui::Folder));
}

void GUI_LocalLibrary::clearSelections()
{
	ui->tvAlbums->clearSelection();
	ui->tvArtists->clearSelection();
	ui->tvTracks->clearSelection();

	if (ui->coverView)
	{
		ui->coverView->clearSelections();
	}

	ui->lvGenres->clearSelection();
}

void GUI_LocalLibrary::invalidGenreSelected()
{
	ui->leSearch->setGenre(GenreView::invalidGenreName(), true);

	searchTriggered();

	ui->leSearch->setGenre(GenreView::invalidGenreName(), false);
}

void GUI_LocalLibrary::genreSelectionChanged(const QStringList& genres)
{
	if (!genres.isEmpty())
	{
		ui->leSearch->setGenre(genres.join(","));
		searchTriggered();
	}
}

TrackDeletionMode GUI_LocalLibrary::showDeleteDialog(const int track_count)
{
	auto dialog = GUI_DeleteDialog(track_count, this);
	dialog.exec();

	return dialog.answer();
}

void GUI_LocalLibrary::progressChanged(const QString& type, int progress)
{
	checkViewState();

	ui->pbProgress->setMaximum((progress > 0) ? 100 : 0); // NOLINT(readability-magic-numbers)
	ui->pbProgress->setValue(progress);
	ui->labProgress->setText
	(
		fontMetrics().elidedText(type, Qt::ElideRight, ui->widgetReload->width() / 2)
	);
}

void GUI_LocalLibrary::reloadLibraryRequested()
{
	reloadLibraryRequestedWithQuality(ReloadQuality::Unknown);
}

void GUI_LocalLibrary::reloadLibraryDeepRequested()
{
	reloadLibraryRequestedWithQuality(ReloadQuality::Accurate);
}

void GUI_LocalLibrary::reloadLibraryRequestedWithQuality(const ReloadQuality quality)
{
	if (quality == ReloadQuality::Unknown)
	{
		auto* dialog = new GUI_LibraryReloadDialog(m->library->info().name(), this);
		connect(dialog, &GUI_LibraryReloadDialog::sigAccepted, this, &GUI_LocalLibrary::reloadLibraryAccepted);

		dialog->setQuality(quality);
		dialog->show();
	}

	else
	{
		reloadLibrary(quality);
	}
}

void GUI_LocalLibrary::reloadLibraryAccepted(const ReloadQuality quality)
{
	if (sender())
	{
		sender()->deleteLater();
	}

	reloadLibrary(quality);
}

void GUI_LocalLibrary::reloadLibrary(const ReloadQuality quality)
{
	m->libraryMenu->setLibraryBusy(true);
	m->library->reloadLibrary(false, quality);
}

void GUI_LocalLibrary::reloadFinished()
{
	m->libraryMenu->setLibraryBusy(false);
	checkViewState();
}

void GUI_LocalLibrary::showInfoBox()
{
	const auto info = m->library->info();
	GUI_LibraryInfoBox infoBox(info, this);
	infoBox.exec();
}

void GUI_LocalLibrary::importDirsRequested()
{
	const auto directories = Gui::DirectoryChooser::getDirectories(Lang::get(Lang::ImportDir), QDir::homePath(), this);
	if (!directories.isEmpty())
	{
		m->library->importFiles(directories);
	}
}

void GUI_LocalLibrary::importFilesRequested()
{
	const auto files = QFileDialog::getOpenFileNames
	(
		nullptr,
		Lang::get(Lang::ImportFiles),
		QDir::homePath(),
		Util::getFileFilter(Util::Extension::Soundfile, tr("Audio files"))
	);

	m->library->importFiles(files);
}

void GUI_LocalLibrary::importDialogRequested(const QString& targetDirectory)
{
	if (isVisible())
	{
		auto* uiImporter = new GUI_ImportDialog(m->library->importer(), m->library->info().path(), true, this);
		uiImporter->setTargetDirectory(targetDirectory);

		connect(uiImporter, &GUI_ImportDialog::sigFinished, uiImporter, &QObject::deleteLater);
		uiImporter->show();
	}
}

void GUI_LocalLibrary::nameChanged(const QString& newName)
{
	m->libraryMenu->refreshName(newName);
	ui->labLibraryName->setText(newName);
}

void GUI_LocalLibrary::pathChanged(const QString& newPath)
{
	m->libraryMenu->refreshPath(newPath);

	if (isVisible())
	{
		reloadLibraryRequestedWithQuality(ReloadQuality::Accurate);
		ui->labPath->setText(newPath);
	}
}

void GUI_LocalLibrary::checkMainSplitterStatus()
{
	const auto isLibraryEmpty = m->library->isEmpty();
	const auto libraryInfo = m->library->info();

	const auto index = getReloadWidgetIndex(libraryInfo, isLibraryEmpty);
	ui->swReload->setCurrentIndex(static_cast<int>(index));

	const auto inLibraryState = (index == ReloadWidgetIndex::TableView);
	ui->leSearch->setVisible(inLibraryState);
	ui->btnScanForFiles->setVisible(!inLibraryState);
	ui->btnImportDirectories->setVisible(!inLibraryState);

	if (index == ReloadWidgetIndex::NoDirView)
	{
		ui->labDir->setText(libraryInfo.path());
	}

	else
	{
		const auto isReloading = m->library->isReloading();

		ui->pbProgress->setVisible(isReloading);
		ui->labProgress->setVisible(isReloading);
		ui->widgetReload->setVisible(isReloading || isLibraryEmpty);
		m->libraryMenu->setLibraryEmpty(isLibraryEmpty);
	}
}

void GUI_LocalLibrary::splitterArtistMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx)
{
	const auto data = ui->splitterArtistAlbum->saveState();
	SetSetting(Set::Lib_SplitterStateArtist, data);
}

void GUI_LocalLibrary::splitterTracksMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx)
{
	const auto data = ui->splitterTracks->saveState();
	SetSetting(Set::Lib_SplitterStateTrack, data);
}

void GUI_LocalLibrary::splitterGenreMoved([[maybe_unused]] int pos, [[maybe_unused]] int idx)
{
	const auto data = ui->splitterGenre->saveState();
	SetSetting(Set::Lib_SplitterStateGenre, data);
}

void GUI_LocalLibrary::initCoverView()
{
	if (!ui->coverView->isInitialized())
	{
		ui->coverView->init(m->library);
		connect(ui->coverView, &GUI_CoverView::sigDeleteClicked, this, &GUI_LocalLibrary::itemDeleteClicked);
		connect(ui->coverView, &GUI_CoverView::sigReloadClicked,
		        this, &GUI_LocalLibrary::reloadLibraryRequested);
	}
}

void GUI_LocalLibrary::switchViewType()
{
	constexpr auto ArtistAlbumTableView = 0;
	constexpr auto AlbumCoverView = 1;
	constexpr auto DirectoryView = 2;

	switch (GetSetting(Set::Lib_ViewType))
	{
		case ViewType::CoverView:
			initCoverView();

			if (m->library->isLoaded() && !m->library->selectedArtists().isEmpty())
			{
				m->library->selectedArtistsChanged(IndexSet());
			}

			ui->swViewType->setCurrentIndex(AlbumCoverView);
			break;

		case ViewType::FileView:
			ui->swViewType->setCurrentIndex(DirectoryView);
			break;

		case ViewType::Standard:
		default:
			ui->swViewType->setCurrentIndex(ArtistAlbumTableView);
			break;
	}

	ui->swViewType->setFocus();
}

void GUI_LocalLibrary::queryLibrary() const
{
	const auto filter = createFilter(ui->leSearch);
	m->library->changeFilter(filter);

	ui->directoryView->setFilterTerm(m->library->filter().filtertext(false).join(""));
}

void GUI_LocalLibrary::showEvent(QShowEvent* e)
{
	Widget::showEvent(e);
	boldFontChanged();

	const auto splitters = QHash<QSplitter*, QByteArray>{
		{ui->splitterArtistAlbum, GetSetting(Set::Lib_SplitterStateArtist)},
		{ui->splitterTracks, GetSetting(Set::Lib_SplitterStateTrack)},
		{ui->splitterGenre, GetSetting(Set::Lib_SplitterStateGenre)}
	};

	for (auto it = splitters.begin(); it != splitters.end(); ++it)
	{
		if (!it.value().isEmpty())
		{
			it.key()->restoreState(it.value());
		}
	}

	checkViewState();
	if (!m->library->isLoaded())
	{
		m->library->init();
	}
}

QMenu* GUI_LocalLibrary::menu() const { return m->libraryMenu; }

QFrame* GUI_LocalLibrary::headerFrame() const { return ui->headerFrame; }

void GUI_LocalLibrary::searchTriggered() const { queryLibrary(); }

void GUI_LocalLibrary::searchEdited(const QString& searchString) const
{
	if (GetSetting(Set::Lib_LiveSearch) || searchString.isEmpty())
	{
		queryLibrary();
	}
}

bool GUI_LocalLibrary::hasSelections() const
{
	return !m->library->selectedAlbums().isEmpty() ||
	       !m->library->selectedArtists().isEmpty() ||
	       !ui->lvGenres->selectedItems().isEmpty() ||
	       !ui->coverView->selectedItems().isEmpty();
}

void GUI_LocalLibrary::escapePressed(const int /*key*/)
{
	if (hasSelections())
	{
		clearSelections();
	}

	if (!ui->leSearch->text().isEmpty())
	{
		ui->leSearch->clear();
	}

	else
	{
		ui->leSearch->setCurrentMode(Filter::Mode::Fulltext);
		m->library->refetch();
	}
}

void GUI_LocalLibrary::itemDeleteClicked()
{
	const auto trackCount = m->library->tracks().count();
	if (const auto answer = showDeleteDialog(trackCount); answer != TrackDeletionMode::None)
	{
		m->library->deleteFetchedTracks(answer);
	}
}

void GUI_LocalLibrary::tracksDeleteClicked()
{
	const auto trackCount = m->library->currentTracks().count();
	if (const auto answer = showDeleteDialog(trackCount); answer != TrackDeletionMode::None)
	{
		m->library->deleteCurrentTracks(answer);
	}
}

void GUI_LocalLibrary::showDeleteAnswer(const QString& answer)
{
	Message::info(answer, Lang::get(Lang::Library));
}

void GUI_LocalLibrary::liveSearchChanged()
{
	if (GetSetting(Set::Lib_LiveSearch))
	{
		connect(ui->leSearch, &QLineEdit::textChanged, this, &GUI_LocalLibrary::searchEdited);
	}

	else
	{
		disconnect(ui->leSearch, &QLineEdit::textEdited, this, &GUI_LocalLibrary::searchEdited);
	}
}

void GUI_LocalLibrary::boldFontChanged()
{
	const auto views = QList<QAbstractItemView*>{ui->tvAlbums, ui->tvArtists, ui->tvTracks};
	for (auto* view: views)
	{
		applyFontSetting(view);
	}
}

void GUI_LocalLibrary::languageChanged()
{
	ui->retranslateUi(this);

	ui->btnLibraryPreferences->setText(Lang::get(Lang::Preferences));
	ui->labGenres->setText(Lang::get(Lang::Genres));
	ui->btnScanForFiles->setText(Lang::get(Lang::ScanForFiles));
	ui->btnImportDirectories->setText(Lang::get(Lang::ImportDir));
}

void GUI_LocalLibrary::skinChanged() { checkViewState(); }

void GUI_LocalLibrary::closeEvent(QCloseEvent* e)
{
	ui->tvAlbums->saveState();
	ui->tvArtists->saveState();
	ui->tvTracks->saveState();

	Widget::closeEvent(e);
}