/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/buffer.hpp>
#include <ox/std/span.hpp>

#include <ox/fs/filestore/filestoretemplate.hpp>
#include <ox/fs/filesystem/filelocation.hpp>
#include <ox/fs/filesystem/types.hpp>

#include "directory.hpp"

namespace ox {

namespace detail {
inline void fsBuffFree(char *buff) noexcept {
	safeDelete(buff);
}
}

class FileSystem {

	public:
		virtual ~FileSystem() noexcept = default;

		virtual Error mkdir(StringViewCR path, bool recursive) noexcept = 0;

		/**
		 * Moves an entry from one directory to another.
		 * @param src the path to the file
		 * @param dest the path of the destination directory
		 */
		virtual Error move(StringViewCR src, StringViewCR dest) noexcept = 0;

		Error read(const FileAddress &addr, void *buffer, std::size_t size) noexcept;

		Result<Buffer> read(FileAddress const &addr, size_t size) noexcept;

		Result<Buffer> read(StringViewCR path, size_t size) noexcept;

		Result<Buffer> read(const FileAddress &addr) noexcept;

		Result<Buffer> read(StringViewCR path) noexcept;

		Error read(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
			return readFilePath(path, buffer, buffSize);
		}

		Error read(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
			return readFileInode(inode, buffer, buffSize);
		}

		Error read(
			FileAddress const &addr,
			size_t readStart,
			size_t readSize,
			void *buffer,
			size_t *size) noexcept;

		/**
		 *
		 * @param path
		 * @param readStart
		 * @param readSize
		 * @param buff
		 * @return error or number of bytes read
		 */
		Result<size_t> read(
			StringViewCR path, size_t readStart, size_t readSize, ox::Span<char> buff) noexcept;

		virtual Result<Vector<String>> ls(StringViewCR dir) const noexcept = 0;

		Error remove(StringViewCR path, bool recursive = false) noexcept {
			return removePath(path, recursive);
		}

		virtual Error resize(uint64_t size, void *buffer) noexcept = 0;

		Error write(StringViewCR path, const void *buffer, uint64_t size) noexcept {
			return writeFilePath(path, buffer, size, FileType::NormalFile);
		}

		Error write(StringViewCR path, ox::SpanView<char> const&buff) noexcept {
			return write(path, buff.data(), buff.size(), FileType::NormalFile);
		}

		Error write(uint64_t inode, const void *buffer, uint64_t size) noexcept {
			return write(inode, buffer, size, FileType::NormalFile);
		}

		Error write(uint64_t inode, ox::SpanView<char> const&buff) noexcept {
			return write(inode, buff.data(), buff.size(), FileType::NormalFile);
		}

		Error write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType = FileType::NormalFile) noexcept;

		Error write(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept {
			return writeFilePath(path, buffer, size, fileType);
		}

		Error write(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
			return writeFileInode(inode, buffer, size, fileType);
		}

		Result<FileStat> stat(uint64_t inode) const noexcept {
			return statInode(inode);
		}

		Result<FileStat> stat(StringViewCR path) const noexcept {
			return statPath(path);
		}

		Result<FileStat> stat(const FileAddress &addr) const noexcept;

		[[nodiscard]]
		bool exists(uint64_t inode) const noexcept {
			return statInode(inode).ok();
		}

		[[nodiscard]]
		bool exists(ox::StringView path) const noexcept {
			return statPath(path).ok();
		}

		[[nodiscard]]
		bool exists(FileAddress const&addr) const noexcept {
			return stat(addr).ok();
		}

		[[nodiscard]]
		virtual uint64_t spaceNeeded(uint64_t size) const noexcept = 0;

		[[nodiscard]]
		virtual Result<uint64_t> available() const noexcept = 0;

		[[nodiscard]]
		virtual Result<uint64_t> size() const noexcept = 0;

		[[nodiscard]]
		virtual char *buff() noexcept = 0;

		virtual Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept = 0;

		[[nodiscard]]
		virtual bool valid() const noexcept = 0;

	protected:
		virtual Result<FileStat> statInode(uint64_t inode) const noexcept = 0;

		virtual Result<FileStat> statPath(StringViewCR path) const noexcept = 0;

		virtual Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept = 0;

		virtual Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept = 0;

		virtual Error readFilePathRange(
			StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept = 0;

		virtual Error readFileInodeRange(uint64_t inode, size_t readStart, size_t readSize, void *buffer, size_t *size) noexcept = 0;

		virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0;

		virtual Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;

		virtual Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept = 0;

};

class MemFS: public FileSystem {
	public:
		Result<const char*> directAccess(const FileAddress &addr) const noexcept;

		Result<const char*> directAccess(StringViewCR path) const noexcept {
			return directAccessPath(path);
		}

		Result<const char*> directAccess(uint64_t inode) const noexcept {
			return directAccessInode(inode);
		}

	protected:
		virtual Result<const char*> directAccessPath(StringViewCR path) const noexcept = 0;

		virtual Result<const char*> directAccessInode(uint64_t inode) const noexcept = 0;
};

/**
 * FileSystemTemplate used to create file system that wraps around a FileStore,
 * taking an inode size and a directory type as parameters.
 *
 * Note: Directory parameter must have a default constructor.
 */
template<typename FileStore, typename Directory>
class FileSystemTemplate: public MemFS {
	private:
		static constexpr auto InodeFsData = 2;

		struct OX_PACKED FileSystemData {
			LittleEndian<typename FileStore::InodeId_t> rootDirInode;
		};

		FileStore m_fs;
		void(*m_freeBuffer)(char*) = nullptr;

	public:
		FileSystemTemplate() noexcept = default;

		FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*) = detail::fsBuffFree) noexcept;

