#pragma once

#include "fs/IPack.h"
#include "fs/IFilesystem.h"
#include "fs/Error.h"
#include "fs/Mode.h"
#include "fs/Api.h"
#include "tl/vector.h"
#include "tl/identifier.h"

namespace fs
{

class FS_API CustomFilesystem final : public IFilesystem
{
public:
	CustomFilesystem() = default;
	CustomFilesystem(CustomFilesystem&&) = delete;
	CustomFilesystem& operator=(CustomFilesystem&&) = delete;
	CustomFilesystem(const CustomFilesystem&) = delete;
	CustomFilesystem& operator=(const CustomFilesystem&) = delete;

	//////////////////////////////////////////////////////////////////////////
	// New API

	TL_DECLARE_INTEGRAL_ID(PackId, uint64_t)

	tl::result<PackId> mountFront(AbsPathView mountPoint, tl::unique_ref<IPack> pack);
	tl::result<PackId> mountBack(AbsPathView mountPoint, tl::unique_ref<IPack> pack);

	//unmounts all the packs for this point. It basically deletes the mount point
	cppcoro::generator<tl::unique_ref<IPack>> unmountAll();

	//When unmounting, the Filesystem returns the pack back to you (if found)
	tl::unique_ptr<IPack> unmount(PackId packId);

	//Returns the physical location of the virtual pack. 
	ConvertToNativePathResult convertToNativePath(AbsPathView path) const override;

	OpenSourceResult openSource(AbsPathView path, SourceFlags flags = SourceFlags()) const override;
	OpenStreamSourceResult openStreamSource(AbsPathView path, SourceFlags flags = SourceFlags()) const override;
	OpenMapSourceResult openMapSource(AbsPathView path, MapView mapView = MapView(), SourceFlags flags = SourceFlags()) const override;
	OpenSinkResult openSink(AbsPathView path, Mode mode, SinkFlags flags = SinkFlags()) override;
	OpenStreamSinkResult openStreamSink(AbsPathView path, Mode mode, SinkFlags flags = SinkFlags()) override;
	OpenMapSinkResult openMapSink(AbsPathView path, Mode mode, size_t size, SinkFlags flags = SinkFlags()) override;

	IsFileResult isFile(AbsPathView path) const override;
	IsFolderResult isFolder(AbsPathView path) const override;
	ExistsResult exists(AbsPathView path) const override;

	MakeFolderResult makeFolder(AbsPathView path) override;

	RenameResult rename(AbsPathView path, AbsPathView newPath) override;

	RemoveResult remove(AbsPathView path) override;
	RemoveRecursivelyResult removeRecursively(AbsPathView path) override;

	CopyResult copy(AbsPathView path, AbsPathView newPath) override;

	MakeHardLinkResult makeHardLink(AbsPathView sourcePath, AbsPathView linkPath) override;
	MakeSoftLinkResult makeSymLink(AbsPathView sourcePath, AbsPathView linkPath) override;

	SetWriteTimeResult setWriteTime(AbsPathView path, time_t time) override;

	GetStatResult getStat(AbsPathView path) const override;

	cppcoro::generator<EnumerateEntry> enumerate(AbsPath path) const override;
	cppcoro::generator<EnumerateEntry> enumerateRecursively(AbsPath path) const override;

private:
	struct PackData
	{
		PackId id;
		tl::unique_ref<IPack> pack;
		AbsPath mountPoint;

		explicit PackData(tl::unique_ref<IPack>&& pack, PackId i_packId) noexcept
			: pack(std::move(pack))
			, id(i_packId)
		{}

		PackData(PackData&& other) noexcept = default;
		PackData& operator=(PackData&& other) noexcept = default;
		PackData(const PackData& other) noexcept = delete;
		PackData& operator=(const PackData& other) noexcept = delete;
	};
	tl::vector<PackData> m_packsData;

	void convertToPackPath(AbsPath& packPath, AbsPathView path, AbsPathView mountPoint) const;

	enum class MountPolicy
	{
		Front, 		//the pack will be mounted over packs from the same mount point. This pack will take precedence
		Back 		//the pack will be at the bottom of existing packs from the same mount point. Its streams will be searched last
	};
	tl::result<PackId> mount(AbsPathView mountPoint, tl::unique_ref<IPack> pack, MountPolicy policy);

};

}
