/*
 * 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 <cstdio>
#include <fstream>

#include <ox/fs/fs.hpp>

struct Buff {
	char *data = nullptr;
	std::size_t size = 0;
};

static ox::Result<Buff> loadFsBuff(const char *path) noexcept {
	std::ifstream file(path, std::ios::binary | std::ios::ate);
	if (!file.good()) {
		oxErrorf("Could not find OxFS file: {}", path);
		return ox::Error(1, "Could not find OxFS file");
	}
	try {
		const auto size = static_cast<std::size_t>(file.tellg());
		file.seekg(0, std::ios::beg);
		const auto buff = new char[size];
		file.read(buff, static_cast<std::streamsize>(size));
		return Buff{buff, size};
	} catch (const std::ios_base::failure &e) {
		oxErrorf("Could not read OxFS file: {}", e.what());
		return ox::Error(2, "Could not read OxFS file");
	}
}

static ox::Result<ox::UPtr<ox::FileSystem>> loadFs(const char *path) noexcept {
	OX_REQUIRE(buff, loadFsBuff(path));
	return {ox::make_unique<ox::FileSystem32>(buff.data, buff.size)};
}

static ox::Error runLs(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
	if (args.size() < 2) {
		oxErr("Must provide a directory to ls\n");
		return ox::Error(1);
	}
	OX_REQUIRE(files, fs->ls(args[1]));
	for (const auto &file : files) {
		oxOutf("{}\n", file);
	}
	return {};
}

static ox::Error runRead(ox::FileSystem *fs, ox::Span<const char*> args) noexcept {
	if (args.size() < 2) {
		oxErr("Must provide a path to a file to read\n");
		return ox::Error(1);
	}
	OX_REQUIRE(buff, fs->read(ox::StringView(args[1])));
	OX_ALLOW_UNSAFE_BUFFERS_BEGIN
	std::ignore = fwrite(buff.data(), sizeof(decltype(buff)::value_type), buff.size(), stdout);
	OX_ALLOW_UNSAFE_BUFFERS_END
	return {};
}

static ox::Error run(int argc, const char **argv) noexcept {
	if (argc < 3) {
		oxErr("OxFS file and subcommand arguments are required\n");
		return ox::Error(1);
	}
	auto const args = ox::Span{argv, static_cast<size_t>(argc)};
	auto const fsPath = args[1];
	ox::String subCmd(args[2]);
	OX_REQUIRE(fs, loadFs(fsPath));
	if (subCmd == "ls") {
		return runLs(fs.get(), args + 2);
	} else if (subCmd == "read") {
		return runRead(fs.get(), args + 2);
	}
	return ox::Error(1);
}

int main(int argc, const char **argv) {
	auto err = run(argc, argv);
	oxAssert(err, "unhandled error");
	return static_cast<int>(err);
}
