#pragma once

#include "tl/result.h"
#include "tl/string.h"
#include "tl/vector_map.h"
#include "fs/IStreamSource.h"

#include "fs/IPack.h"
#include "fs/AbsPath.h"

#include "fs/Api.h"

struct zip;
struct zip_source;
struct zip_file;
namespace tl
{
template<> struct ptr_traits<zip>
{
	static constexpr inline bool custom_deleter = true;
	static constexpr inline bool requires_virtual_destructor_check = false;
};
//template<> struct ptr_traits<zip_source>
//{
//	static constexpr inline bool custom_deleter = true;
//	static constexpr inline bool requires_virtual_destructor_check = false;
//};
template<> struct ptr_traits<zip_file>
{
	static constexpr inline bool custom_deleter = true;
	static constexpr inline bool requires_virtual_destructor_check = false;
};
}

namespace fs
{
class MapSourceView;

class FS_API ZipPack final : public IPack
{
public:
	ZipPack() = default;
	~ZipPack() override = default;

	typedef tl::result<tl::unique_ref<ZipPack>, Error> CreateResult;

	static CreateResult create(tl::unique_ref<IMapSource> source);
	static CreateResult create(tl::lent_ref<const IFilesystem> filesystem, AbsPathView path);

	void setEncryptionData(const tl::string& key, uint32_t rounds);

	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;

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

	GetStatResult getStat(AbsPathView path) const override;

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

	tl::result<AbsPath, Error> convertToNativePath(AbsPathView path) const override;

	struct ZipData
	{
		tl::unique_ptr<IMapSource> fsMapSource;

		tl::lent_ptr<const IFilesystem> filesystem;
		AbsPath path;
		tl::unique_ptr<ISource> fsSource;
		uint64_t cachedSize = uint64_t(-1);

		//tl::unique_ptr<zip_source> zipSource;
		tl::unique_ptr<zip> zipArchive;
	};

protected:
	struct File;
	struct Folder;
	struct EntryIndex;

	ZipPack(tl::shared_ptr<ZipData> data);

	//OpenMapSourceResult openRawMapSource(const File& file, MapView mapView) const;
	//OpenSourceResult openRawSource(const File& file) const;

	OpenSourceResult _openSource(const File& file) const;
	OpenMapSourceResult _openMapSource(const File& file) const;
	OpenStreamSourceResult _openStreamSource(const File& file) const;

	uint16_t getOrAddFolder(AbsPathView folderPath, tl::vector<tl::vector<EntryIndex>>& io_childrenIndices);
	void createEntries(zip& archive);

	struct File
	{
		uint16_t zipIndex = 0; //index inside the zip archive
		tl::string name;
		uint16_t parentIndex = 0;
	};
	static_assert(sizeof(File) <= sizeof(tl::string) + 40, "Check your sizes!!!");

	struct Folder
	{
		uint16_t zipIndex = 0; //index inside the zip archive
		tl::string name;
		uint16_t parentIndex = 0;
		uint16_t childrenStartIndex = 0; // this is an index inside m_childrenIndices
		uint16_t childrenCount = 0;
	};
	static_assert(sizeof(Folder) <= sizeof(tl::string) + 20, "Check your sizes!!!");

	struct EntryIndex
	{
		EntryIndex() = default;
		EntryIndex(bool isFolder, uint16_t index)
			: isFolder(isFolder)
			, index(index)
		{
		}
		uint32_t isFolder : 1;
		uint32_t index : 16;
	};
	static_assert(sizeof(EntryIndex) == 4, "Check your sizes!!!");

private:
	mutable std::mutex m_zipMapSourceLock;
	tl::shared_ptr<ZipData> m_zipData;

	tl::string m_encryptionKey;
	uint32_t m_encryptionRounds = 32;

	tl::vector_map<uint64_t, EntryIndex> m_pathToIndex;
	tl::vector<File> m_files;
	tl::vector<Folder> m_folders;
	tl::vector<EntryIndex> m_childrenIndices; // these are indices inside the m_files or m_folders

	void buildUpPath(RelPath& path, const Folder& folder, uint32_t toIndex) const;
	void buildUpPath(RelPath& path, const File& file, uint32_t toIndex) const;
};

}
