#include <AtomicDataStore/AtomicDataStore.hpp>
#include <DevCommand/WUnpackCommand/WUnpackCommand.hpp>
#include <Log/Log.hpp>
#include <filesystem>
#include <fstream>

void WUnpackCommand::unpackHashTable(FileIO &file, uint64_t pos, uint64_t size, const std::string &targetDir)
{
    std::vector<uint8_t> compressedData(size);
    if (!file.read(pos, compressedData.data(), size))
    {
        std::cout << "Failed to read the hash table." << std::endl;
        return;
    }

    std::vector<uint8_t> uncompressedData;
    if (!uncompressBytes(compressedData, uncompressedData))
    {
        std::cout << "Failed to uncompress the hash table." << std::endl;
        return;
    }

    AtomicDataStore::HashTable hashtab;
    if (uncompressedData.size() != sizeof(hashtab))
    {
        std::cout << "The uncompressed hash table has the wrong size." << std::endl;
        return;
    }

    memcpy(&hashtab, uncompressedData.data(), sizeof(hashtab));

    for (const auto &entry : hashtab.entries)
    {
        if (entry.type == AtomicDataStore::HashTableEntryType::Unmapped)
        {
            // NOP
        }
        else if (entry.type == AtomicDataStore::HashTableEntryType::NestedHashTable)
        {
            unpackHashTable(file, entry.pos, entry.dataSize, targetDir);
        }
        else if (entry.type == AtomicDataStore::HashTableEntryType::FileDescriptor)
        {
            std::vector<char> nameBytes;
            nameBytes.resize(entry.fileNameLen);

            if (!file.read(entry.pos, nameBytes.data(), nameBytes.size()))
            {
                Log::error("Failed to read a file name.");
                exit(1);
            }

            std::string name((char*) nameBytes.data(), entry.fileNameLen);
            Log::info(formatString("Unpacking $+...", name));

            std::vector<uint8_t> payload;
            payload.resize(entry.dataSize);
            if (!file.read(entry.pos + entry.fileNameLen, payload.data(), payload.size()))
            {
                Log::error("Failed to read file data.");
                exit(1);
            }

            std::vector<uint8_t> data;
            if (!uncompressBytes(payload, data))
            {
                Log::error(formatString("Failed to uncompress file data at 0x$+$+.", std::hex, entry.pos + entry.fileNameLen));
                exit(1);
            }

            std::filesystem::path fullpath = formatString("$+/$+", targetDir, name);
            std::filesystem::create_directories(fullpath.parent_path());

            std::ofstream ofs(fullpath, std::ios::binary | std::ios::trunc);
            ofs.write((char*) data.data(), data.size());

            if (!ofs.good())
            {
                Log::error("Failed to write the file.");
                exit(1);
            }

            ofs.close();
        }
        else
        {
            Log::warn(formatString("Unrecognised hash table entry type: $+", static_cast<int>(entry.type)));
        }
    }
}

int WUnpackCommand::execute(const std::vector<std::string> &args)
{
    if (args.size() != 4)
    {
        Log::error("SYNTAX: wunpack <world-file> <target-dir>");
        return 1;
    }

    std::string worldFilePath = args[2];
    std::string targetDir = args[3];

    AtomicDataStore store;
    store.open(worldFilePath);
    const std::string &error = store.getError();

    if (!error.empty())
    {
        Log::error(error);
        return 1;
    }

    unpackHashTable(store.file, store.rootHashTablePos, store.rootHashTableSize, targetDir);

    Log::info("Unpacking complete.");
    return 0;
}