/*
 * 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/.
 */

#include <ox/std/error.hpp>

#include <ox/fs/filesystem/passthroughfs.hpp>

#if defined(OX_HAS_PASSTHROUGHFS)

#include <fstream>
#include <string_view>

namespace ox {

PassThroughFS::PassThroughFS(StringViewCR dirPath) {
	m_path = std::string_view(dirPath.data(), dirPath.bytes());
}

PassThroughFS::~PassThroughFS() noexcept = default;

String PassThroughFS::basePath() const noexcept {
	return ox::String(m_path.string().c_str());
}

Error PassThroughFS::mkdir(StringViewCR path, bool recursive) noexcept {
	auto const cleanPath =
		endsWith(path, '/') ? substr(path, 0, path.size() - 1) : path;
	bool success = false;
	auto const p = cleanPath.size() ? m_path / stripSlash(cleanPath) : m_path;
	auto const u8p = p.u8string();
	oxTrace("ox.fs.PassThroughFS.mkdir", std::bit_cast<const char*>(u8p.c_str()));
	if (recursive) {
		std::error_code ec;
		const auto isDir = std::filesystem::is_directory(p, ec);
		if (isDir && !ec.value()) {
			success = true;
		} else {
			success = std::filesystem::create_directories(p, ec);
			OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
		}
	} else {
		std::error_code ec;
		const auto isDir = std::filesystem::is_directory(p, ec);
		if (isDir) {
			success = true;
		} else {
			success = std::filesystem::create_directory(p, ec);
			OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed"));
		}
	}
	return ox::Error(success ? 0 : 1);
}

Error PassThroughFS::move(StringViewCR src, StringViewCR dest) noexcept {
	std::error_code ec;
	std::filesystem::rename(m_path / stripSlash(src), m_path / stripSlash(dest), ec);
	if (ec.value()) {
		return ox::Error(1);
	}
	return {};
}

Result<Vector<String>> PassThroughFS::ls(StringViewCR dir) const noexcept {
	Vector<String> out;
	std::error_code ec;
	const auto di = std::filesystem::directory_iterator(m_path / stripSlash(dir), ec);
	OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: ls failed"));
	for (const auto &p : di) {
		const auto u8p = p.path().filename().u8string();
		out.emplace_back(reinterpret_cast<const char*>(u8p.c_str()));
	}
	return out;
}

Error PassThroughFS::resize(uint64_t, void*) noexcept {
	// unsupported
	return ox::Error(1, "resize is not supported by PassThroughFS");
}

Result<FileStat> PassThroughFS::statInode(uint64_t) const noexcept {
	// unsupported
	return ox::Error(1, "statInode(uint64_t) is not supported by PassThroughFS");
}

Result<FileStat> PassThroughFS::statPath(StringViewCR path) const noexcept {
	std::error_code ec;
	const auto p = m_path / stripSlash(path);
	const FileType type = std::filesystem::is_directory(p, ec) ?
		FileType::Directory : FileType::NormalFile;
	oxTracef("ox.fs.PassThroughFS.statInode", "{} {}", ec.message(), path);
	const uint64_t size = type == FileType::Directory ? 0 : std::filesystem::file_size(p, ec);
	oxTracef("ox.fs.PassThroughFS.statInode.size", "{} {}", path, size);
	if (auto err = ec.value()) {
		return ox::Error{static_cast<ox::ErrorCode>(err), "PassThroughFS: stat failed"};
	}
	return FileStat{0, 0, size, type};
}

uint64_t PassThroughFS::spaceNeeded(uint64_t size) const noexcept {
	return size;
}

Result<uint64_t> PassThroughFS::available() const noexcept {
	std::error_code ec;
	const auto s = std::filesystem::space(m_path, ec);
	OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
	return s.available;
}

Result<uint64_t> PassThroughFS::size() const noexcept {
	std::error_code ec;
	const auto s = std::filesystem::space(m_path, ec);
	OX_RETURN_ERROR(ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: could not get FS size"));
	return s.capacity;
}

char *PassThroughFS::buff() noexcept {
	return nullptr;
}

Error PassThroughFS::walk(Error(*)(uint8_t, uint64_t, uint64_t)) noexcept {
	return ox::Error(1, "walk(Error(*)(uint8_t, uint64_t, uint64_t)) is not supported by PassThroughFS");
}

bool PassThroughFS::valid() const noexcept {
	std::error_code ec;
	const auto out = std::filesystem::is_directory(m_path, ec);
	if (!ec.value()) {
		return out;
	}
	return false;
}

Error PassThroughFS::readFilePath(StringViewCR path, void *buffer, std::size_t buffSize) noexcept {
	try {
		std::ifstream file((m_path / stripSlash(path)), std::ios::binary | std::ios::ate);
		const std::size_t size = static_cast<std::size_t>(file.tellg());
		file.seekg(0, std::ios::beg);
		if (size > buffSize) {
			oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
			return ox::Error(1);
		}
		file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(buffSize));
	} catch (const std::fstream::failure &f) {
		oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
		return ox::Error(2);
	}
	return {};
}

Error PassThroughFS::readFileInode(uint64_t, void*, std::size_t) noexcept {
	// unsupported
	return ox::Error(1, "readFileInode(uint64_t, void*, std::size_t) is not supported by PassThroughFS");
}

Error PassThroughFS::readFilePathRange(
	StringViewCR path, size_t const readStart, size_t readSize, void *buffer, size_t *buffSize) noexcept {
	try {
		std::ifstream file(m_path / stripSlash(path), std::ios::binary | std::ios::ate);
		auto const size = static_cast<size_t>(file.tellg());
		readSize = ox::min(readSize, size);
		file.seekg(static_cast<off_t>(readStart), std::ios::beg);
		if (readSize > *buffSize) {
			oxTracef("ox.fs.PassThroughFS.read.error", "Read failed: Buffer too small: {}", path);
			return ox::Error{1};
		}
		file.read(static_cast<char*>(buffer), static_cast<std::streamsize>(readSize));
		return {};
	} catch (std::fstream::failure const &f) {
		oxTracef("ox.fs.PassThroughFS.read.error", "Read of {} failed: {}", path, f.what());
		return ox::Error{2};
	}
}

Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void*, std::size_t*) noexcept {
	// unsupported
	return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS");
}

Error PassThroughFS::removePath(StringViewCR path, bool const recursive) noexcept {
	if (recursive) {
		return ox::Error{std::filesystem::remove_all(m_path / stripSlash(path)) == 0};
	} else {
		return ox::Error{!std::filesystem::remove(m_path / stripSlash(path))};
	}
}

Error PassThroughFS::writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType) noexcept {
	const auto p = (m_path / stripSlash(path));
	try {
		std::ofstream f(p, std::ios::binary);
		f.write(static_cast<const char*>(buffer), static_cast<std::streamsize>(size));
	} catch (const std::fstream::failure &f) {
		oxTracef("ox.fs.PassThroughFS.read.error", "Write of {} failed: {}", path, f.what());
		return ox::Error(1);
	}
	return {};
}

Error PassThroughFS::writeFileInode(uint64_t, const void*, uint64_t, FileType) noexcept {
	// unsupported
	return ox::Error(1, "writeFileInode(uint64_t, void*, uint64_t, uint8_t) is not supported by PassThroughFS");
}

std::string_view PassThroughFS::stripSlash(StringView path) noexcept {
	for (auto i = 0u; i < path.size() && path[0] == '/'; i++) {
		path = substr(path, 1);
	}
	return {path.data(), path.bytes()};
}

}

#endif