		explicit FileSystemTemplate(ox::Buffer &buffer) noexcept;

		explicit FileSystemTemplate(FileStore fs) noexcept;

		~FileSystemTemplate() noexcept override;

		static Error format(void *buff, uint64_t buffSize) noexcept;

		Error mkdir(StringViewCR path, bool recursive) noexcept override;

		Error move(StringViewCR src, StringViewCR dest) noexcept override;

		Error readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept override;

		Result<const char*> directAccessPath(StringViewCR) const noexcept override;

		Error readFileInode(uint64_t inode, void *buffer, std::size_t size) noexcept override;

		Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override;

		Error readFilePathRange(
			StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept override;

		Error removePath(StringViewCR path, bool recursive) noexcept override;

		Result<const char*> directAccessInode(uint64_t) const noexcept override;

		Result<Vector<String>> ls(StringViewCR dir) const noexcept override;

		template<typename F>
		Error ls(StringViewCR path, F cb) const;

		/**
		 * Resizes FileSystem to minimum possible size.
		 */
		Error resize() noexcept;

		Error resize(uint64_t size, void *buffer) noexcept override;

		Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override;

		Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override;

		Result<FileStat> statInode(uint64_t inode) const noexcept override;

		Result<FileStat> statPath(StringViewCR path) const noexcept override;

		[[nodiscard]]
		uint64_t spaceNeeded(uint64_t size) const noexcept override;

		Result<uint64_t> available() const noexcept override;

		Result<uint64_t> size() const noexcept override;

		char *buff() noexcept override;

		Error walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept override;

		[[nodiscard]]
		bool valid() const noexcept override;

	private:
		Result<FileSystemData> fileSystemData() const noexcept;

		/**
		 * Finds the inode ID at the given path.
		 */
		Result<uint64_t> find(StringViewCR path) const noexcept;

		Result<Directory> rootDir() const noexcept;

};

template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(FileStore fs) noexcept {
	m_fs = fs;
}

template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(ox::Buffer &buffer) noexcept:
		m_fs(buffer.data(), static_cast<std::size_t>(buffer.size())) {
}

template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::FileSystemTemplate(void *buffer, uint64_t bufferSize, void(*freeBuffer)(char*)) noexcept:
	m_fs(buffer, static_cast<std::size_t>(bufferSize)),
	m_freeBuffer(freeBuffer) {
}

template<typename FileStore, typename Directory>
FileSystemTemplate<FileStore, Directory>::~FileSystemTemplate() noexcept {
	if (m_freeBuffer && m_fs.buff()) {
		m_freeBuffer(m_fs.buff());
	}
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::format(void *buff, uint64_t buffSize) noexcept {
	OX_RETURN_ERROR(FileStore::format(buff, static_cast<size_t>(buffSize)));
	FileStore fs(buff, static_cast<size_t>(buffSize));

	constexpr auto rootDirInode = MaxValue<typename FileStore::InodeId_t> / 2;
	Directory rootDir(fs, rootDirInode);
	OX_RETURN_ERROR(rootDir.init());

	FileSystemData fd;
	fd.rootDirInode = rootDirInode;
	oxTracef("ox.fs.FileSystemTemplate.format", "rootDirInode: {}", fd.rootDirInode.get());
	OX_RETURN_ERROR(fs.write(InodeFsData, &fd, sizeof(fd)));

	if (!fs.read(fd.rootDirInode).valid()) {
		oxTrace("ox.fs.FileSystemTemplate.format.error", "FileSystemTemplate::format did not correctly create root directory");
		return ox::Error(1);
	}

	return {};
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::mkdir(StringViewCR path, bool recursive) noexcept {
	oxTracef("ox.fs.FileSystemTemplate.mkdir", "path: {}, recursive: {}", path, recursive);
	OX_REQUIRE_M(rootDir, this->rootDir());
	return rootDir.mkdir(path, recursive);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::move(StringViewCR src, StringViewCR dest) noexcept {
	OX_REQUIRE(fd, fileSystemData());
	Directory rootDir(m_fs, fd.rootDirInode);
	OX_REQUIRE_M(inode, rootDir.find(src));
	OX_RETURN_ERROR(rootDir.write(dest, inode));
	OX_RETURN_ERROR(rootDir.remove(src));
	return {};
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
	oxTrace("ox.fs.FileSystemTemplate.readFilePath", path);
	OX_REQUIRE(fd, fileSystemData());
	Directory rootDir(m_fs, fd.rootDirInode);
	OX_REQUIRE(s, stat(path));
	if (s.size > buffSize) {
		return ox::Error(1, "Buffer to small to load file");
	}
	return readFileInodeRange(s.inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
}

template<typename FileStore, typename Directory>
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessPath(StringViewCR path) const noexcept {
	OX_REQUIRE(fd, fileSystemData());
	Directory rootDir(m_fs, fd.rootDirInode);
	OX_REQUIRE(inode, rootDir.find(path));
	return directAccessInode(inode);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFileInode(uint64_t inode, void *buffer, std::size_t buffSize) noexcept {
	oxTracef("ox.fs.FileSystemTemplate.readFileInode", "{}", inode);
	OX_REQUIRE(s, stat(inode));
	if (s.size > buffSize) {
		return ox::Error(1, "Buffer to small to load file");
	}
	return readFileInodeRange(inode, 0, static_cast<size_t>(s.size), buffer, &buffSize);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept {
	return m_fs.read(inode, readStart, readSize, reinterpret_cast<uint8_t*>(buffer), size);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::readFilePathRange(
	StringViewCR path, size_t readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
	OX_REQUIRE(s, stat(path));
	return readFileInodeRange(s.inode, readStart, readSize, buffer, buffSize);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::removePath(StringViewCR path, bool recursive) noexcept {
	OX_REQUIRE(fd, fileSystemData());
	Directory rootDir(m_fs, fd.rootDirInode);
	OX_REQUIRE(inode, rootDir.find(path));
	OX_REQUIRE(st, statInode(inode));
	if (st.fileType == FileType::NormalFile || recursive) {
		if (auto err = rootDir.remove(path)) {
			// removal failed, try putting the index back
			oxLogError(rootDir.write(path, inode));
			return err;
		}
	} else {
		oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting.");
		return ox::Error(1);
	}
	return {};
}

template<typename FileStore, typename Directory>
Result<const char*> FileSystemTemplate<FileStore, Directory>::directAccessInode(uint64_t inode) const noexcept {
	auto data = m_fs.read(inode);
	if (!data.valid()) {
		return ox::Error(1, "Data not valid");
	}
	return reinterpret_cast<char*>(data.get());
}

template<typename FileStore, typename Directory>
Result<Vector<String>> FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path) const noexcept {
	Vector<String> out;
	OX_RETURN_ERROR(ls(path, [&out](StringViewCR name, typename FileStore::InodeId_t) {
		out.emplace_back(name);
		return ox::Error{};
	}));
	return out;
}

template<typename FileStore, typename Directory>
template<typename F>
Error FileSystemTemplate<FileStore, Directory>::ls(StringViewCR path, F cb) const {
	oxTracef("ox.fs.FileSystemTemplate.ls", "path: {}", path);
	OX_REQUIRE(s, stat(path));
	Directory dir(m_fs, s.inode);
	return dir.ls(cb);
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::resize() noexcept {
	return m_fs.resize();
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::resize(uint64_t size, void *buffer) noexcept {
	OX_RETURN_ERROR(m_fs.resize(static_cast<size_t>(size), buffer));
	return {};
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::writeFilePath(
		StringViewCR path,
		const void *buffer,
		uint64_t size,
		FileType fileType) noexcept {
	oxTrace("ox.fs.FileSystemTemplate.writeFilePath", path);
	auto [inode, err] = find(path);
	if (err) {
		OX_RETURN_ERROR(m_fs.generateInodeId().moveTo(inode));
		OX_REQUIRE_M(rootDir, this->rootDir());
		OX_RETURN_ERROR(rootDir.write(path, inode));
	}
	OX_RETURN_ERROR(writeFileInode(inode, buffer, size, fileType));
	return {};
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept {
	oxTrace("ox.fs.FileSystemTemplate.writeFileInode", ox::intToStr(inode));
	return m_fs.write(inode, buffer, static_cast<size_t>(size), static_cast<uint8_t>(fileType));
}

template<typename FileStore, typename Directory>
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statInode(uint64_t inode) const noexcept {
	OX_REQUIRE(s, m_fs.stat(inode));
	FileStat out;
	out.inode = s.inode;
	out.links = s.links;
	out.size = s.size;
	out.fileType = static_cast<FileType>(s.fileType);
	return out;
}

template<typename FileStore, typename Directory>
Result<FileStat> FileSystemTemplate<FileStore, Directory>::statPath(StringViewCR path) const noexcept {
	OX_REQUIRE(inode, find(path));
	return stat(inode);
}

template<typename FileStore, typename Directory>
uint64_t FileSystemTemplate<FileStore, Directory>::spaceNeeded(uint64_t size) const noexcept {
	return m_fs.spaceNeeded(static_cast<size_t>(size));
}

template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::available() const noexcept {
	return m_fs.available();
}

template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::size() const noexcept {
	return m_fs.size();
}

template<typename FileStore, typename Directory>
char *FileSystemTemplate<FileStore, Directory>::buff() noexcept {
	return m_fs.buff();
}

template<typename FileStore, typename Directory>
Error FileSystemTemplate<FileStore, Directory>::walk(Error(*cb)(uint8_t, uint64_t, uint64_t)) noexcept {
	return m_fs.walk(cb);
}

template<typename FileStore, typename Directory>
bool FileSystemTemplate<FileStore, Directory>::valid() const noexcept {
	return m_fs.valid();
}

template<typename FileStore, typename Directory>
Result<typename FileSystemTemplate<FileStore, Directory>::FileSystemData> FileSystemTemplate<FileStore, Directory>::fileSystemData() const noexcept {
	FileSystemData fd;
	OX_RETURN_ERROR(m_fs.read(InodeFsData, &fd, sizeof(fd)));
	return fd;
}

template<typename FileStore, typename Directory>
Result<uint64_t> FileSystemTemplate<FileStore, Directory>::find(StringViewCR path) const noexcept {
	OX_REQUIRE(fd, fileSystemData());
	// return root as a special case
	if (path == "/") {
		return static_cast<uint64_t>(fd.rootDirInode);
	}
	Directory rootDir(m_fs, fd.rootDirInode);
	OX_REQUIRE(out, rootDir.find(path));
	return static_cast<uint64_t>(out);
}

template<typename FileStore, typename Directory>
Result<Directory> FileSystemTemplate<FileStore, Directory>::rootDir() const noexcept {
	OX_REQUIRE(fd, fileSystemData());
	return Directory(m_fs, fd.rootDirInode);
}

extern template class FileSystemTemplate<FileStore16, Directory16>;
extern template class FileSystemTemplate<FileStore32, Directory32>;

using FileSystem16 = FileSystemTemplate<FileStore16, Directory16>;
using FileSystem32 = FileSystemTemplate<FileStore32, Directory32>;

}
