/* GUI_DirectoryView.cpp
 *
 * Copyright (C) 2011-2024 Michael Lugmair
 *
 * 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/>.
 */

#include "GUI_DirectoryView.h"
#include "DirectoryTreeView.h"
#include "FileListView.h"

#include "Gui/Library/ui_GUI_DirectoryView.h"

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

#include "Gui/Utils/Icons.h"
#include "Gui/Utils/InputDialog/LineInputDialog.h"
#include "Gui/Utils/Widgets/DirectoryChooser.h"

#include "Utils/Algorithm.h"
#include "Utils/Settings/Settings.h"
#include "Utils/Language/Language.h"
#include "Utils/FileUtils.h"
#include "Utils/Message/Message.h"
#include "Utils/Library/LibraryInfo.h"
#include "Utils/MetaData/MetaDataList.h"

#include <QAction>
#include <QDesktopServices>
#include <QItemSelectionModel>
#include <QLabel>
#include <QListView>
#include <QTimer>

using Directory::TreeView;
using Directory::FileListView;

namespace
{
	QString copyOrMoveLibraryRequested(const Library::Info& info, const QStringList& paths, QWidget* parent)
	{
		if(paths.isEmpty())
		{
			return {};
		}

		auto targetDirectory =
			Gui::DirectoryChooser::getDirectory(QObject::tr("Choose target directory"), info.path(), true, parent);
		if(targetDirectory.isEmpty())
		{
			return {};
		}

		const auto isSubDir = Util::File::isSubdir(targetDirectory, info.path());
		const auto isSamePath = Util::File::isSamePath(targetDirectory, info.path());
		if(!isSubDir && !isSamePath)
		{
			Message::error(QObject::tr("%1 is not a subdirectory of %2").arg(targetDirectory).arg(info.path()));
			return {};
		}

		return targetDirectory;
	}

	void showImageLabel(const QString& filename)
	{
		constexpr const auto DefaultSize = 600;
		const auto f = Util::File::getFilenameOfPath(filename);
		const auto pm = QPixmap(filename);

		auto* label = new QLabel();
		label->setPixmap(pm);
		label->setScaledContents(true);
		label->setAttribute(Qt::WA_DeleteOnClose);
		label->resize((DefaultSize * pm.width()) / pm.height(), DefaultSize);
		label->setToolTip(QString("%1x%2").arg(pm.width()).arg(pm.height()));
		label->setWindowTitle(QString("%1: %2x%3")
			                      .arg(f)
			                      .arg(pm.width())
			                      .arg(pm.height())
		);

		label->show();
	}
}

struct GUI_DirectoryView::Private
{
	Library::Manager* libraryManager;
	DirectorySelectionHandler* directorySelectionHandler;
	QString filterTerm;

	explicit Private(Library::Manager* libraryManager) :
		libraryManager {libraryManager},
		directorySelectionHandler {new DirectorySelectionHandler {libraryManager}} {}

	[[nodiscard]] Library::Info currentLibrary() const { return directorySelectionHandler->libraryInfo(); }
};

GUI_DirectoryView::GUI_DirectoryView(QWidget* parent) :
	Gui::Widget(parent) {}

GUI_DirectoryView::~GUI_DirectoryView() = default;

void GUI_DirectoryView::init(Library::Manager* libraryManager, LibraryId libraryId)
{
	m = Pimpl::make<Private>(libraryManager);
	m->directorySelectionHandler->setLibraryId(libraryId);
}

