#include <DevCommand/WDumpCommand/WDumpCommand.hpp>
#include <FileIO/FileIO.hpp>
#include <Log/Log.hpp>
#include <AtomicDataStore/AtomicDataStore.hpp>
#include <iomanip>

void WDumpCommand::printHashTable(FileIO &file, uint64_t pos, uint64_t size, int depth)
{
    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 (size_t entryIndex = 0; entryIndex < AtomicDataStore::numHashTableEntries; entryIndex++)
    {
        auto &entry = hashtab.entries[entryIndex];
        if (entry.type == AtomicDataStore::HashTableEntryType::Unmapped)
        {
            continue;
        }

        for (int i = 0; i < depth; i++)
        {
            std::cout << "│     ";
        }

        std::cout << "├[" << std::setfill(' ') << std::setw(3) << entryIndex << "]";

        if (entry.type == AtomicDataStore::HashTableEntryType::FileDescriptor)
        {
            std::vector<char> fileNameChars;
            fileNameChars.resize(entry.fileNameLen);

            if (!file.read(entry.pos, fileNameChars.data(), entry.fileNameLen))
            {
                std::cout << " # Failed to read the file name.";
            }

            std::string fileName(fileNameChars.data(), fileNameChars.size());
            std::cout << " -> '" << fileName << "'";
            std::cout << std::endl;
        }
        else if (entry.type == AtomicDataStore::HashTableEntryType::NestedHashTable)
        {
            std::cout << "┐" << std::endl;
            printHashTable(file, entry.pos, entry.dataSize, depth + 1);
        }
    }
}

int WDumpCommand::execute(const std::vector<std::string> &args)
{
    if (args.size() != 3)
    {
        Log::error("SYNTAX: wdump <world-file>");
        return 1;
    }

    FileIO file;
    if (!file.open(args[2]))
    {
        Log::error("Failed to open the world file.");
        return 1;
    }

    if (!file.lock())
    {
        Log::error("This world is open in another program.");
        return 1;
    }

    AtomicDataStore::MainHeader mainHeader;
    if (!file.read(0, &mainHeader, sizeof(mainHeader)))
    {
        Log::error("Failed to read the main header.");
        return 1;
    }

    if (memcmp(mainHeader.magic, "\0VVW", 4) != 0)
    {
        Log::error("This is not a Voxelverse world.");
        return 1;
    }

    AtomicDataStore::IndexBlock indexBlocks[2];
    if (!file.read(sizeof(mainHeader), indexBlocks, sizeof(indexBlocks)))
    {
        Log::error("Failed to read the index blocks.");
        return 1;
    }

    size_t validIndex = 0;
    for (size_t i = 0; i < 2; i++)
    {
        AtomicDataStore::IndexBlock &block = indexBlocks[i];
        std::cout << "Index block " << (i == 0 ? 'A' : 'B') << ":" <<  std::endl;
        std::cout << std::hex << std::setfill('0');
        std::cout << "  Root hash table pos:    " << std::setw(16) << block.rootHashTablePos << std::endl;
        std::cout << "  Root hash table size:   " << formatDataSize(block.rootHashTableSize) << std::endl;
        std::cout << "  Free region table pos:  " << std::setw(16) << block.freeRegionTablePos << std::endl;
        std::cout << std::dec;
        std::cout << "  Number of free regions: " << block.numFreeRegions << std::endl;
        std::cout << "  Generation:             " << block.generation << std::endl;
        std::cout << "  Hash:                   " << block.hash << std::endl;
        std::cout << "  Hash valid?:            " << block.isValid() << std::endl;
        std::cout << std::endl;

        if (indexBlocks[i].isValid())
        {
            validIndex = i;
        }
    }

    if (!indexBlocks[0].isValid() && !indexBlocks[1].isValid())
    {
        std::cerr << "There are no valid index blocks in this file." << std::endl;
        return 1;
    }

    if (indexBlocks[0].isValid() == indexBlocks[1].isValid())
    {
        std::cerr << "Currently we only handle the case where only 1 index block is valid." << std::endl;
        return 1;
    }

    std::cout << "Using index block: " << (validIndex == 0 ? 'A' : 'B') << std::endl << std::endl;

    std::cout << "Tree of hash tables:" << std::endl;
    printHashTable(file, indexBlocks[validIndex].rootHashTablePos, indexBlocks[validIndex].rootHashTableSize, 0);

    std::cout << std::endl << "Free region table (" << indexBlocks[validIndex].numFreeRegions << " entries):" << std::endl;
    std::cout << "┌──────────────────┬──────────────────┐" << std::endl;
    std::cout << "│Base              │Size              │" << std::endl;
    std::cout << "├──────────────────┼──────────────────┤" << std::endl;

    for (size_t regionIndex = 0; regionIndex < indexBlocks[validIndex].numFreeRegions; regionIndex++)
    {
        AtomicDataStore::Region region;
        if (!file.read(indexBlocks[validIndex].freeRegionTablePos + sizeof(region) * regionIndex, &region, sizeof(region)))
        {
            std::cout << "# Failed to read this entry." << std::endl;
        }

        std::cout << "│" << std::hex << std::setfill('0') << std::setw(16) << region.basePos << "  │ "
            << std::setfill(' ') << std::setw(17) << formatDataSize(region.size) << "│" << std::endl;
    }

    std::cout << "└──────────────────┴──────────────────┘" << std::endl;

    return 0;
}