#include "generator.hpp"
#include "../include/hash.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>
#include <sstream>
#include <vector>
#include "xxhash.hpp"

namespace fs = std::filesystem;

static std::string sanitize(const std::string& name) {
    std::string out = name;
    for (char& c : out) if (!isalnum(c)) c = '_';
    return out;
}


static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}


void generate_file_code(const std::string& source, const std::string& destfolder, bool silent) {
    fs::path src(source);
    fs::path dest(destfolder);
    std::string ns = "recreate_" + sanitize(src.stem().string());
    std::string cppname = "_" + src.stem().string() + ".cpp";
    std::string hppname = "_" + src.stem().string() + ".hpp";
    fs::create_directories(dest);
    std::ifstream in(source, std::ios::binary);
    std::ostringstream oss;
    oss << in.rdbuf();
    std::string filedata = oss.str();
    uint64_t hash = fnv1a_64(filedata.data(), filedata.size());
    // Write HPP
    std::ofstream hpp(dest / hppname);
    hpp << "#pragma once\n#include <string>\nnamespace " << ns << " {\nbool recreate_file(std::string destination_folder);\n}\n";
    // Write CPP
    std::ofstream cpp(dest / cppname);
    cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <iomanip>

// Tiny dependency-free FNV-1a 64-bit hash
static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}
)cpp";
    cpp << "#include \"" << hppname << "\"\n";
    cpp << "namespace " << ns << " {\n";
    // Embed file data
    cpp << "static const unsigned char filedata[] = {";
    for (size_t i = 0; i < filedata.size(); ++i) {
        if (i % 16 == 0) cpp << "\n    ";
        cpp << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)(unsigned char)filedata[i];
        if (i + 1 != filedata.size()) cpp << ", ";
    }
    cpp << "\n};\n";
    // Reset to decimal format
    cpp << std::dec;
    cpp << "static const size_t filedata_len = " << filedata.size() << ";\n";
    cpp << "static uint64_t file_hash = " << hash << "U;\n";
    cpp << R"cpp(
bool recreate_file(std::string destination_folder) {
    namespace fs = std::filesystem;
    fs::path outpath = fs::path(destination_folder) / ")cpp" << src.filename().string() << R"cpp(";
    uint64_t existing_hash;
    if (fs::exists(outpath)) {
        std::ifstream in(outpath, std::ios::binary);
        std::ostringstream oss;
        oss << in.rdbuf();
        std::string data = oss.str();
        existing_hash = fnv1a_64(data.data(), data.size());
    }
    bool needs_write = !fs::exists(outpath) || existing_hash != file_hash;
    if (needs_write) {
        std::ofstream out(outpath, std::ios::binary);
        out.write(reinterpret_cast<const char*>(filedata), filedata_len);
    }
    
    if (!fs::exists(outpath)) {
        std::cout << "[dehydrate] " << outpath.filename() << ": created\n";
    } else if (needs_write) {
        std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n";
    }
    return needs_write;
}
)cpp";
    cpp << "}\n";
    if (!silent) {
        std::cout << "[dehydrate] Generated: " << (dest / cppname) << ", " << (dest / hppname) << std::endl;
    }
}

// Helper to recursively collect all files in a directory
template<typename F>
void walk_dir(const fs::path& dir, F&& f) {
    for (auto& p : fs::recursive_directory_iterator(dir)) {
        if (fs::is_regular_file(p)) f(p.path());
    }
}


void generate_folder_code(const std::string& source, const std::string& destfolder, bool silent) {
    fs::path src(source);
    fs::path dest(destfolder);
    std::string ns = "recreate_" + sanitize(src.stem().string());
    std::string cppname = "_" + src.stem().string() + ".cpp";
    std::string hppname = "_" + src.stem().string() + ".hpp";
    fs::create_directories(dest);
    // Collect all files
    std::vector<fs::path> files;
    walk_dir(src, [&](const fs::path& p) { files.push_back(p); });
    // Write HPP
    std::ofstream hpp(dest / hppname);

    // -------------------------------------------------------------------------
    // Generate HPP
    hpp << R"hpp(
#pragma once

/*

   THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
   DO NOT EDIT THIS FILE.

*/


#include <string>
namespace )hpp" << ns << R"hpp( {
    bool recreate_tree(std::string destination_folder);
}
)hpp";


    // -------------------------------------------------------------------------
    // Write CPP
    std::ofstream cpp(dest / cppname);
    cpp << R"cpp(#include <fstream>