void GUI_DirectoryView::initUi()
{
	if(ui)
	{
		return;
	}

	ui = new Ui::GUI_DirectoryView();
	ui->setupUi(this);

	const auto info = m->currentLibrary();

	ui->tvDirs->init(m->libraryManager, info);
	ui->lvFiles->init(m->libraryManager, info);

	connect(m->directorySelectionHandler, &DirectorySelectionHandler::sigFileOperationStarted,
	        this, &GUI_DirectoryView::fileOperationStarted);
	connect(m->directorySelectionHandler, &DirectorySelectionHandler::sigFileOperationFinished,
	        this, &GUI_DirectoryView::fileOperationFinished);
	connect(m->directorySelectionHandler, &DirectorySelectionHandler::sigLibrariesChanged,
	        this, &GUI_DirectoryView::load);

	connect(ui->tvDirs, &QTreeView::pressed, this, &GUI_DirectoryView::dirPressed);
	connect(ui->tvDirs, &TreeView::sigCurrentIndexChanged, this, &GUI_DirectoryView::dirClicked);
	connect(ui->tvDirs, &TreeView::sigImportRequested, this, &GUI_DirectoryView::importRequested);
	connect(ui->tvDirs, &TreeView::sigEnterPressed, this, &GUI_DirectoryView::dirEnterPressed);
	connect(ui->tvDirs, &TreeView::sigAppendClicked, this, &GUI_DirectoryView::dirAppendClicked);
	connect(ui->tvDirs, &TreeView::sigPlayClicked, this, &GUI_DirectoryView::dirPlayClicked);
	connect(ui->tvDirs, &TreeView::sigPlayNextClicked, this, &GUI_DirectoryView::dirPlayNextClicked);
	connect(ui->tvDirs, &TreeView::sigPlayNewTabClicked, this, &GUI_DirectoryView::dirPlayInNewTabClicked);
	connect(ui->tvDirs, &TreeView::sigDeleteClicked, this, &GUI_DirectoryView::dirDeleteClicked);
	connect(ui->tvDirs, &TreeView::sigDirectoryLoaded, this, &GUI_DirectoryView::dirOpened);
	connect(ui->tvDirs, &TreeView::sigCopyRequested, this, &GUI_DirectoryView::dirCopyRequested);
	connect(ui->tvDirs, &TreeView::sigMoveRequested, this, &GUI_DirectoryView::dirMoveRequested);
	connect(ui->tvDirs, &TreeView::sigRenameRequested, this, &GUI_DirectoryView::dirRenameRequested);
	connect(ui->tvDirs, &TreeView::sigCopyToLibraryRequested, this, &GUI_DirectoryView::dirCopyToLibRequested);
	connect(ui->tvDirs, &TreeView::sigMoveToLibraryRequested, this, &GUI_DirectoryView::dirMoveToLibRequested);
	connect(ui->tvDirs->selectionModel(), &QItemSelectionModel::selectionChanged,
	        this, &GUI_DirectoryView::dirSelectionChanged);

	connect(ui->lvFiles, &QListView::pressed, this, &GUI_DirectoryView::filePressed);
	connect(ui->lvFiles, &QListView::doubleClicked, this, &GUI_DirectoryView::fileDoubleClicked);
	connect(ui->lvFiles, &FileListView::sigImportRequested, this, &GUI_DirectoryView::importRequested);
	connect(ui->lvFiles, &FileListView::sigEnterPressed, this, &GUI_DirectoryView::fileEnterPressed);
	connect(ui->lvFiles, &FileListView::sigAppendClicked, this, &GUI_DirectoryView::fileAppendClicked);
	connect(ui->lvFiles, &FileListView::sigPlayClicked, this, &GUI_DirectoryView::filePlayClicked);
	connect(ui->lvFiles, &FileListView::sigPlayNextClicked, this, &GUI_DirectoryView::filePlayNextClicked);
	connect(ui->lvFiles, &FileListView::sigPlayNewTabClicked, this, &GUI_DirectoryView::filePlayNewTabClicked);
	connect(ui->lvFiles, &FileListView::sigDeleteClicked, this, &GUI_DirectoryView::fileDeleteClicked);
	connect(ui->lvFiles, &FileListView::sigRenameRequested, this, &GUI_DirectoryView::fileRenameRequested);
	connect(ui->lvFiles, &FileListView::sigRenameByExpressionRequested,
	        this, &GUI_DirectoryView::fileRenameByExpressionRequested);
	connect(ui->lvFiles, &FileListView::sigCopyToLibraryRequested,
	        this, &GUI_DirectoryView::fileCopyToLibraryRequested);
	connect(ui->lvFiles, &FileListView::sigMoveToLibraryRequested,
	        this, &GUI_DirectoryView::fileMoveToLibraryRequested);
	connect(ui->lvFiles->selectionModel(), &QItemSelectionModel::selectionChanged,
	        this, &GUI_DirectoryView::fileSelectionChanged);

	connect(ui->splitter, &QSplitter::splitterMoved, this, &GUI_DirectoryView::splitterMoved);
	connect(ui->btnCreateDir, &QPushButton::clicked, this, &GUI_DirectoryView::createDirectoryClicked);
	connect(ui->btnClearSelection, &QPushButton::clicked, ui->tvDirs, &TreeView::clearSelection);

	ui->tvDirs->setEnabled(false);
	ui->tvDirs->setBusy(true);

	QTimer::singleShot(500, this, &GUI_DirectoryView::load); // NOLINT(*-magic-numbers)
}

