/*
 * Copyright 2016 - 2024 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include <ox/fs/fs.hpp>
#include <ox/model/modelvalue.hpp>

#include <keel/media.hpp>

#include <keel/pack.hpp>

namespace keel {

static ox::Error pathToInode(
		keel::Context &ctx, ox::FileSystem &dest, ox::ModelObject &obj) noexcept {
	auto &o = obj;
	auto type = static_cast<ox::FileAddressType>(o["type"].get<int8_t>());
	auto &data = o["data"].get<ox::ModelUnion>();
	ox::String path;
	switch (type) {
		case ox::FileAddressType::Path:
			path = data["path"].get<ox::String>();
			break;
		case ox::FileAddressType::ConstPath:
			path = data["constPath"].get<ox::String>();
			break;
		case ox::FileAddressType::Inode:
		case ox::FileAddressType::None:
			return {};
	}
	if (beginsWith(path, "uuid://")) {
		auto const uuid = substr<ox::StringView>(path, 7);
		oxReturnError(keel::uuidToPath(ctx, uuid).moveTo(path));
	}
	oxRequire(s, dest.stat(path));
	oxReturnError(o["type"].set(static_cast<int8_t>(ox::FileAddressType::Inode)));
	return data.set(2, s.inode);
}

static ox::Error transformFileAddressesObj(
		keel::Context &ctx, ox::FileSystem &dest, ox::ModelObject &obj) noexcept;
static ox::Error transformFileAddressesVec(
		keel::Context &ctx, ox::FileSystem &dest, ox::ModelValueVector &v) noexcept;

static ox::Error transformFileAddresses(
		keel::Context &ctx,
		ox::FileSystem &dest,
		ox::ModelValue &v) noexcept {
	if (v.type() == ox::ModelValue::Type::Object) {
		auto &obj = v.get<ox::ModelObject>();
		return transformFileAddressesObj(ctx, dest, obj);
	} else if (v.type() == ox::ModelValue::Type::Vector) {
		auto &vec = v.get<ox::ModelValueVector>();
		return transformFileAddressesVec(ctx, dest, vec);
	}
	return {};
}

static ox::Error transformFileAddressesVec(
		keel::Context &ctx,
		ox::FileSystem &dest,
		ox::ModelValueVector &v) noexcept {
	for (auto &f : v) {
		oxReturnError(transformFileAddresses(ctx, dest, f));
	}
	return {};
}

/**
 * Convert path references in Claw data to inodes to save space
 * @return error
 */
static ox::Error transformFileAddressesObj(
		keel::Context &ctx,
		ox::FileSystem &dest,
		ox::ModelObject &obj) noexcept {
	if (obj.typeName() == "net.drinkingtea.ox.FileAddress" && obj.typeVersion() == 1) {
		return pathToInode(ctx, dest, obj);
	}
	for (auto &f : obj) {
		auto &v = f->value;
		oxReturnError(transformFileAddresses(ctx, dest, v));
	}
	return {};
}

static ox::Error doTransformations(
		keel::Context &ctx,
		ox::TypeStore &ts,
		ox::FileSystem &dest,
		ox::CRStringView filePath) noexcept {
	// load file
	oxRequire(s, dest.stat(filePath));
	// do transformations
	oxRequireM(buff, dest.read(s.inode));
	oxReturnError(keel::performPackTransforms(ctx, buff));
	// transform FileAddresses
	oxRequireM(obj, keel::readAsset(ts, buff));
	oxReturnError(transformFileAddressesObj(ctx, dest, obj));
	oxReturnError(ox::writeClaw(obj).moveTo(buff));
	// write file to dest
	oxReturnError(dest.write(s.inode, buff));
	return {};
}

// claw file transformations are broken out from copy because path to inode
// transformations need to be done after the copy to the new FS is complete
static ox::Error transformClaw(
		keel::Context &ctx,
		ox::TypeStore &ts,
		ox::FileSystem &dest,
		ox::CRStringView path) noexcept {
	// copy
	oxTracef("pack.transformClaw", "path: {}", path);
	oxRequire(fileList, dest.ls(path));
	for (auto const&name : fileList) {
		auto const filePath = ox::sfmt("{}{}", path, name);
		oxRequire(stat, dest.stat(filePath));
		if (stat.fileType == ox::FileType::Directory) {
			auto const dir = ox::sfmt("{}{}/", path, name);
			oxReturnError(transformClaw(ctx, ts, dest, dir));
		} else {
			auto const err = doTransformations(ctx, ts, dest, filePath);
			if (err) {
				oxErrf("\033[31;1;1mCould not do transformations for {}:\n\t{}\n", filePath, toStr(err));
				return err;
			}
		}
	}
	return {};
}

static ox::Error copy(
		ox::FileSystem &src,
		ox::FileSystem &dest,
		ox::CRStringView path) noexcept {
	oxOutf("copying directory: {}\n", path);
	// copy
	oxRequire(fileList, src.ls(path));
	for (auto const&name : fileList) {
		auto currentFile = ox::sfmt("{}{}", path, name);
		if (beginsWith(name, ".")) {
			continue;
		}
		oxOutf("reading {}\n", currentFile);
		oxRequire(stat, src.stat(currentFile));
		if (stat.fileType == ox::FileType::Directory) {
			oxReturnError(dest.mkdir(currentFile, true));
			oxReturnError(copy(src, dest, currentFile + '/'));
		} else {
			// load file
			oxRequireM(buff, src.read(currentFile));
			// write file to dest
			oxOutf("writing {}\n", currentFile);
			oxReturnError(dest.write(currentFile, buff));
		}
	}
	return {};
}

static ox::Error copyFS(ox::FileSystem &src, ox::FileSystem &dest) noexcept {
	oxReturnError(copy(src, dest, "/"));
	return {};
}

ox::Error pack(keel::Context &ctx, ox::TypeStore &ts, ox::FileSystem &dest) noexcept {
	oxReturnError(copyFS(*ctx.rom, dest));
	oxOut("Doing transforms\n");
	oxReturnError(transformClaw(ctx, ts, dest, "/"));
	return {};
}

}
