#pragma once

#include "fs/AbsPath.h"
#include "tl/ptr.h"

#include "fs/Error.h"
#include "fs/Mode.h"

#include "fs/ISource.h"
#include "fs/IMapSource.h"
#include "fs/ISink.h"
#include "fs/IMapSink.h"
#include "fs/Api.h"
#include <cppcoro/generator.hpp>


namespace fs
{
using OpenSourceResult = tl::result<tl::unique_ref<ISource>, Error>;
using OpenStreamSourceResult = tl::result<tl::unique_ref<IStreamSource>, Error>;
using OpenMapSourceResult = tl::result<tl::unique_ref<IMapSource>, Error>;
using OpenSinkResult = tl::result<tl::unique_ref<ISink>, Error>;
using OpenStreamSinkResult = tl::result<tl::unique_ref<IStreamSink>, Error>;
using OpenMapSinkResult = tl::result<tl::unique_ref<IMapSink>, Error>;
using ConvertToNativePathResult = tl::result<AbsPath, Error>;
using IsFileResult = tl::result<bool, Error>;
using IsFolderResult = tl::result<bool, Error>;
using ExistsResult = tl::result<bool, Error>;
using MakeFolderResult = tl::result<void, Error>;
using RenameResult = tl::result<void, Error>;
using RemoveResult = tl::result<void, Error>;
using CopyResult = tl::result<void, Error>;
using MakeHardLinkResult = tl::result<void, Error>;
using MakeSoftLinkResult = tl::result<void, Error>;
using RemoveRecursivelyResult = tl::result<void, Error>;
struct Stat
{
	uint64_t size = 0;
	time_t mTime = 0;
	time_t aTime = 0;
};
using GetStatResult = tl::result<Stat, Error>;
using SetWriteTimeResult = tl::result<void, Error>;

struct EnumerateEntry
{
	RelPathView path;
	bool isFolder = false;
};

class FS_API IFilesystem
{
public:
	//////////////////////////////////////////////////////////////////////////

	virtual ~IFilesystem() = default;

	using SourceFlag = ISource::Flag;
	using SourceFlags = ISource::Flags;

	struct MapView
	{
		MapView(uint64_t offset = 0, size_t size = 0)
			: offset(offset), size(size) {}
		uint64_t offset;
		size_t size; //zero size means full size
	};

	//Opens a Read-only Stream
	//Searches for the stream in the packs from the mountpoint in top to bottom order, and opens if found
	virtual OpenSourceResult openSource(AbsPathView path, SourceFlags flags = SourceFlags()) const = 0;
	virtual OpenStreamSourceResult openStreamSource(AbsPathView path, SourceFlags flags = SourceFlags()) const = 0;
	virtual OpenMapSourceResult openMapSource(AbsPathView path, MapView mapView = MapView(), SourceFlags flags = SourceFlags()) const = 0;

	using SinkFlag = ISink::Flag;
	using SinkFlags = ISink::Flags;

	//Opens a Write-only Stream
	//Searches for the stream in the packs from the mountpoint in top to bottom order, and opens if found.
	//If used with the CREATE flag, it will create the stream if not found.
	//NOTE: this will fail if the mountpoint has only read-only packs (zip files, etc)
	virtual OpenSinkResult openSink(AbsPathView path, Mode mode, SinkFlags flags = SinkFlags()) = 0;
	virtual OpenStreamSinkResult openStreamSink(AbsPathView path, Mode mode, SinkFlags flags = SinkFlags()) = 0;
	virtual OpenMapSinkResult openMapSink(AbsPathView path, Mode mode, size_t size, SinkFlags flags = SinkFlags()) = 0;

	//////////////////////////////////////////////////////////////////////////

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

	virtual IsFileResult isFile(AbsPathView path) const = 0;
	virtual IsFolderResult isFolder(AbsPathView path) const = 0;
	virtual ExistsResult exists(AbsPathView path) const = 0;

	virtual GetStatResult getStat(AbsPathView path) const = 0;

	virtual MakeFolderResult makeFolder(AbsPathView path) = 0;

	virtual RenameResult rename(AbsPathView path, AbsPathView newPath) = 0;

	//this removes one file or one folder. The folder is removed ONLY if it's empty. If you want to remove a non-empty folder, use RemoveRecursively
	virtual RemoveResult remove(AbsPathView path) = 0;

	virtual CopyResult copy(AbsPathView path, AbsPathView newPath) = 0;

	// Hard links are essentially a new name for the same file data.
	// The file becomes reference counted and the data will be deleted when all handles to it are removed from the FS.
	virtual MakeHardLinkResult makeHardLink(AbsPathView sourcePath, AbsPathView linkPath) = 0;

	// Soft links are like 'weak' pointers to a file. If the original gets deleted the soft link becomes 'null'.
	virtual MakeSoftLinkResult makeSymLink(AbsPathView sourcePath, AbsPathView linkPath) = 0;

	//Use this function in order to remove the set folder and all of the files and folders contained inside.
	//This function is not atomic; if the removal of any of the folders or files fails, the process will be stopped
	//and the function will return false, but any of the previously removed files will be already removed.
	virtual RemoveRecursivelyResult removeRecursively(AbsPathView path) = 0;

	virtual SetWriteTimeResult setWriteTime(AbsPathView path, time_t time) = 0;

	//The AbsPath is a copy, because there are lifetime subtleties when using coroutines.
	//In particular, without pass-by-value, this would crash because nobody is storing the rvalue:
	// for (auto e: fs.enumerate(path + "other folder"))
	// {}
	virtual cppcoro::generator<EnumerateEntry> enumerate(AbsPath path) const = 0;
	virtual cppcoro::generator<EnumerateEntry> enumerateRecursively(AbsPath path) const = 0;
};


}

