/*
	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/driver.h>
#include <interface/input.h>
#include <memory.hpp>
#include <stropts.h>
#include <ints.hpp>
#include <task.hpp>
#include <printf.h>
#include <exec.hpp>
#include <rand.hpp>
#include <cwalk.h>
#include <md5.h>

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

using namespace vfs;

namespace Driver
{
	/**
	 * maj = 0
	 *	min:
	 *		0 - <ROOT>
	 *		1 - /proc/self
	 *		2 - /dev/null
	 *		3 - /dev/zero
	 *		4 - /dev/random
	 *		5 - /dev/mem
	 *		6 - /dev/kcon
	 *		7 - /dev/tty
	 *		8 - /dev/ptmx
	 *
	 * maj = 1
	 * 	min:
	 *		0 - /dev/input/keyboard
	 *		1 - /dev/input/mouse
	 *		..- /dev/input/eventN
	 *
	 * maj = 2
	 * 	min:
	 *		0 - /dev/fb0
	 *		..- /dev/fbN
	 */

	TTY::PTMXDevice *ptmx = nullptr;

	int __fs_Lookup(struct Inode *_Parent, const char *Name, struct Inode **Result)
	{
		func("%#lx %s %#lx", _Parent, Name, Result);

		assert(_Parent != nullptr);

		const char *basename;
		size_t length;
		cwk_path_get_basename(Name, &basename, &length);
		if (basename == NULL)
		{
			error("Invalid name %s", Name);
			return -EINVAL;
		}

		auto Parent = (Manager::DeviceInode *)_Parent;
		for (const auto &child : Parent->Children)
		{
			debug("Comparing %s with %s", basename, child->Name.c_str());
			if (strcmp(child->Name.c_str(), basename) != 0)
				continue;

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

		debug("Not found %s", Name);
		return -ENOENT;
	}

	int __fs_Create(struct Inode *_Parent, const char *Name, mode_t Mode, struct Inode **Result)
	{
		func("%#lx %s %d", _Parent, Name, Mode);

		assert(_Parent != nullptr);

		/* We expect to be /dev or children of it */
		auto Parent = (Manager::DeviceInode *)_Parent;
		auto _dev = new Manager::DeviceInode;
		_dev->Parent = nullptr;
		_dev->ParentInode = _Parent;
		_dev->Name = Name;
		_dev->Node.Mode = Mode;
		_dev->Node.Index = Parent->Node.Index + Parent->Children.size();
		Parent->Children.push_back(_dev);

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

	ssize_t __fs_Read(struct Inode *Node, void *Buffer, size_t Size, off_t Offset)
	{
		func("%#lx %d %d", Node, Size, Offset);
		switch (Node->GetMajor())
		{
		case 0:
		{
			switch (Node->GetMinor())
			{
			case 2: /* /dev/null */
			{
				return 0;
			}
			case 3: /* /dev/zero */
			{
				if (Size <= 0)
					return 0;

				memset(Buffer, 0, Size);
				return Size;
			}
			case 4: /* /dev/random */
			{
				if (Size <= 0)
					return 0;

				if (Size < sizeof(uint64_t))
				{
					uint8_t *buf = (uint8_t *)Buffer;
					for (size_t i = 0; i < Size; i++)
						buf[i] = (uint8_t)(Random::rand16() & 0xFF);
					return Size;
				}

				uint64_t *buf = (uint64_t *)Buffer;
				for (size_t i = 0; i < Size / sizeof(uint64_t); i++)
					buf[i] = Random::rand64();
				return Size;
			}
			case 5: /* /dev/mem */
			{
				stub;
				return 0;
			}
			case 6: /* /dev/kcon */
				return KernelConsole::CurrentTerminal.load()->Read(Buffer, Size, Offset);
			case 7: /* /dev/tty */
			{
				TTY::TeletypeDriver *tty = (TTY::TeletypeDriver *)thisProcess->tty;
				if (tty == nullptr)
					return -ENOTTY;
				return tty->Read(Buffer, Size, Offset);
			}
			case 8: /* /dev/ptmx */
				return -ENOSYS;
			default:
				return -ENOENT;
			};
			break;
		}
		case 1:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/input/keyboard */
			{
				if (Size < sizeof(KeyboardReport))
					return -EINVAL;

				size_t nReads = Size / sizeof(KeyboardReport);

				KeyboardReport *report = (KeyboardReport *)Buffer;

				while (DriverManager->GlobalKeyboardInputReports.Count() == 0)
					TaskManager->Yield();

				DriverManager->GlobalKeyboardInputReports.Read(report, nReads);
				return sizeof(KeyboardReport) * nReads;
			}
			case 1: /* /dev/input/mouse */
			{
				if (Size < sizeof(MouseReport))
					return -EINVAL;

				size_t nReads = Size / sizeof(MouseReport);

				MouseReport *report = (MouseReport *)Buffer;

				while (DriverManager->GlobalMouseInputReports.Count() == 0)
					TaskManager->Yield();

				DriverManager->GlobalMouseInputReports.Read(report, nReads);
				return sizeof(MouseReport) * nReads;
			}
			default:
				return -ENOENT;
			};
		}
		case 2:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/fb0 */
				return -EINVAL;
			default:
				return -ENOENT;
			}
		}
		default:
		{
			std::unordered_map<dev_t, Driver::DriverObject> &drivers =
				DriverManager->GetDrivers();
			const auto it = drivers.find(Node->GetMajor());
			if (it == drivers.end())
				ReturnLogError(-EINVAL, "Driver %d not found", Node->GetMajor());
			const Driver::DriverObject *drv = &it->second;

			auto dop = drv->DeviceOperations;
			auto dOps = dop->find(Node->GetMinor());
			if (dOps == dop->end())
				ReturnLogError(-EINVAL, "Device %d not found", Node->GetMinor());
			AssertReturnError(dOps->second.Ops, -ENOTSUP);
			AssertReturnError(dOps->second.Ops->Read, -ENOTSUP);
			return dOps->second.Ops->Read(Node, Buffer, Size, Offset);
		}
		}
	}

	ssize_t __fs_Write(struct Inode *Node, const void *Buffer, size_t Size, off_t Offset)
	{
		func("%#lx %p %d %d", Node, Buffer, Size, Offset);

		switch (Node->GetMajor())
		{
		case 0:
		{
			switch (Node->GetMinor())
			{
			case 2: /* /dev/null */
			{
				return Size;
			}
			case 3: /* /dev/zero */
			{
				return Size;
			}
			case 4: /* /dev/random */
			{
				return Size;
			}
			case 5: /* /dev/mem */
			{
				stub;
				return 0;
			}
			case 6: /* /dev/kcon */
				return KernelConsole::CurrentTerminal.load()->Write(Buffer, Size, Offset);
			case 7: /* /dev/tty */
			{
				TTY::TeletypeDriver *tty = (TTY::TeletypeDriver *)thisProcess->tty;
				if (tty == nullptr)
					return -ENOTTY;
				return tty->Write(Buffer, Size, Offset);
			}
			case 8: /* /dev/ptmx */
				return -ENOSYS;
			default:
				return -ENOENT;
			};
		}
		case 1:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/input/keyboard */
			{
				return -ENOTSUP;
			}
			case 1: /* /dev/input/mouse */
			{
				return -ENOTSUP;
			}
			default:
				return -ENOENT;
			};
		}
		case 2:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/fb0 */
				return -EINVAL;
			default:
				return -ENOENT;
			}
		}
		default:
		{
			std::unordered_map<dev_t, Driver::DriverObject> &drivers =
				DriverManager->GetDrivers();
			const auto it = drivers.find(Node->GetMajor());
			if (it == drivers.end())
				ReturnLogError(-EINVAL, "Driver %d not found", Node->GetMajor());
			const Driver::DriverObject *drv = &it->second;

			auto dop = drv->DeviceOperations;
			auto dOps = dop->find(Node->GetMinor());
			if (dOps == dop->end())
				ReturnLogError(-EINVAL, "Device %d not found", Node->GetMinor());
			AssertReturnError(dOps->second.Ops, -ENOTSUP);
			AssertReturnError(dOps->second.Ops->Write, -ENOTSUP);
			return dOps->second.Ops->Write(Node, Buffer, Size, Offset);
		}
		}
	}

	int __fs_Open(struct Inode *Node, int Flags, mode_t Mode)
	{
		func("%#lx %d %d", Node, Flags, Mode);

		switch (Node->GetMajor())
		{
		case 0:
		{
			switch (Node->GetMinor())
			{
			case 2: /* /dev/null */
			case 3: /* /dev/zero */
			case 4: /* /dev/random */
			case 5: /* /dev/mem */
				return -ENOSYS;
			case 6: /* /dev/kcon */
				return KernelConsole::CurrentTerminal.load()->Open(Flags, Mode);
			case 7: /* /dev/tty */
			{
				TTY::TeletypeDriver *tty = (TTY::TeletypeDriver *)thisProcess->tty;
				if (tty == nullptr)
					return -ENOTTY;
				return tty->Open(Flags, Mode);
			}
			case 8: /* /dev/ptmx */
				return ptmx->Open();
			default:
				return -ENOENT;
			};
		}
		case 1:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/input/keyboard */
			case 1: /* /dev/input/mouse */
				return -ENOTSUP;
			default:
				return -ENOENT;
			};
		}
		default:
		{
			std::unordered_map<dev_t, Driver::DriverObject> &drivers =
				DriverManager->GetDrivers();
			const auto it = drivers.find(Node->GetMajor());
			if (it == drivers.end())
				ReturnLogError(-EINVAL, "Driver %d not found", Node->GetMajor());
			const Driver::DriverObject *drv = &it->second;

			auto dop = drv->DeviceOperations;
			auto dOps = dop->find(Node->GetMinor());
			if (dOps == dop->end())
				ReturnLogError(-EINVAL, "Device %d not found", Node->GetMinor());
			AssertReturnError(dOps->second.Ops, -ENOTSUP);
			AssertReturnError(dOps->second.Ops->Open, -ENOTSUP);
			return dOps->second.Ops->Open(Node, Flags, Mode);
		}
		}
	}

	int __fs_Close(struct Inode *Node)
	{
		func("%#lx", Node);

		switch (Node->GetMajor())
		{
		case 0:
		{
			switch (Node->GetMinor())
			{
			case 2: /* /dev/null */
			case 3: /* /dev/zero */
			case 4: /* /dev/random */
			case 5: /* /dev/mem */
				return -ENOSYS;
			case 6: /* /dev/kcon */
				return KernelConsole::CurrentTerminal.load()->Close();
			case 7: /* /dev/tty */
			{
				TTY::TeletypeDriver *tty = (TTY::TeletypeDriver *)thisProcess->tty;
				if (tty == nullptr)
					return -ENOTTY;
				return tty->Close();
			}
			case 8: /* /dev/ptmx */
				return ptmx->Close();
			default:
				return -ENOENT;
			};
		}
		case 1:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/input/keyboard */
			case 1: /* /dev/input/mouse */
				return -ENOTSUP;
			default:
				return -ENOENT;
			};
		}
		default:
		{
			std::unordered_map<dev_t, Driver::DriverObject> &drivers =
				DriverManager->GetDrivers();
			const auto it = drivers.find(Node->GetMajor());
			if (it == drivers.end())
				ReturnLogError(-EINVAL, "Driver %d not found", Node->GetMajor());
			const Driver::DriverObject *drv = &it->second;

			auto dop = drv->DeviceOperations;
			auto dOps = dop->find(Node->GetMinor());
			if (dOps == dop->end())
				ReturnLogError(-EINVAL, "Device %d not found", Node->GetMinor());
			AssertReturnError(dOps->second.Ops, -ENOTSUP);
			AssertReturnError(dOps->second.Ops->Close, -ENOTSUP);
			return dOps->second.Ops->Close(Node);
		}
		}
	}

	int __fs_Ioctl(struct Inode *Node, unsigned long Request, void *Argp)
	{
		func("%#lx %lu %#lx", Node, Request, Argp);

		switch (Node->GetMajor())
		{
		case 0:
		{
			switch (Node->GetMinor())
			{
			case 2: /* /dev/null */
			case 3: /* /dev/zero */
			case 4: /* /dev/random */
			case 5: /* /dev/mem */
				return -ENOSYS;
			case 6: /* /dev/kcon */
				return KernelConsole::CurrentTerminal.load()->Ioctl(Request, Argp);
			case 7: /* /dev/tty */
			{
				TTY::TeletypeDriver *tty = (TTY::TeletypeDriver *)thisProcess->tty;
				if (tty == nullptr)
					return -ENOTTY;
				return tty->Ioctl(Request, Argp);
			}
			case 8: /* /dev/ptmx */
				return -ENOSYS;
			default:
				return -ENOENT;
			};
			break;
		}
		case 1:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/input/keyboard */
			case 1: /* /dev/input/mouse */
				return -ENOSYS;
			default:
				return -ENOENT;
			};
		}
		case 2:
		{
			switch (Node->GetMinor())
			{
			case 0: /* /dev/fb0 */
			{
				switch (Request)
				{
				case FBIOGET_SCREEN_INFO:
				{
					FramebufferScreenInfo *info = (FramebufferScreenInfo *)Argp;
					info->Width = Display->GetWidth;
					info->Height = Display->GetHeight;
					info->Pitch = Display->GetPitch();
					info->Bpp = Display->GetBitsPerPixel();
					info->Size = Display->GetSize;
					return 0;
				}
				default:
					return -ENOSYS;
				}
			}
			default:
				return -ENOENT;
			}
		}
		default:
		{
			std::unordered_map<dev_t, Driver::DriverObject> &drivers =
				DriverManager->GetDrivers();
			const auto it = drivers.find(Node->GetMajor());
			if (it == drivers.end())
				ReturnLogError(-EINVAL, "Driver %d not found", Node->GetMajor());
			const Driver::DriverObject *drv = &it->second;

			auto dop = drv->DeviceOperations;
			auto dOps = dop->find(Node->GetMinor());
			if (dOps == dop->end())
				ReturnLogError(-EINVAL, "Device %d not found", Node->GetMinor());
			AssertReturnError(dOps->second.Ops, -ENOTSUP);
			AssertReturnError(dOps->second.Ops->Ioctl, -ENOTSUP);
			return dOps->second.Ops->Ioctl(Node, Request, Argp);
		}
		}
	}

	__no_sanitize("alignment")
		ssize_t __fs_Readdir(struct Inode *_Node, struct kdirent *Buffer, size_t Size, off_t Offset, off_t Entries)
	{
		func("%#lx %#lx %d %d %d", _Node, Buffer, Size, Offset, Entries);

		auto Node = (Manager::DeviceInode *)_Node;

		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;
		}

		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 if (Node->ParentInode)
				ent->d_ino = Node->ParentInode->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;
		}

		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)
		{
			debug("iterating \"%s\" inside \"%s\"", var->Name.c_str(), Node->Name.c_str());
			if (var->Node.Offset < realOffset)
			{
				debug("skipping \"%s\" (%d < %d)", var->Name.c_str(), var->Node.Offset, Offset);
				continue;
			}

			if (entries >= Entries)
				break;

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

			if (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;
			ent->d_type = IFTODT(var->Node.Mode);
			strncpy(ent->d_name, var->Name.c_str(), strlen(var->Name.c_str()));

			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;
	}

	void ManagerDaemonWrapper() { DriverManager->Daemon(); }

	void Manager::Daemon()
	{
		while (true)
		{
			TaskManager->Sleep(1000);
		}
	}

	dev_t Manager::RegisterInputDevice(std::unordered_map<dev_t, DriverHandlers> *dop,
									   dev_t DriverID, size_t i, const InodeOperations *Operations)
	{
		std::string prefix = "event";
		for (size_t j = 0; j < 128; j++)
		{
			std::string deviceName = prefix + std::to_string(j);
			FileNode *node = fs->GetByPath(deviceName.c_str(), devInputNode);
			if (node)
				continue;

			/* c rwx r-- r-- */
			mode_t mode = S_IRWXU |
						  S_IRGRP |
						  S_IROTH |
						  S_IFCHR;

			node = fs->ForceCreate(devInputNode, deviceName.c_str(), mode);
			node->Node->SetDevice(DriverID, i);

			DriverHandlers dh{};
			dh.Ops = Operations;
			dh.Node = node->Node;
			dh.InputReports = new RingBuffer<InputReport>(16);
			dop->insert({i, std::move(dh)});
			return i;
		}

		ReturnLogError(-1, "No available slots for device %d", DriverID);
		return -1; /* -Werror=return-type */
	}

	dev_t Manager::RegisterBlockDevice(std::unordered_map<dev_t, DriverHandlers> *dop,
									   dev_t DriverID, size_t i, const InodeOperations *Operations)
	{
		std::string prefix = "event";
		for (size_t j = 0; j < 128; j++)
		{
			std::string deviceName = prefix + std::to_string(j);
			FileNode *node = fs->GetByPath(deviceName.c_str(), devInputNode);
			if (node)
				continue;

			/* c rwx r-- r-- */
			mode_t mode = S_IRWXU |
						  S_IRGRP |
						  S_IROTH |
						  S_IFBLK;

			node = fs->ForceCreate(devInputNode, deviceName.c_str(), mode);
			node->Node->SetDevice(DriverID, i);

			DriverHandlers dh{};
			dh.Ops = Operations;
			dh.Node = node->Node;
			dh.InputReports = new RingBuffer<InputReport>(16);
			dop->insert({i, std::move(dh)});
			return i;
		}

		ReturnLogError(-1, "No available slots for device %d", DriverID);
		return -1; /* -Werror=return-type */
	}

	dev_t Manager::RegisterDevice(dev_t DriverID, DeviceType Type, const InodeOperations *Operations)
	{
		std::unordered_map<dev_t, Driver::DriverObject> &drivers =
			DriverManager->GetDrivers();
		const auto it = drivers.find(DriverID);
		if (it == drivers.end())
			ReturnLogError(-EINVAL, "Driver %d not found", DriverID);
		const Driver::DriverObject *drv = &it->second;

		auto dop = drv->DeviceOperations;
		for (size_t i = 0; i < 128; i++)
		{
			const auto dOps = dop->find(i);
			const auto dOpsEnd = dop->end();
			if (dOps != dOpsEnd)
				continue;

			DeviceType devType = (DeviceType)(Type & DEVICE_TYPE_MASK);
			switch (devType)
			{
			case DEVICE_TYPE_INPUT:
				return RegisterInputDevice(dop, DriverID, i, Operations);
			case DEVICE_TYPE_BLOCK:
				return RegisterBlockDevice(dop, DriverID, i, Operations);
			default:
				ReturnLogError(-1, "Invalid device type %d", Type);
			}
		}

		ReturnLogError(-1, "No available slots for device %d", DriverID);
	}

	int Manager::UnregisterDevice(dev_t DriverID, dev_t Device)
	{
		std::unordered_map<dev_t, Driver::DriverObject> &drivers =
			DriverManager->GetDrivers();
		const auto it = drivers.find(DriverID);
		if (it == drivers.end())
			ReturnLogError(-EINVAL, "Driver %d not found", DriverID);
		const Driver::DriverObject *drv = &it->second;

		auto dop = drv->DeviceOperations;
		const auto dOps = dop->find(Device);
		if (dOps == dop->end())
			ReturnLogError(-EINVAL, "Device %d not found", Device);
		dop->erase(dOps);
		fixme("remove eventX from /dev/input");
		fixme("delete InputReports");
		return 0;
	}

	int Manager::ReportInputEvent(dev_t DriverID, InputReport *Report)
	{
		std::unordered_map<dev_t, Driver::DriverObject> &drivers =
			DriverManager->GetDrivers();
		const auto it = drivers.find(DriverID);
		if (it == drivers.end())
			ReturnLogError(-EINVAL, "Driver %d not found", DriverID);
		const Driver::DriverObject *drv = &it->second;

		auto dop = drv->DeviceOperations;
		auto dOps = dop->find(Report->Device);
		if (dOps == dop->end())
			ReturnLogError(-EINVAL, "Device %d not found", Report->Device);

		dOps->second.InputReports->Write(Report, 1);

		switch (Report->Type)
		{
		case INPUT_TYPE_KEYBOARD:
		{
			KeyboardReport *kReport = &Report->Keyboard;
			GlobalKeyboardInputReports.Write(kReport, 1);
			break;
		}
		case INPUT_TYPE_MOUSE:
		{
			MouseReport *mReport = &Report->Mouse;
			GlobalMouseInputReports.Write(mReport, 1);
			break;
		}
		default:
			assert(!"Invalid input type");
		}
		return 0;
	}

	void Manager::InitializeDaemonFS()
	{
		ptmx = new TTY::PTMXDevice;

		dev_t MinorID = 0;
		DeviceInode *_dev = new DeviceInode;
		_dev->Name = "dev";

		/* d rwx r-- r-- */
		mode_t mode = S_IRWXU |
					  S_IRGRP |
					  S_IROTH |
					  S_IFDIR;
		Inode *dev = (Inode *)_dev;
		dev->Mode = mode;
		dev->Flags = I_FLAG_MOUNTPOINT | I_FLAG_CACHE_KEEP;

		FileSystemInfo *fsi = new FileSystemInfo;
		fsi->Name = "Device Virtual File System";
		fsi->RootName = "dev";
		fsi->Flags = I_FLAG_ROOT | I_FLAG_MOUNTPOINT | I_FLAG_CACHE_KEEP;
		fsi->SuperOps = {};
		fsi->Ops.Lookup = __fs_Lookup;
		fsi->Ops.Create = __fs_Create;
		fsi->Ops.Read = __fs_Read;
		fsi->Ops.Write = __fs_Write;
		fsi->Ops.Open = __fs_Open;
		fsi->Ops.Close = __fs_Close;
		fsi->Ops.Ioctl = __fs_Ioctl;
		fsi->Ops.ReadDir = __fs_Readdir;

		dev->Device = fs->RegisterFileSystem(fsi, dev);
		dev->SetDevice(0, MinorID++);
		MinorID++; /* 1 = /proc/self */

		devNode = fs->Mount(fs->GetRoot(0), dev, "/dev");
		_dev->Parent = devNode->Parent;
		_dev->ParentInode = devNode->Parent->Node;

		/* d rwx r-- r-- */
		mode = S_IRWXU |
			   S_IRGRP |
			   S_IROTH |
			   S_IFDIR;
		DeviceInode *input = new DeviceInode;
		input->Parent = devNode;
		input->ParentInode = devNode->Node;
		input->Name = "input";
		input->Node.Device = dev->Device;
		input->Node.Mode = mode;
		input->Node.Flags = I_FLAG_CACHE_KEEP;
		input->Node.Offset = _dev->Children.size();
		_dev->Children.push_back(input);
		devInputNode = fs->GetByPath("input", devNode);

		auto createDevice = [](DeviceInode *p1, FileNode *p2, const std::string &name, dev_t maj, dev_t min, mode_t mode)
		{
			DeviceInode *device = new DeviceInode;
			device->Parent = p2;
			device->ParentInode = p2->Node;
			device->Name = name;
			device->Node.Device = p2->Node->Device;
			device->Node.Mode = mode;
			device->Node.SetDevice(maj, min);
			device->Node.Flags = I_FLAG_CACHE_KEEP;
			device->Node.Offset = p1->Children.size();
			p1->Children.push_back(device);
		};

		/* c rw- rw- rw- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP | S_IWGRP |
			   S_IROTH | S_IWOTH |
			   S_IFCHR;
		createDevice(_dev, devNode, "null", 0, MinorID++, mode);

		/* c rw- rw- rw- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP | S_IWGRP |
			   S_IROTH | S_IWOTH |
			   S_IFCHR;
		createDevice(_dev, devNode, "zero", 0, MinorID++, mode);

		/* c rw- rw- rw- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP | S_IWGRP |
			   S_IROTH | S_IWOTH |
			   S_IFCHR;
		createDevice(_dev, devNode, "random", 0, MinorID++, mode);

		/* c rw- r-- --- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP |

			   S_IFCHR;
		createDevice(_dev, devNode, "mem", 0, MinorID++, mode);

		/* c rw- r-- --- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP |

			   S_IFCHR;
		createDevice(_dev, devNode, "kcon", 0, MinorID++, mode);

		/* c rw- rw- rw- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP | S_IWGRP |
			   S_IRUSR | S_IWUSR |
			   S_IFCHR;
		createDevice(_dev, devNode, "tty", 0, MinorID++, mode);

		/* c rw- rw- rw- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP | S_IWGRP |
			   S_IRUSR | S_IWUSR |
			   S_IFCHR;
		createDevice(_dev, devNode, "ptmx", 0, MinorID++, mode);

		/* ------------------------------------------------------ */
		MinorID = 0;

		/* c rw- r-- --- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP |

			   S_IFCHR;
		createDevice(input, devInputNode, "keyboard", 1, MinorID++, mode);

		/* c rw- r-- --- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP |

			   S_IFCHR;
		createDevice(input, devInputNode, "mouse", 1, MinorID++, mode);

		/* ------------------------------------------------------ */
		MinorID = 0;

		/* c rw- r-- --- */
		mode = S_IRUSR | S_IWUSR |
			   S_IRGRP |
			   S_IFCHR;
		createDevice(_dev, devNode, "fb0", 2, MinorID++, mode);
	}
}