void GUI_DirectoryView::load()
{
	const auto info = m->currentLibrary();

	ui->tvDirs->setFilterTerm(m->filterTerm);
	ui->lvFiles->setParentDirectory(info.path());
	ui->btnClearSelection->setVisible(false);

	ui->tvDirs->setEnabled(true);
	ui->tvDirs->setBusy(false);
}

void GUI_DirectoryView::setFilterTerm(const QString& filter)
{
	m->filterTerm = filter;

	if(ui)
	{
		ui->tvDirs->setFilterTerm(filter);
	}
}

void GUI_DirectoryView::importRequested(LibraryId id, const QStringList& paths, const QString& targetDirectory)
{
	m->directorySelectionHandler->requestImport(id, paths, targetDirectory);
}

void GUI_DirectoryView::newDirectoryClicked()
{
	const auto newDirName = Gui::LineInputDialog::getNewFilename(this, Lang::get(Lang::CreateDirectory));
	if(newDirName.isEmpty())
	{
		return;
	}

	const auto info = m->currentLibrary();
	const auto libraryPath = info.path();
	const auto libraryDir = QDir {libraryPath};

	const auto success = libraryDir.mkdir(newDirName);
	if(!success)
	{
		const auto message = QString("%1<br>%2")
			.arg(tr("Could not create directory"))
			.arg(libraryDir.absoluteFilePath(newDirName));

		Message::error(message);
	}
}

void GUI_DirectoryView::viewInFileManagerClicked()
{
	const auto info = m->currentLibrary();
	const auto url = QUrl::fromLocalFile(info.path());

	QDesktopServices::openUrl(url);
}

void GUI_DirectoryView::dirEnterPressed()
{
	const auto indexes = ui->tvDirs->selectedRows();
	if(!indexes.isEmpty())
	{
		ui->tvDirs->expand(indexes.first());
	}
}

void GUI_DirectoryView::dirOpened(const QModelIndex& idx)
{
	const auto dir = idx.isValid()
	                 ? ui->tvDirs->directoryName(idx)
	                 : m->currentLibrary().path();

	const auto selectedPaths = ui->tvDirs->selectedPaths();
	const auto dirs = (!selectedPaths.isEmpty())
	                  ? selectedPaths
	                  : QStringList {dir};

	ui->lvFiles->setParentDirectory(dir);

	// show in metadata table view
	m->directorySelectionHandler->libraryInstance()->fetchTracksByPath(dirs);
}

void GUI_DirectoryView::dirPressed(const QModelIndex& /*idx*/)
{
	const auto buttons = QApplication::mouseButtons();
	if(buttons & Qt::MiddleButton)
	{
		const auto selectedPaths = ui->tvDirs->selectedPaths();
		m->directorySelectionHandler->prepareTracksForPlaylist(selectedPaths, true);
	}
}

void GUI_DirectoryView::dirSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/)
{
	ui->btnClearSelection->setVisible(!selected.isEmpty());
}

void GUI_DirectoryView::dirClicked(const QModelIndex& idx)
{
	ui->lvFiles->clearSelection();

	dirOpened(idx);
}

void GUI_DirectoryView::dirAppendClicked()
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	m->directorySelectionHandler->appendTracks(selectedPaths);
}