#include <filesystem>
#include <string>
#include <iostream>
#include <iomanip>



/*

   THIS FILE IS AUTO-GENERATED BY DEHYDRATE.
   DO NOT EDIT THIS FILE.

*/


)cpp";
    cpp << "#include \"" << hppname << "\"\n";
    cpp << "namespace " << ns << " {\n";

    cpp << R"cpp(

// Tiny dependency-free FNV-1a 64-bit hash
static uint64_t fnv1a_64(const void* data, size_t len) {
    const uint8_t* p = static_cast<const uint8_t*>(data);
    uint64_t h = 0xcbf29ce484222325ULL;
    for (size_t i = 0; i < len; ++i)
        h = (h ^ p[i]) * 0x100000001b3ULL;
    return h;
}

)cpp";

    // Embed all files
    for (const auto& file : files) {
        std::ifstream in(file, std::ios::binary);
        std::ostringstream oss;
        oss << in.rdbuf();
        std::string filedata = oss.str();
        uint64_t hash = fnv1a_64(filedata.data(), filedata.size());
        std::string rel = fs::relative(file, src).string();
        std::string var = sanitize(rel);
        cpp << "static const unsigned char data_" << var << "[] = {";
        for (size_t i = 0; i < filedata.size(); ++i) {
            if (i % 16 == 0) cpp << "\n    ";
            cpp << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)(unsigned char)filedata[i];
            if (i + 1 != filedata.size()) cpp << ", ";
        }
        cpp << "\n};\n";
        // Reset to decimal format
        cpp << std::dec;
        cpp << "static const size_t len_" << var << " = " << filedata.size() << ";\n";
        cpp << "static uint64_t hash_" << var << " = " << hash << "U;\n";
        cpp << "static const char* rel_" << var << " = \"" << rel << "\";\n";
    }
    // Write recreate_tree using heredoc style
    cpp << R"cpp(
bool recreate_tree(std::string destination_folder) {
    namespace fs = std::filesystem;
    bool any_written = false;
)cpp";
    for (const auto& file : files) {
        std::string rel = fs::relative(file, src).string();
        std::string var = sanitize(rel);
        cpp << R"cpp(    {
        fs::path outpath = fs::path(destination_folder) / )cpp" << "rel_" << var << R"cpp(;
        fs::create_directories(outpath.parent_path());
        uint64_t existing_hash;
        if (fs::exists(outpath)) {
            std::ifstream in(outpath, std::ios::binary);
            std::ostringstream oss; oss << in.rdbuf();
            std::string data = oss.str();
            existing_hash = fnv1a_64(data.data(), data.size());
        }
        bool needs_write = !fs::exists(outpath) || existing_hash != )cpp" << "hash_" << var << R"cpp(;
        if (needs_write) {
            std::ofstream out(outpath, std::ios::binary);
            out.write(reinterpret_cast<const char*>()cpp" << "data_" << var << R"cpp(), )cpp" << "len_" << var << R"cpp();
        }

        if (!fs::exists(outpath)) {
            std::cout << "[dehydrate] " << outpath.filename() << ": created\n";
        } else if (needs_write) {
            std::cout << "[dehydrate] " << outpath.filename() << ": updated (hash changed)\n";
        }
        any_written = any_written || needs_write;
    }
)cpp";
    }
    cpp << R"cpp(    return any_written;
}
)cpp";
    cpp << "}\n";
    if (!silent) {
        std::cout << "[dehydrate] Generated: " << (dest / cppname) << ", " << (dest / hppname) << std::endl;
    }
} 