#include <clang-c/CXDiagnostic.h>
#include <clang-c/CXFile.h>
#include <clang-c/CXSourceLocation.h>
#include <clang-c/CXString.h>
#include <clang-c/Index.h>
#include <cstdio>
#include <fstream>
#include <filesystem>
#include "enum/analysis.h"
#include "enum/codegen.h"
#include "object/analysis.h"
#include "object/codegen.h"
#include "data/analysis.h"
#include "data/codegen.h"
#include "util.h"

// namespace data {
//     #include "data/analysis.h"
//     #include "data/codegen.h"
// }

namespace fs = std::filesystem;

// https://clang.llvm.org/docs/LibClang.html
int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
    std::string srcRootStr = string_of(srcRoot);
    std::string srcPathStr = string_of(srcPath);
    std::string outPathStr = string_of(outPath);
    std::string logFileStr = outPathStr + ".log";

    const char* cargs[] = { "-xc++", "-std=c++17", "-I", srcRootStr.c_str(), "-D__AUTOGEN__", 0 };
    // THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
    // https://stackoverflow.com/a/59206378/16255372
    CXIndex index = clang_createIndex(0, 0);
    CXTranslationUnit unit = clang_parseTranslationUnit(
        index,
        srcPathStr.c_str(), cargs, 5,
        nullptr, 0,
        CXTranslationUnit_None);

    if (!unit) {
        fprintf(stderr, "Failed to parse file\n");
        return 1;
    }

    fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it

    // We write to a special log file instead of stdout/stderr to
    // 1. avoid confusion
    // 2. prevent MSBuild from reading the word "error" and detecting there's a problem with the program (there isn't)
    FILE* logout = fopen(logFileStr.c_str(), "w");
    
    // Print errors
    int ndiags = clang_getNumDiagnostics(unit);
    for (int i = 0; i < ndiags; i++) {
        CXDiagnostic diag = clang_getDiagnostic(unit, i);
        CXString str = clang_formatDiagnostic(diag, 0);
        fprintf(logout, "diag: %s\n", clang_getCString(str));

        clang_disposeString(str);
        clang_disposeDiagnostic(diag);
    }

    fclose(logout);

    CXCursor cursor = clang_getTranslationUnitCursor(unit);

    object::AnalysisState objectAnlyState;
    data::AnalysisState dataAnlyState;
    enum_::AnalysisState enumAnlyState;

    fs::path relpath = fs::relative(srcPath, srcRoot);
    std::string relpathStr = string_of(relpath);
    printf("[AUTOGEN] Processing file %s...\n", relpathStr.c_str());
    object::analyzeClasses(cursor, srcRootStr, &objectAnlyState);
    data::analyzeClasses(cursor, srcRootStr, &dataAnlyState);
    enum_::analyzeClasses(cursor, srcRootStr, &enumAnlyState);

    printf("[AUTOGEN] Generating file %s...\n", relpathStr.c_str());
    std::ofstream outStream(outPathStr);

    if (!objectAnlyState.classes.empty() || !dataAnlyState.classes.empty()) {
        outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n";
        outStream << "// This file was automatically generated by autogen, and should not be edited manually //\n";
        outStream << "/////////////////////////////////////////////////////////////////////////////////////////\n\n";
    }

    for (auto& [_, clazz] : objectAnlyState.classes) {
        object::writeCodeForClass(outStream, relpathStr, clazz);
    }

    for (auto& [_, clazz] : dataAnlyState.classes) {
        data::writeCodeForClass(outStream, relpathStr, clazz);
    }

    for (auto& [_, clazz] : enumAnlyState.classes) {
        enum_::writeCodeForClass(outStream, relpathStr, clazz);
    }

    outStream.close();

    return 0;
}

int main(int argc, char** argv) {
    if (argc < 4) {
        fprintf(stderr, "Usage: autogen <src-root> <src-file> <out-dir>\n");
        return 1;
    }

    fs::path srcRoot = argv[1];
    fs::path srcPath = argv[2];
    fs::path outPath = argv[3];

    // fprintf(stderr, "Some error here\n");
    // return 0;
    return processHeader(srcRoot, srcPath, outPath);
}