void GUI_DirectoryView::dirPlayClicked()
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	m->directorySelectionHandler->prepareTracksForPlaylist(selectedPaths, false);
}

void GUI_DirectoryView::dirPlayNextClicked()
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	m->directorySelectionHandler->playNext(selectedPaths);
}

void GUI_DirectoryView::dirPlayInNewTabClicked()
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	m->directorySelectionHandler->createPlaylist(selectedPaths, true);
}

void GUI_DirectoryView::dirDeleteClicked()
{
	const auto answer = Message::question_yn(Lang::get(Lang::Delete) + ": " + Lang::get(Lang::Really) + "?");
	if(answer == Message::Answer::Yes)
	{
		const auto selectedPaths = ui->tvDirs->selectedPaths();
		m->directorySelectionHandler->deletePaths(selectedPaths);
	}
}

void GUI_DirectoryView::dirCopyRequested(const QStringList& files, const QString& target)
{
	m->directorySelectionHandler->copyPaths(files, target);
}

void GUI_DirectoryView::dirMoveRequested(const QStringList& files, const QString& target)
{
	m->directorySelectionHandler->movePaths(files, target);
}

void GUI_DirectoryView::dirRenameRequested(const QString& oldName, const QString& newName)
{
	m->directorySelectionHandler->renamePath(oldName, newName);
}

void GUI_DirectoryView::dirCopyToLibRequested(LibraryId libraryId)
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	const auto targetDirectory =
		copyOrMoveLibraryRequested(m->libraryManager->libraryInfo(libraryId),
		                           selectedPaths,
		                           this);
	if(!targetDirectory.isEmpty())
	{
		m->directorySelectionHandler->copyPaths(selectedPaths, targetDirectory);
	}
}

void GUI_DirectoryView::dirMoveToLibRequested(LibraryId libraryId)
{
	const auto selectedPaths = ui->tvDirs->selectedPaths();
	const auto targetDirectory =
		copyOrMoveLibraryRequested(m->libraryManager->libraryInfo(libraryId),
		                           selectedPaths,
		                           this);
	if(!targetDirectory.isEmpty())
	{
		m->directorySelectionHandler->movePaths(selectedPaths, targetDirectory);
	}
}

void GUI_DirectoryView::filePressed(const QModelIndex& /*idx*/)
{
	const auto buttons = QApplication::mouseButtons();
	if(buttons & Qt::MiddleButton)
	{
		m->directorySelectionHandler->prepareTracksForPlaylist(ui->lvFiles->selectedPaths(), true);
	}
}

void GUI_DirectoryView::fileSelectionChanged(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/)
{
	auto selectedPaths = ui->lvFiles->selectedPaths();
	const auto lastIt = std::remove_if(selectedPaths.begin(), selectedPaths.end(), [](const auto& path) {
		return (!Util::File::isSoundFile(path) && !Util::File::isPlaylistFile(path));
	});

	selectedPaths.erase(lastIt, selectedPaths.end());

	if(!selectedPaths.isEmpty())
	{ // may happen if an invalid path is clicked
		m->directorySelectionHandler->libraryInstance()->fetchTracksByPath(selectedPaths);
	}

	else if(!ui->tvDirs->selectedPaths().isEmpty())
	{
		m->directorySelectionHandler->libraryInstance()->fetchTracksByPath(ui->tvDirs->selectedPaths());
	}

	else
	{
		m->directorySelectionHandler->libraryInstance()->refetch();
	}
}

void GUI_DirectoryView::fileDoubleClicked(const QModelIndex& /*idx*/)
{
	fileEnterPressed();
}

void GUI_DirectoryView::fileEnterPressed()
{
	const auto paths = ui->lvFiles->selectedPaths();
	if(paths.size() == 1 && Util::File::isImageFile(paths[0]))
	{
		showImageLabel(paths[0]);
		return;
	}

	const auto hasSoundfiles = Util::Algorithm::contains(paths, [](auto path) {
		return (Util::File::isSoundFile(path) || Util::File::isPlaylistFile(path));
	});

	if(hasSoundfiles)
	{
		m->directorySelectionHandler->prepareTracksForPlaylist(paths, false);
	}
}

