/*
 * Copyright © 2021-2026 Pierrick Caillon <pierrick.caillon+zipdirfs@megami.fr>
 */
#include "ZipDirFs/Fuse/ZipDirectory.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/Components/ZipFileChanged.h"
#include "ZipDirFs/Containers/EntryGenerator.h"
#include "ZipDirFs/Containers/EntryList.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, const std::string& item) :
				path(path), item(item)
			{
			}
			Changed::FileTime operator()() const { return NativeChanged::getTime(path); }

		private:
			const fs::path& path;
			const std::string& item;
		};

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

		private:
			const fs::path& path;
			const std::string& item;
		};

		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,
			const std::string& f, std::shared_ptr<ChangedProxy>& t)
		{
			return std::make_shared<ChangedProxy>(std::unique_ptr<Changed>(
				std::make_unique<ChangedStart>(FirstValue(p, f), BuildReal(p, f), 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

	ZipDirectory::ZipDirectory(
		fs::path p, std::string f, EntryGenerator::changed_ptr& rc) :
		zip(std::move(p)),
		folder(std::move(f)), _changed(getChangedProxy(zip, folder, _changed)), _rootChanged(rc),
		_factory(new ZipFactory<ZipDirectory, ZipFile>(
			zip, folder, EntryGenerator::changed_ptr(rc))),
		_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>(zip, folder),
			EntryGenerator::factory_ptr(_factory), EntryGenerator::locker_ptr(_locker))
	{
	}
	ZipDirectory::~ZipDirectory()
	{
		Utilities::CleanupEntryList(*_proxyBase, *_factory);
	}
	std::time_t ZipDirectory::getParentModificationTime() const { return std::time_t{Changed::FileTime{*_rootChanged}}; }
	std::time_t ZipDirectory::getModificationTime() const
	{
		const Changed::FileTime changed {*_changed};
		return std::time_t{changed != Changed::FileTime{} ? changed : Changed::FileTime{*_rootChanged}};
	}
	EntryGenerator& ZipDirectory::generator() { return _generator; }
	EntryGenerator::proxy_ptr& ZipDirectory::proxy() { return _proxy; }
	EntryGenerator::locker_ptr& ZipDirectory::locker() { return _locker; }

	int ZipDirectory::stat(struct stat& stbuf)
	{
		try
		{
			return basic_directory::stat(stbuf);
		}
		catch (Exception const& ex)
		{
			if (ex.code() != 0)
			{
				return -ex.code();
			}
			return -EIO;
		}
	}

	int ZipDirectory::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
