/*
 * Copyright © 2021-2026 Pierrick Caillon <pierrick.caillon+zipdirfs@megami.fr>
 */
#include "ZipDirFs/Fuse/ZipRootDirectory.h"
#include "ZipDirFs/Components/ChangedStart.h"
#include "ZipDirFs/Components/EntryListProxyIncremental.h"
#include "ZipDirFs/Components/NativeChanged.h"
#include "ZipDirFs/Components/ZipDirectoryEnumerator.h"
#include "ZipDirFs/Components/ZipFactory.h"
#include "ZipDirFs/Containers/EntryGenerator.h"
#include "ZipDirFs/Containers/EntryList.h"
#include "ZipDirFs/Fuse/ZipDirectory.h"
#include "ZipDirFs/Fuse/ZipFile.h"
#include "ZipDirFs/Utilities/FileSystem.h"
#include "ZipDirFs/Utilities/list_cleanup.h"
#include <filesystem>
#include <memory>
#include <utility>

namespace ZipDirFs::Fuse
{
	namespace
	{
		namespace fs = std::filesystem;

		using namespace ::ZipDirFs::Components;
		using Containers::Helpers::Changed;
		using Containers::Helpers::EntryListProxy;

		struct FirstValue
		{
			FirstValue(const fs::path& path) : path(path) {}
			Changed::FileTime operator()() const { return NativeChanged::getTime(path); }

		private:
			const fs::path& path;
		};

		struct BuildReal
		{
			BuildReal(const fs::path& path) : path(path) {}
			std::unique_ptr<Changed> operator()() const
			{
				return {std::make_unique<NativeChanged>(path)};
			}

		private:
			const fs::path& path;
		};

		struct Resolve
		{
			Resolve(EntryGenerator::factory_ptr factory) : factory(std::move(factory)) {}
			entry* operator()(const std::string& name) const
			{
				return factory->create(name);
			}

		private:
			EntryGenerator::factory_ptr factory;
		};

		std::shared_ptr<ChangedProxy> getChangedProxy(
			const fs::path& p, std::shared_ptr<ChangedProxy>& t)
		{
			return std::make_shared<ChangedProxy>(std::unique_ptr<Changed>(
				std::make_unique<ChangedStart>(FirstValue(p), BuildReal(p), t)));
		}
		std::shared_ptr<EntryListProxy> getEntryListProxy(
			const EntryGenerator::factory_ptr& factory,
			const std::shared_ptr<EntryListProxy>& proxy)
		{
			return std::make_shared<EntryListProxyProxy>(std::unique_ptr<EntryListProxy>(
				std::make_unique<EntryListProxyIncremental>(
					Resolve(factory), EntryGenerator::proxy_ptr(proxy))));
		}
	} // namespace

	std::string ZipRootDirectoryItem;

	ZipRootDirectory::ZipRootDirectory(fs::path p) :
		path(std::move(p)), _changed(getChangedProxy(path, _changed)),
		_factory(new ZipFactory<ZipDirectory, ZipFile>(
			path, ZipRootDirectoryItem, EntryGenerator::changed_ptr(_changed))),
		_proxyBase(Containers::EntryList<>::createWithProxy()),
		_proxy(getEntryListProxy(_factory, _proxyBase)), _locker(new EntryGenerator::locker_type()),
		_generator(EntryGenerator::proxy_ptr(_proxyBase), EntryGenerator::changed_ptr(_changed),
			std::make_shared<ZipDirectoryEnumerator>(path, ZipRootDirectoryItem),
			EntryGenerator::factory_ptr(_factory), EntryGenerator::locker_ptr(_locker))
	{
	}
	ZipRootDirectory::ZipRootDirectory(fs::path p, std::string f) :
		path(std::move(p)), folder(std::move(f)), _changed(getChangedProxy(path, _changed)),
		_factory(new ZipFactory<ZipDirectory, ZipFile>(
			path, folder, EntryGenerator::changed_ptr(_changed))),
		_proxyBase(Containers::EntryList<>::createWithProxy()),
		_proxy(getEntryListProxy(_factory, _proxyBase)), _locker(new EntryGenerator::locker_type()),
		_generator(EntryGenerator::proxy_ptr(_proxyBase), EntryGenerator::changed_ptr(_changed),
			std::make_shared<ZipDirectoryEnumerator>(path, folder),
			EntryGenerator::factory_ptr(_factory), EntryGenerator::locker_ptr(_locker))
	{
	}
	ZipRootDirectory::~ZipRootDirectory()
	{
		Utilities::CleanupEntryList(*_proxyBase, *_factory);
	}
	std::time_t ZipRootDirectory::getChangeTime() const { return std::time_t{Changed::FileTime{*_changed}}; }
	std::time_t ZipRootDirectory::getModificationTime() const { return std::time_t{Changed::FileTime{*_changed}}; }
	EntryGenerator& ZipRootDirectory::generator() { return _generator; }
	EntryGenerator::proxy_ptr& ZipRootDirectory::proxy() { return _proxy; }
	EntryGenerator::locker_ptr& ZipRootDirectory::locker() { return _locker; }

	int ZipRootDirectory::opendir(fuse_file_info& finfo)
	{
		try
		{
			generator().begin();
		}
		catch (Exception const& ex)
		{
			if (ex.code() != 0)
			{
				return -ex.code();
			}
			return -EIO;
		}
		return base<DefaultDirectoryNode>().opendir(finfo);
	}

} // namespace ZipDirFs::Fuse