void GUI_DirectoryView::fileAppendClicked()
{
	m->directorySelectionHandler->appendTracks(ui->lvFiles->selectedPaths());
}

void GUI_DirectoryView::filePlayClicked()
{
	m->directorySelectionHandler->prepareTracksForPlaylist(ui->lvFiles->selectedPaths(), false);
}

void GUI_DirectoryView::filePlayNextClicked()
{
	m->directorySelectionHandler->playNext(ui->lvFiles->selectedPaths());
}

void GUI_DirectoryView::filePlayNewTabClicked()
{
	m->directorySelectionHandler->createPlaylist(ui->lvFiles->selectedPaths(), true);
}

void GUI_DirectoryView::fileDeleteClicked()
{
	const auto answer = Message::question_yn(Lang::get(Lang::Delete) + ": " + Lang::get(Lang::Really) + "?");
	if(answer == Message::Answer::Yes)
	{
		m->directorySelectionHandler->deletePaths(ui->lvFiles->selectedPaths());
	}
}

void GUI_DirectoryView::fileRenameRequested(const QString& oldName, const QString& newName)
{
	m->directorySelectionHandler->renamePath(oldName, newName);
}

void GUI_DirectoryView::fileRenameByExpressionRequested(const QString& oldName, const QString& expression)
{
	m->directorySelectionHandler->renameByExpression(oldName, expression);
	fileOperationFinished();
}

void GUI_DirectoryView::fileCopyToLibraryRequested(LibraryId libraryId)
{
	const auto targetDirectory =
		copyOrMoveLibraryRequested(m->libraryManager->libraryInfo(libraryId),
		                           ui->lvFiles->selectedPaths(),
		                           this);
	if(!targetDirectory.isEmpty())
	{
		m->directorySelectionHandler->copyPaths(ui->lvFiles->selectedPaths(), targetDirectory);
	}
}

void GUI_DirectoryView::fileMoveToLibraryRequested(LibraryId libraryId)
{
	const auto targetDirectory = copyOrMoveLibraryRequested(m->libraryManager->libraryInfo(libraryId),
	                                                        ui->lvFiles->selectedPaths(),
	                                                        this);
	if(!targetDirectory.isEmpty())
	{
		m->directorySelectionHandler->movePaths(ui->lvFiles->selectedPaths(), targetDirectory);
	}
}

void GUI_DirectoryView::fileOperationStarted()
{
	ui->tvDirs->setBusy(true);
}

void GUI_DirectoryView::fileOperationFinished()
{
	ui->tvDirs->setBusy(false);
	ui->lvFiles->setParentDirectory(ui->lvFiles->parentDirectory());
}

void GUI_DirectoryView::splitterMoved(const int /*pos*/, const int /*index*/)
{
	SetSetting(Set::Dir_SplitterDirFile, ui->splitter->saveState());
}

void GUI_DirectoryView::createDirectoryClicked()
{
	const auto libraryPath = m->directorySelectionHandler->libraryInfo().path();
	const auto text =
		Gui::LineInputDialog::getNewFilename(this, Lang::get(Lang::CreateDirectory), libraryPath);

	if(!text.isEmpty())
	{
		Util::File::createDir(m->directorySelectionHandler->libraryInfo().path() + "/" + text);
	}
}

void GUI_DirectoryView::languageChanged()
{
	if(ui)
	{
		ui->retranslateUi(this);
		ui->btnCreateDir->setText(Lang::get(Lang::CreateDirectory));
		ui->btnClearSelection->setText(Lang::get(Lang::ClearSelection));
	}
}

void GUI_DirectoryView::skinChanged()
{
	if(ui)
	{
		ui->btnCreateDir->setIcon(Gui::icon(Gui::Folder));
		ui->btnClearSelection->setIcon(Gui::icon(Gui::Clear));
	}
}

void GUI_DirectoryView::showEvent(QShowEvent* event)
{
	initUi();
	Gui::Widget::showEvent(event);
}
