#include "ContentPack.hpp"

#include <iostream>
#include <algorithm>
#include <utility>

#include "../coders/json.hpp"
#include "../files/files.hpp"
#include "../files/engine_paths.hpp"
#include "../data/dynamic.hpp"

namespace fs = std::filesystem;

const std::string ContentPack::PACKAGE_FILENAME = "package.json";
const std::string ContentPack::CONTENT_FILENAME = "content.json";
const fs::path ContentPack::BLOCKS_FOLDER = "blocks";
const fs::path ContentPack::ITEMS_FOLDER = "items";
const std::vector<std::string> ContentPack::RESERVED_NAMES = {
    "res", "abs", "local", "core", "user", "world", "none", "null"
};

contentpack_error::contentpack_error(
    std::string packId, 
    fs::path folder, 
    const std::string& message)
    : std::runtime_error(message), packId(std::move(packId)), folder(std::move(folder)) {
}

std::string contentpack_error::getPackId() const {
    return packId;
}
fs::path contentpack_error::getFolder() const {
    return folder;
}

fs::path ContentPack::getContentFile() const {
    return folder/fs::path(CONTENT_FILENAME);
}

bool ContentPack::is_pack(const fs::path& folder) {
    return fs::is_regular_file(folder/fs::path(PACKAGE_FILENAME));
}

static void checkContentPackId(const std::string& id, const fs::path& folder) {
    if (id.length() < 2 || id.length() > 24)
        throw contentpack_error(id, folder, 
            "content-pack id length is out of range [2, 24]");
    if (isdigit(id[0])) 
        throw contentpack_error(id, folder, 
            "content-pack id must not start with a digit");
    for (char c : id) {
        if (!isalnum(c) && c != '_') {
            throw contentpack_error(id, folder, 
                "illegal character in content-pack id");
        }
    }
    if (std::find(ContentPack::RESERVED_NAMES.begin(), 
                  ContentPack::RESERVED_NAMES.end(), id)
        != ContentPack::RESERVED_NAMES.end()) {
        throw contentpack_error(id, folder, 
                "this content-pack id is reserved");
    }
}

ContentPack ContentPack::read(const fs::path& folder) {
    auto root = files::read_json(folder/fs::path(PACKAGE_FILENAME));
    ContentPack pack;
    root->str("id", pack.id);
    root->str("title", pack.title);
    root->str("version", pack.version);
    root->str("creator", pack.creator);
    root->str("description", pack.description);
    pack.folder = folder;

    auto dependencies = root->list("dependencies");
    if (dependencies) {
        for (size_t i = 0; i < dependencies->size(); i++) {
            pack.dependencies.push_back(
                {DependencyLevel::required, dependencies->str(i)}
            );
        }
    }

    if (pack.id == "none")
        throw contentpack_error(pack.id, folder, 
            "content-pack id is not specified");
    checkContentPackId(pack.id, folder);

    return pack;
}

void ContentPack::scanFolder(
    const fs::path& folder,
    std::vector<ContentPack>& packs
) {
    if (!fs::is_directory(folder)) {
        return;
    }
    for (const auto& entry : fs::directory_iterator(folder)) {
        const fs::path& folder = entry.path();
        if (!fs::is_directory(folder))
            continue;
        if (!is_pack(folder))
            continue;
        try {
            packs.push_back(read(folder));
        } catch (const contentpack_error& err) {
            std::cerr << "package.json error at " << err.getFolder().u8string();
            std::cerr << ": " << err.what() << std::endl;
        } catch (const std::runtime_error& err) {
            std::cerr << err.what() << std::endl;
        }
    }
}

std::vector<std::string> ContentPack::worldPacksList(const fs::path& folder) {
    fs::path listfile = folder / fs::path("packs.list");
    if (!fs::is_regular_file(listfile)) {
        std::cerr << "warning: packs.list not found (will be created)";
        std::cerr << std::endl;
        files::write_string(listfile, "# autogenerated, do not modify\nbase\n");
    }
    return files::read_list(listfile);
}

fs::path ContentPack::findPack(const EnginePaths* paths, const fs::path& worldDir, const std::string& name) {
    fs::path folder = worldDir / fs::path("content") / fs::path(name);
    if (fs::is_directory(folder)) {
        return folder;
    }
    folder = paths->getUserfiles() / fs::path("content") / fs::path(name);
    if (fs::is_directory(folder)) {
        return folder;
    }
    folder = paths->getResources() / fs::path("content") / fs::path(name);
    if (fs::is_directory(folder)) {
        return folder;
    }
    return folder;
}

ContentPackRuntime::ContentPackRuntime(
    ContentPack info,
    scriptenv env
) : info(std::move(info)), env(std::move(env))
{
}

ContentPackRuntime::~ContentPackRuntime() {
}
