/*
	This file is part of Fennix Kernel.

	Fennix Kernel is free software: you can redistribute it and/or
	modify it under the terms of the GNU General Public License as
	published by the Free Software Foundation, either version 3 of
	the License, or (at your option) any later version.

	Fennix Kernel is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#include <driver.hpp>
#include <interface/fs.h>
#include <memory.hpp>
#include <functional>
#include <debug.h>

#include "../../../kernel.h"

using namespace vfs;

namespace Driver::UnixStandardTAR
{
	dev_t DriverID;

	enum TypeFlag
	{
		AREGTYPE = '\0',
		REGTYPE = '0',
		LNKTYPE = '1',
		SYMTYPE = '2',
		CHRTYPE = '3',
		BLKTYPE = '4',
		DIRTYPE = '5',
		FIFOTYPE = '6',
		CONTTYPE = '7'
	};

	enum ModeFlag
	{
		TSUID = 04000,
		TSGID = 02000,
		TSVTX = 01000,
		TUREAD = 00400,
		TUWRITE = 00200,
		TUEXEC = 00100,
		TGREAD = 00040,
		TGWRITE = 00020,
		TGEXEC = 00010,
		TOREAD = 00004,
		TOWRITE = 00002,
		TOEXEC = 00001,
	};

	struct TarHeader
	{
		char name[100];
		char mode[8];
		char uid[8];
		char gid[8];
		char size[12];
		char mtime[12];
		char chksum[8];
		char typeflag[1];
		char link[100];
		char signature[6];
		char version[2];
		char owner[32];
		char group[32];
		char dev_maj[8];
		char dev_min[8];
		char prefix[155];
		char pad[12];
	} __packed;

	constexpr static int INODE_CHECKSUM = 0x7757A4;

#define TAR_BLOCK_SIZE 512
#define TMAGIC "ustar"
#define TMAGLEN 6
#define TVERSION "00"
#define TVERSLEN 2

	inline uint32_t GetSize(const char *String)
	{
		uint32_t ret = 0;
		while (*String)
		{
			ret *= 8;
			ret += *String - '0';
			String++;
		}
		return ret;
	}

	inline int StringToInt(const char *String)
	{
		int ret = 0;
		for (int i = 0; String[i] != '\0'; ++i)
			ret = ret * 10 + String[i] - '0';
		return ret;
	}

	class USTARInstance
	{
	public:
		struct USTARInode
		{
			Inode Node;
			off_t HeaderOffset; // Offset of the header in the device
			USTARInode *Parent;
			std::string Name;
			std::string Path;
			std::vector<USTARInode *> Children;
			bool Deleted;
			int Checksum;
		};
		std::unordered_map<ino_t, USTARInode *> Files;
		ino_t NextInode = 0;
		FileSystemDevice Device;

	private:
		ssize_t DeviceRead(void *Buffer, size_t Size, off_t Offset)
		{
			if (Device.inode.node)
				return Device.inode.ops->Read(Device.inode.node, Buffer, Size, Offset);
			else if (Device.Block)
				return Device.Block->Ops->Read(nullptr, Buffer, Size, Offset);
			else
				return -EINVAL;
		}

		ssize_t DeviceWrite(const void *Buffer, size_t Size, off_t Offset)
		{
			if (Device.inode.node)
				return Device.inode.ops->Write(Device.inode.node, Buffer, Size, Offset);
			else if (Device.Block)
				return Device.Block->Ops->Write(nullptr, Buffer, Size, Offset);
			else
				return -EINVAL;
		}

	public:
		int Lookup(Inode *_Parent, const char *Name, Inode **Result)
		{
			auto Parent = (USTARInode *)_Parent;
			debug("looking up for %s", Name);
			const char *basename;
			size_t length;
			cwk_path_get_basename(Name, &basename, &length);
			if (basename == NULL)
			{
				if (strcmp(Name, "/") == 0)
				{
					auto &it = Files.at(0);
					*Result = &it->Node;
					return 0;
				}

				error("Invalid name %s", Name);
				return -EINVAL;
			}

			if (_Parent)
			{
				for (const auto &child : Parent->Children)
				{
					if (child->Deleted || strcmp(child->Name.c_str(), basename) != 0)
						continue;

					*Result = &child->Node;
					return 0;
				}

				return -ENOENT;
			}

			auto fileItr = Files.begin();
			while (fileItr != Files.end())
			{
				USTARInode *node = fileItr->second;

				if (node->Deleted || strcmp(node->Name.c_str(), basename) != 0)
				{
					fileItr++;
					continue;
				}

				*Result = &fileItr->second->Node;
				return 0;
			}
			return -ENOENT;
		}

		int Create(Inode *_Parent, const char *Name, mode_t Mode, Inode **Result)
		{
			USTARInode *Parent = (USTARInode *)_Parent;

			Inode inode{};
			inode.Mode = Mode;
			inode.Device = DriverID;
			inode.RawDevice = 0;
			inode.Index = NextInode;
			inode.Offset = 0;
			inode.PrivateData = this;

			const char *basename;
			size_t length;
			cwk_path_get_basename(Name, &basename, &length);

			auto SetMode = [&](mode_t &Mode, TarHeader *header)
			{
				if (Mode & S_IFREG)
					header->typeflag[0] = REGTYPE;
				else if (Mode & S_IFLNK)
					header->typeflag[0] = SYMTYPE;
				else if (Mode & S_IFCHR)
					header->typeflag[0] = CHRTYPE;
				else if (Mode & S_IFBLK)
					header->typeflag[0] = BLKTYPE;
				else if (Mode & S_IFDIR)
					header->typeflag[0] = DIRTYPE;
				else if (Mode & S_IFIFO)
					header->typeflag[0] = FIFOTYPE;

				mode_t final = 0;
				if (Mode & S_ISUID)
					final |= TSUID;
				else if (Mode & S_ISGID)
					final |= TSGID;
				else if (Mode & S_ISVTX)
					final |= TSVTX;
				else if (Mode & S_IRUSR)
					final |= TUREAD;
				else if (Mode & TUWRITE)
					final |= TUWRITE;
				else if (Mode & TUEXEC)
					final |= TUEXEC;
				else if (Mode & TGREAD)
					final |= TGREAD;
				else if (Mode & TGWRITE)
					final |= TGWRITE;
				else if (Mode & TGEXEC)
					final |= TGEXEC;
				else if (Mode & TOREAD)
					final |= TOREAD;
				else if (Mode & TOWRITE)
					final |= TOWRITE;
				else if (Mode & TOEXEC)
					final |= TOEXEC;

				snprintf(header->mode, sizeof(header->mode), "%07o", final);
			};

			TarHeader *hdr = new TarHeader{};
			SetMode(inode.Mode, hdr);
			strncpy(hdr->name, basename, sizeof(hdr->name));
			strncpy(hdr->signature, TMAGIC, TMAGLEN);
			strncpy(hdr->version, TVERSION, TVERSLEN);

			USTARInode *node = new USTARInode{.Node = inode,
											  .HeaderOffset = 0,
											  .Parent = Parent,
											  .Name{},
											  .Path{},
											  .Children{},
											  .Deleted = false,
											  .Checksum = INODE_CHECKSUM};

			node->Name.assign(basename, length);
			node->Path.assign(Name, strlen(Name));

			auto file = Files.insert(std::make_pair(NextInode, node));
			assert(file.second == true);
			*Result = &Files.at(NextInode)->Node;
			if (Parent)
			{
				Parent->Children.push_back(Files.at(NextInode));
				Files.at(NextInode)->Parent = Parent;
			}
			NextInode++;
			return 0;
		}

		int Remove(Inode *Parent, const char *Name)
		{
			assert(!"not implemented");
		}

		int Rename(Inode *Parent, const char *OldName, const char *NewName)
		{
			assert(!"not implemented");
		}

		ssize_t Read(Inode *Node, void *Buffer, size_t Size, off_t Offset)
		{
			auto fileItr = Files.find(Node->Index);
			assert(fileItr != Files.end());

			if (fileItr->second->Deleted)
				return -ENOENT;

			USTARInode *node = fileItr->second;
			TarHeader header;
			if (DeviceRead(&header, sizeof(TarHeader), node->HeaderOffset) != sizeof(TarHeader))
				return -EIO;
			size_t fileSize = GetSize(header.size);

			if (Size <= 0)
			{
				debug("Size is less than or equal to 0");
				Size = fileSize;
			}

			if ((size_t)Offset > fileSize)
			{
				debug("Offset %d is greater than file size %d", Offset, fileSize);
				return 0;
			}

			if ((fileSize - Offset) == 0)
			{
				debug("Offset %d is equal to file size %d", Offset, fileSize);
				return 0; /* EOF */
			}

			if ((size_t)Offset + Size > fileSize)
			{
				debug("Offset %d + Size %d is greater than file size %d",
					  Offset, Size, fileSize);
				Size = fileSize - Offset;
			}

			off_t dataOffset = node->HeaderOffset + sizeof(TarHeader) + Offset;
			if (DeviceRead(Buffer, Size, dataOffset) != (ssize_t)Size)
				return -EIO;
			return Size;
		}

		ssize_t Write(Inode *Node, const void *Buffer, size_t Size, off_t Offset)
		{
			assert(!"not implemented");
		}

		int Truncate(Inode *Node, off_t Size)
		{
			assert(!"not implemented");
		}

		int Open(Inode *Node, int Flags, mode_t Mode)
		{
			return 0;
		}

		int Close(Inode *Node)
		{
			return 0;
		}

		int Ioctl(Inode *Node, unsigned long Request, void *Argp)
		{
			assert(!"not implemented");
		}

		__no_sanitize("alignment") ssize_t ReadDir(Inode *_Node, kdirent *Buffer, size_t Size, off_t Offset, off_t Entries)
		{
			/* FIXME: FIX ALIGNMENT FOR DIRENT! */
			auto Node = (USTARInode *)_Node;
			debug("reading directory %s", Node->Path.c_str());

			off_t realOffset = Offset;

			size_t totalSize = 0;
			uint16_t reclen = 0;
			struct kdirent *ent = nullptr;

			if (Offset == 0)
			{
				reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen(".") + 1);
				if (totalSize + reclen >= Size)
					return -EINVAL;

				ent = (struct kdirent *)((uintptr_t)Buffer + totalSize);
				ent->d_ino = Node->Node.Index;
				ent->d_off = Offset++;
				ent->d_reclen = reclen;
				ent->d_type = DT_DIR;
				strcpy(ent->d_name, ".");
				totalSize += reclen;
				debug(".");
			}

			if (Offset <= 1)
			{
				reclen = (uint16_t)(offsetof(struct kdirent, d_name) + strlen("..") + 1);
				if (totalSize + reclen >= Size)
				{
					if (realOffset == 1)
						return -EINVAL;
					return totalSize;
				}

				ent = (struct kdirent *)((uintptr_t)Buffer + totalSize);

				if (Node->Parent)
					ent->d_ino = Node->Parent->Node.Index;
				else
				{
					warn("Parent is null for %s", Node->Name.c_str());
					ent->d_ino = Node->Node.Index;
				}
				ent->d_off = Offset++;
				ent->d_reclen = reclen;
				ent->d_type = DT_DIR;
				strcpy(ent->d_name, "..");
				totalSize += reclen;
				debug("..");
			}

			if (!S_ISDIR(Node->Node.Mode))
				return -ENOTDIR;

			if ((Offset >= 2 ? (Offset - 2) : Offset) > (off_t)Node->Children.size())
				return -EINVAL;

			off_t entries = 0;
			for (const auto &var : Node->Children)
			{
				if (var->Node.Offset < Offset)
					continue;

				if (entries >= Entries)
					break;

				if (var->Deleted)
					continue;

				TarHeader header;
				ssize_t hdrRead = DeviceRead(&header, sizeof(TarHeader), var->HeaderOffset);
				if (hdrRead != sizeof(TarHeader))
				{
					warn("Failed to read header for %s at offset %ld", var->Name.c_str(), var->HeaderOffset);
					continue;
				}
				size_t fileSize = GetSize(header.size);
				debug("Entry: %s, typeflag: %c, size: %zu, offset: %ld", var->Name.c_str(), header.typeflag[0], fileSize, var->HeaderOffset);

				reclen = (uint16_t)(offsetof(struct kdirent, d_name) + var->Name.size() + 1);

				if (totalSize + reclen > Size)
				{
					debug("not enough space for %s (%zu + %zu = %zu > %zu)", var->Name.c_str(), totalSize, reclen, totalSize + reclen, Size);
					break;
				}

				ent = (struct kdirent *)((uintptr_t)Buffer + totalSize);
				ent->d_ino = var->Node.Index;
				ent->d_off = var->Node.Offset;
				ent->d_reclen = reclen;
				switch (header.typeflag[0])
				{
				case AREGTYPE:
				case REGTYPE:
					ent->d_type = DT_REG;
					break;
				case LNKTYPE:
					fixme("Hard link not implemented for %s", header.name);
					ent->d_type = DT_LNK;
					break;
				case SYMTYPE:
					ent->d_type = DT_LNK;
					break;
				case CHRTYPE:
					ent->d_type = DT_CHR;
					break;
				case BLKTYPE:
					ent->d_type = DT_BLK;
					break;
				case DIRTYPE:
					ent->d_type = DT_DIR;
					break;
				case FIFOTYPE:
					ent->d_type = DT_FIFO;
					break;
				case CONTTYPE:
				default:
					ent->d_type = 0;
					break;
				}
				strncpy(ent->d_name, var->Name.c_str(), strlen(var->Name.c_str()));
				debug("Added entry: %s, type: %d, size: %zu", var->Name.c_str(), ent->d_type, fileSize);
				totalSize += reclen;
				entries++;
			}

			if (totalSize + sizeof(struct kdirent) >= Size)
				return totalSize;

			ent = (struct kdirent *)((uintptr_t)Buffer + totalSize);
			ent->d_ino = 0;
			ent->d_off = 0;
			ent->d_reclen = 0;
			ent->d_type = DT_UNKNOWN;
			ent->d_name[0] = '\0';
			return totalSize;
		}

		int MkDir(Inode *Parent, const char *Name, mode_t Mode, Inode **Result)
		{
			assert(!"not implemented");
		}

		int RmDir(Inode *Parent, const char *Name)
		{
			assert(!"not implemented");
		}

		int SymLink(Inode *Parent, const char *Name, const char *Target, Inode **Result)
		{
			int ret = this->Create(Parent, Name, S_IFLNK, Result);
			if (ret < 0)
				return ret;

			USTARInode *node = (USTARInode *)*Result;
			TarHeader header;
			if (DeviceRead(&header, sizeof(TarHeader), node->HeaderOffset) != sizeof(TarHeader))
				return -EIO;
			strncpy(header.link, Target, MIN(sizeof(header.link) - 1, strlen(Target)));
			return 0;
		}

		ssize_t ReadLink(Inode *Node, char *Buffer, size_t Size)
		{
			auto fileItr = Files.find(Node->Index);
			assert(fileItr != Files.end());

			if (fileItr->second->Deleted)
				return -ENOENT;

			USTARInode *node = fileItr->second;
			TarHeader header;
			if (DeviceRead(&header, sizeof(TarHeader), node->HeaderOffset) != sizeof(TarHeader))
				return -EIO;

			size_t linkLen = 0;
			while (linkLen < sizeof(header.link) && header.link[linkLen] != '\0')
				++linkLen;
			if (linkLen > Size)
				linkLen = Size;
			memcpy(Buffer, header.link, linkLen);
			debug("Read %zu bytes from %d: \"%.*s\"", linkLen, Node->Index, (int)linkLen, Buffer);
			return linkLen;
		}

		off_t Seek(Inode *Node, off_t Offset)
		{
			assert(!"not implemented");
		}

		int Stat(Inode *Node, kstat *Stat)
		{
			auto fileItr = Files.find(Node->Index);
			assert(fileItr != Files.end());
			if (fileItr->second->Deleted)
				return -ENOENT;

			USTARInode *node = fileItr->second;
			TarHeader header;
			if (DeviceRead(&header, sizeof(TarHeader), node->HeaderOffset) != sizeof(TarHeader))
				return -EIO;
			size_t fileSize = GetSize(header.size);

			debug("Header: \"%.*s\"", (int)sizeof(struct TarHeader), &header);
			Stat->Device = DriverID;
			Stat->Index = Node->Index;
			Stat->HardLinks = 1;
			Stat->UserID = GetSize(header.uid);
			Stat->GroupID = GetSize(header.gid);
			Stat->RawDevice = Stat->MakeDevice(GetSize(header.dev_maj), GetSize(header.dev_min));
			Stat->Size = fileSize;
			Stat->AccessTime = GetSize(header.mtime);
			Stat->ModifyTime = GetSize(header.mtime);
			Stat->ChangeTime = GetSize(header.mtime);
			Stat->BlockSize = 512;
			Stat->Blocks = (fileSize + 511) / 512;
			Stat->Attribute = 0;

			mode_t hdrMode = StringToInt(header.mode);

			if (hdrMode & TSUID)
				Stat->Mode |= S_ISUID;
			else if (hdrMode & TSGID)
				Stat->Mode |= S_ISGID;
			else if (hdrMode & TSVTX)
				Stat->Mode |= S_ISVTX;
			else if (hdrMode & TUREAD)
				Stat->Mode |= S_IRUSR;
			else if (hdrMode & TUWRITE)
				Stat->Mode |= S_IWUSR;
			else if (hdrMode & TUEXEC)
				Stat->Mode |= S_IXUSR;
			else if (hdrMode & TGREAD)
				Stat->Mode |= S_IRGRP;
			else if (hdrMode & TGWRITE)
				Stat->Mode |= S_IWGRP;
			else if (hdrMode & TGEXEC)
				Stat->Mode |= S_IXGRP;
			else if (hdrMode & TOREAD)
				Stat->Mode |= S_IROTH;
			else if (hdrMode & TOWRITE)
				Stat->Mode |= S_IWOTH;
			else if (hdrMode & TOEXEC)
				Stat->Mode |= S_IXOTH;

			switch (header.typeflag[0])
			{
			case AREGTYPE:
			case REGTYPE:
				Stat->Mode |= S_IFREG;
				break;
			case LNKTYPE:
				fixme("Hard link not implemented for %s", header.name);
				Stat->Mode |= S_IFLNK;
				break;
			case SYMTYPE:
				Stat->Mode |= S_IFLNK;
				break;
			case CHRTYPE:
				Stat->Mode |= S_IFCHR;
				break;
			case BLKTYPE:
				Stat->Mode |= S_IFBLK;
				break;
			case DIRTYPE:
				Stat->Mode |= S_IFDIR;
				break;
			case FIFOTYPE:
				Stat->Mode |= S_IFIFO;
				break;
			case CONTTYPE:
				warn("Reserved type for %s", header.name);
				__fallthrough;
			default:
				error("Unknown type: %d for %s", header.typeflag[0], header.name);
				break;
			}
			return 0;
		}

		int ScanArchiveFromDevice()
		{
			off_t offset = 0;
			std::vector<USTARInode *> tmpNodes;

			auto SetMode = [&](Inode &uNode, TarHeader *header)
			{
				mode_t hdrMode = StringToInt(header->mode);
				if (hdrMode & TSUID)
					uNode.Mode |= S_ISUID;
				else if (hdrMode & TSGID)
					uNode.Mode |= S_ISGID;
				else if (hdrMode & TSVTX)
					uNode.Mode |= S_ISVTX;
				else if (hdrMode & TUREAD)
					uNode.Mode |= S_IRUSR;
				else if (hdrMode & TUWRITE)
					uNode.Mode |= S_IWUSR;
				else if (hdrMode & TUEXEC)
					uNode.Mode |= S_IXUSR;
				else if (hdrMode & TGREAD)
					uNode.Mode |= S_IRGRP;
				else if (hdrMode & TGWRITE)
					uNode.Mode |= S_IWGRP;
				else if (hdrMode & TGEXEC)
					uNode.Mode |= S_IXGRP;
				else if (hdrMode & TOREAD)
					uNode.Mode |= S_IROTH;
				else if (hdrMode & TOWRITE)
					uNode.Mode |= S_IWOTH;
				else if (hdrMode & TOEXEC)
					uNode.Mode |= S_IXOTH;

				switch (header->typeflag[0])
				{
				case AREGTYPE:
				case REGTYPE:
					uNode.Mode |= S_IFREG;
					break;
				case LNKTYPE:
					uNode.Mode |= S_IFLNK;
					break;
				case SYMTYPE:
					uNode.Mode |= S_IFLNK;
					break;
				case CHRTYPE:
					uNode.Mode |= S_IFCHR;
					break;
				case BLKTYPE:
					uNode.Mode |= S_IFBLK;
					break;
				case DIRTYPE:
					uNode.Mode |= S_IFDIR;
					break;
				case FIFOTYPE:
					uNode.Mode |= S_IFIFO;
					break;
				case CONTTYPE:
					warn("Reserved type for %s", header->name);
					__fallthrough;
				default:
					error("Unknown type: %d for %s", header->typeflag[0], header->name);
					break;
				}
			};

			while (true)
			{
				TarHeader header;
				if (DeviceRead(&header, sizeof(TarHeader), offset) != sizeof(TarHeader))
					break;
				if (strncmp(header.signature, TMAGIC, TMAGLEN - 1) != 0)
					break;
				if (isempty(header.name))
					break;

				/* This removes the "." at the beginning of the file name "./foo/bar" > "/foo/bar" */
				if (header.name[0] == '.' && header.name[1] == '/')
					memmove(header.name, header.name + 1, strlen(header.name));

				if (isempty((char *)header.name))
					fixme("Ignoring empty file name \"%.*s\"", sizeof(struct TarHeader), header);

				struct Inode uNode;
				uNode.Device = DriverID;
				uNode.RawDevice = 0;
				uNode.Index = NextInode;
				SetMode(uNode, &header);
				uNode.Offset = 0;
				uNode.PrivateData = this;

				const char *basename;
				size_t length;
				cwk_path_get_basename(header.name, &basename, &length);

				USTARInode *node = new USTARInode{.Node = uNode,
												  .HeaderOffset = offset,
												  .Parent = nullptr,
												  .Name{},
												  .Path{},
												  .Children{},
												  .Deleted = false,
												  .Checksum = INODE_CHECKSUM};

				if (basename)
					node->Name.assign(basename, length);
				else
					node->Name.assign((const char *)header.name, strlen(header.name));
				node->Path.assign((const char *)header.name, strlen(header.name));

				Files.insert(std::make_pair(NextInode, node));
				tmpNodes.push_back(node);

				size_t size = GetSize(header.size);
				offset += ((size / 512) + 1) * 512;
				if (size % 512)
					offset += 512;
				NextInode++;
			}

			/* TODO: This code can be significantly optimized but good luck understanding it */
			USTARInode *parent = nullptr;
			std::vector<USTARInode *> parentStack;
			std::vector<std::string *> pathStack;
			for (auto &file : tmpNodes)
			{
				if (file->Path == "/") /* This is root / */
				{
					parentStack.push_back(file);
					pathStack.push_back(&file->Path);
					// debug("root / generated");
					continue;
				}

				/* pathStack is never empty */

				if (file->Path.back() == '/') /* This is a directory */
				{
					const char *path = file->Path.c_str();
					size_t length;
					/* This converts /one/two/path.txt to /one/two/ */
					cwk_path_get_dirname(path, &length);
					std::string dirName(path, length);

					/* Check if the directory is at the same level as the current directory */
					if (dirName == *pathStack.back())
					{
						parent = parentStack.back();
						parentStack.push_back(file);
						pathStack.push_back(&file->Path);
						parent->Children.push_back(file);
						file->Parent = parent;
						// debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str());
						continue;
					}
					else
					{
						/* Check if the directory is at a higher level */
						for (size_t i = 0; i < parentStack.size(); i++)
						{
							if (dirName != *pathStack[i])
								continue;

							/* Adjust vectors */
							while (!parentStack.empty())
							{
								if (dirName == *pathStack.back())
									break;

								// debug("popping \"%s\"", pathStack.back()->c_str());
								parentStack.pop_back();
								pathStack.pop_back();
							}

							parent = parentStack.back();
							parentStack.push_back(file);
							pathStack.push_back(&file->Path);
							parent->Children.push_back(file);
							file->Parent = parent;
							// debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str());
							goto foundEnd;
						}

						// This is a new directory level
						parentStack.pop_back();
						pathStack.pop_back();
						parent = parentStack.back();
						parentStack.push_back(file);
						pathStack.push_back(&file->Path);
						parent->Children.push_back(file);
						file->Parent = parent;
						// debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str());
					foundEnd:
						continue;
					}
				}

				/* From here, it's a file */

				const char *path = file->Path.c_str();
				size_t length;
				/* This converts /one/two/path.txt to /one/two/ */
				cwk_path_get_dirname(path, &length);
				std::string dirName(path, length);

				/* Check if the file is at the same level as the current directory */
				if (dirName == *pathStack.back())
				{
					parent = parentStack.back();
					parent->Children.push_back(file);
					file->Parent = parent;
					// debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str());
					continue;
				}

				/* Check if the file is at a higher level */
				for (size_t i = 0; i < parentStack.size(); i++)
				{
					if (dirName != *pathStack[i])
						continue;

					/* Adjust vectors */
					while (!parentStack.empty())
					{
						if (dirName == *pathStack.back())
							break;

						// debug("popping \"%s\"", pathStack.back()->c_str());
						parentStack.pop_back();
						pathStack.pop_back();
					}

					parent = parentStack.back();
					parent->Children.push_back(file);
					file->Parent = parent;
					// debug("adding \"%s\" to \"%s\"", file->Path.c_str(), parent->Path.c_str());
				}
			}

			std::function<void(USTARInode *, int, const std::string &)> ustarTree = [&](USTARInode *node, int level, const std::string &prefix)
			{
				debug("%*s\"%s\"%ld", level * 4, prefix.c_str(), node->Name.c_str(), node->Node.Offset);
				off_t offset = 2; /* 0 . | 1 .. */
				for (auto &child : node->Children)
				{
#ifdef DEBUG
					if (offset <= 2)
					{
						if (offset == 2)
							offset = 0;
						/* Pseudo directories . and .. */
						USTARInode pseudoDot{};
						pseudoDot.Node = node->Node;
						pseudoDot.Name = ".";
						pseudoDot.Node.Offset = offset++;
						USTARInode pseudoDDot{};
						pseudoDDot.Node = node->Parent ? node->Parent->Node : node->Node;
						pseudoDDot.Name = "..";
						pseudoDDot.Node.Offset = offset++;
						ustarTree(&pseudoDot, level + 1, "|-- ");
						ustarTree(&pseudoDDot, level + 1, "|-- ");
					}
#endif

					child->Node.Offset = offset++;
					ustarTree(child, level + 1, child == node->Children.back() ? "`-- " : "|-- ");
				}
			};

			ustarTree(tmpNodes[0], 0, "");
			return 0;
		}
	};

	std::vector<USTARInstance *> Namespaces;

	int USTAR_AllocateInode(FileSystemInfo *Info, Inode **Result)
	{
		assert(!"not implemented");
	}

	int USTAR_DeleteInode(FileSystemInfo *Info, Inode *Node)
	{
		assert(!"not implemented");
	}

	int USTAR_Synchronize(FileSystemInfo *Info, Inode *Node)
	{
		assert(!"not implemented");
	}

	int USTAR_Destroy(FileSystemInfo *Info)
	{
		assert(!"not implemented");
	}

	int USTAR_Probe(FileSystemDevice *Device)
	{
		func("%#lx", Device);
		uint8_t buffer[TAR_BLOCK_SIZE];
		int bytesRead = 0;

		if (Device->Block)
			bytesRead = Device->Block->Ops->Read(nullptr, buffer, TAR_BLOCK_SIZE, 0);
		else if (Device->inode.node && Device->inode.ops && Device->inode.ops->Read)
			bytesRead = Device->inode.ops->Read(Device->inode.node, buffer, TAR_BLOCK_SIZE, 0);
		else
			return -EINVAL;

		if (bytesRead != TAR_BLOCK_SIZE)
			return -EIO;

		TarHeader *hdr = (TarHeader *)buffer;

		if (strncmp(hdr->signature, TMAGIC, TMAGLEN) != 0)
		{
			/* For some reason if GRUB inflates the archive, the magic is "ustar  " */
			if (strncmp(hdr->signature, TMAGIC, TMAGLEN - 1) == 0)
				return 0;

			debug("Invalid signature!");
			return -ENODEV;
		}

		return 0;
	}

	int USTAR_Mount(FileSystemInfo *FS, Inode **Root, FileSystemDevice *Device)
	{
		USTARInstance *instance = new USTARInstance();
		instance->Device = *Device;
		if (instance->ScanArchiveFromDevice() < 0)
		{
			delete instance;
			return -EIO;
		}
		// Find root inode (should be index 0)
		auto it = instance->Files.find(0);
		if (it == instance->Files.end())
		{
			delete instance;
			return -ENOENT;
		}
		*Root = &it->second->Node;
		(*Root)->PrivateData = instance;
		Namespaces.push_back(instance);
		return 0;
	}

	int USTAR_Unmount(FileSystemInfo *FS)
	{
		assert(!"not implemented");
	}

	int USTAR_Lookup(Inode *p, const char *nm, Inode **r) { return ((USTARInstance *)p->PrivateData)->Lookup(p, nm, r); }
	int USTAR_Create(Inode *p, const char *nm, mode_t m, Inode **r) { return ((USTARInstance *)p->PrivateData)->Create(p, nm, m, r); }
	int USTAR_Remove(Inode *p, const char *nm) { return ((USTARInstance *)p->PrivateData)->Remove(p, nm); }
	int USTAR_Rename(Inode *p, const char *on, const char *nn) { return ((USTARInstance *)p->PrivateData)->Rename(p, on, nn); }
	ssize_t USTAR_Read(Inode *n, void *b, size_t s, off_t o) { return ((USTARInstance *)n->PrivateData)->Read(n, b, s, o); }
	ssize_t USTAR_Write(Inode *n, const void *b, size_t s, off_t o) { return ((USTARInstance *)n->PrivateData)->Write(n, b, s, o); }
	int USTAR_Truncate(Inode *n, off_t s) { return ((USTARInstance *)n->PrivateData)->Truncate(n, s); }
	int USTAR_Open(Inode *n, int f, mode_t m) { return ((USTARInstance *)n->PrivateData)->Open(n, f, m); }
	int USTAR_Close(Inode *n) { return ((USTARInstance *)n->PrivateData)->Close(n); }
	int USTAR_Ioctl(Inode *n, unsigned long rq, void *ap) { return ((USTARInstance *)n->PrivateData)->Ioctl(n, rq, ap); }
	ssize_t USTAR_ReadDir(Inode *n, kdirent *b, size_t s, off_t o, off_t Entries) { return ((USTARInstance *)n->PrivateData)->ReadDir(n, b, s, o, Entries); }
	int USTAR_MkDir(Inode *p, const char *nm, mode_t m, Inode **r) { return ((USTARInstance *)p->PrivateData)->MkDir(p, nm, m, r); }
	int USTAR_RmDir(Inode *p, const char *nm) { return ((USTARInstance *)p->PrivateData)->RmDir(p, nm); }
	int USTAR_SymLink(Inode *p, const char *nm, const char *t, Inode **r) { return ((USTARInstance *)p->PrivateData)->SymLink(p, nm, t, r); }
	ssize_t USTAR_ReadLink(Inode *n, char *b, size_t s) { return ((USTARInstance *)n->PrivateData)->ReadLink(n, b, s); }
	off_t USTAR_Seek(Inode *n, off_t o) { return ((USTARInstance *)n->PrivateData)->Seek(n, o); }
	int USTAR_Stat(Inode *n, kstat *st) { return ((USTARInstance *)n->PrivateData)->Stat(n, st); }

	static SuperBlockOperations ustarSuperOps = {
		.AllocateInode = USTAR_AllocateInode,
		.DeleteInode = USTAR_DeleteInode,
		.Synchronize = USTAR_Synchronize,
		.Destroy = USTAR_Destroy,
		.Probe = USTAR_Probe,
		.Mount = USTAR_Mount,
		.Unmount = USTAR_Unmount};

	static InodeOperations ustarInodeOps = {
		.Lookup = USTAR_Lookup,
		.Create = USTAR_Create,
		.Remove = USTAR_Remove,
		.Rename = USTAR_Rename,
		.Read = USTAR_Read,
		.Write = USTAR_Write,
		.Truncate = USTAR_Truncate,
		.Open = USTAR_Open,
		.Close = USTAR_Close,
		.Ioctl = USTAR_Ioctl,
		.ReadDir = USTAR_ReadDir,
		.MkDir = USTAR_MkDir,
		.RmDir = USTAR_RmDir,
		.SymLink = USTAR_SymLink,
		.ReadLink = USTAR_ReadLink,
		.Seek = USTAR_Seek,
		.Stat = USTAR_Stat};

	int Entry()
	{
		FileSystemInfo *fsi = new FileSystemInfo;
		fsi->Name = "Unix Standard TAR";
		fsi->SuperOps = ustarSuperOps;
		fsi->Ops = ustarInodeOps;
		v0::RegisterFileSystem(DriverID, fsi);
		return 0;
	}

	int Final()
	{
		for (auto &&i : Namespaces)
			delete i;
		return 0;
	}

	int Panic() { return 0; }
	int Probe() { return 0; }

	REGISTER_BUILTIN_DRIVER(ustar,
							"Unix Standard TAR Driver",
							"enderice2",
							1, 0, 0,
							Entry,
							Final,
							Panic,
							Probe);
}
