// cp-16 / tests / test_suite.cpp — exercise the full pipeline.

#include "diag.hpp"
#include "driver.hpp"
#include "llvm_emit.hpp"
#include "source.hpp"

#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>

using namespace ml;

static int g_checks = 0, g_passed = 0;
#define CHECK(c)                                                                \
    do                                                                          \
    {                                                                           \
        ++g_checks;                                                             \
        if (c)                                                                  \
            ++g_passed;                                                         \
        else                                                                    \
            std::cerr << "FAIL " << __FILE__ << ":" << __LINE__ << " " #c "\n"; \
    } while (0)
#define CHECK_EQ(a, b)                                                       \
    do                                                                       \
    {                                                                        \
        ++g_checks;                                                          \
        auto _a = (a);                                                       \
        auto _b = (b);                                                       \
        if (_a == _b)                                                        \
            ++g_passed;                                                      \
        else                                                                 \
            std::cerr << "FAIL " << __FILE__ << ":" << __LINE__              \
                      << "\n  lhs: |" << _a << "|\n  rhs: |" << _b << "|\n"; \
    } while (0)
#define CHECK_CONTAINS(h, n)                                               \
    do                                                                     \
    {                                                                      \
        ++g_checks;                                                        \
        std::string _h = (h), _n = (n);                                    \
        if (_h.find(_n) != std::string::npos)                              \
            ++g_passed;                                                    \
        else                                                               \
            std::cerr << "FAIL " << __FILE__ << ":" << __LINE__            \
                      << "\n  expected substring: |" << _n << "|\n  in:\n" \
                      << _h;                                               \
    } while (0)

static FrontendResult fe(const std::string &code)
{
    return runFrontend(SourceFile("test.ml", code));
}

static void test_frontend_smoke()
{
    auto r = fe("fn main() { print 42; }");
    CHECK(r.ok());
    CHECK_EQ(r.program.funcs.size(), (size_t)1);
    CHECK_EQ(r.program.funcs[0].name, std::string("main"));
}

static void test_frontend_needs_main()
{
    auto r = fe("fn helper() { return 1; }");
    CHECK(!r.ok());
    bool found = false;
    for (auto &d : r.diagnostics)
        if (d.code == "E0411")
            found = true;
    CHECK(found);
}

static void test_frontend_arity()
{
    auto r = fe("fn add(a, b) { return a + b; } fn main() { print add(1); }");
    CHECK(!r.ok());
    bool found = false;
    for (auto &d : r.diagnostics)
        if (d.code == "E0421")
            found = true;
    CHECK(found);
}

static void test_frontend_unknown_var()
{
    auto r = fe("fn main() { print y; }");
    CHECK(!r.ok());
    bool found = false;
    for (auto &d : r.diagnostics)
        if (d.code == "E0300")
            found = true;
    CHECK(found);
}

static void test_ir_shape()
{
    auto r = fe("fn main() { print 1 + 2; }");
    CHECK(r.ok());
    std::string ir = emitLLVMIR(r.program);
    CHECK_CONTAINS(ir, std::string("declare i32 @printf(ptr, ...)"));
    CHECK_CONTAINS(ir, std::string("define i64 @main()"));
    CHECK_CONTAINS(ir, std::string("call i32 (ptr, ...) @printf(ptr @.fmt"));
}

static void test_ir_call()
{
    auto r = fe(R"(
        fn add(a, b) { return a + b; }
        fn main() { print add(2, 3); }
    )");
    CHECK(r.ok());
    std::string ir = emitLLVMIR(r.program);
    CHECK_CONTAINS(ir, std::string("define i64 @add(i64 %arg0, i64 %arg1)"));
    CHECK_CONTAINS(ir, std::string("call i64 @add(i64"));
}

static bool toolchainAvailable()
{
    // Quick sniff: does llc exist & run?
    FILE *p = ::popen(LLC_PATH " --version 2>/dev/null", "r");
    if (!p)
        return false;
    char buf[64];
    size_t n = std::fread(buf, 1, sizeof(buf), p);
    int s = ::pclose(p);
    return n > 0 && WIFEXITED(s) && WEXITSTATUS(s) == 0;
}

static void test_e2e_print_42()
{
    if (!toolchainAvailable())
    {
        std::cerr << "[skip] toolchain unavailable\n";
        return;
    }
    auto r = fe("fn main() { print 42; }");
    CHECK(r.ok());
    BuildOptions opts;
    opts.outputPath = "/tmp/minilangc-test-42";
    auto br = buildExecutable(emitLLVMIR(r.program), opts);
    CHECK(br.ok);
    auto rr = runExecutable(opts.outputPath);
    CHECK(rr.ok);
    CHECK_EQ(rr.stdoutText, std::string("42\n"));
}

static void test_e2e_fib()
{
    if (!toolchainAvailable())
        return;
    auto r = fe(R"(
        fn fib(n) {
            if (n < 2) { return n; }
            return fib(n - 1) + fib(n - 2);
        }
        fn main() {
            print fib(10);
        }
    )");
    CHECK(r.ok());
    BuildOptions opts;
    opts.outputPath = "/tmp/minilangc-test-fib";
    auto br = buildExecutable(emitLLVMIR(r.program), opts);
    CHECK(br.ok);
    auto rr = runExecutable(opts.outputPath);
    CHECK(rr.ok);
    CHECK_EQ(rr.stdoutText, std::string("55\n"));
}

static void test_e2e_loop()
{
    if (!toolchainAvailable())
        return;
    auto r = fe(R"(
        fn main() {
            let i = 0;
            while (i < 3) {
                print i;
                i = i + 1;
            }
        }
    )");
    CHECK(r.ok());
    BuildOptions opts;
    opts.outputPath = "/tmp/minilangc-test-loop";
    auto br = buildExecutable(emitLLVMIR(r.program), opts);
    CHECK(br.ok);
    auto rr = runExecutable(opts.outputPath);
    CHECK(rr.ok);
    CHECK_EQ(rr.stdoutText, std::string("0\n1\n2\n"));
}

int main()
{
    test_frontend_smoke();
    test_frontend_needs_main();
    test_frontend_arity();
    test_frontend_unknown_var();
    test_ir_shape();
    test_ir_call();
    test_e2e_print_42();
    test_e2e_fib();
    test_e2e_loop();
    std::cout << g_passed << "/" << g_checks << " checks passed\n";
    return g_passed == g_checks ? 0 : 1;
}
