import dshell;

import std.algorithm;
import std.file;
import std.process;
import std.regex;
import std.conv;

int main()
{
    version(DigitalMars) { }
    else
    {
        writeln("Skipping dwarf.d for non-DMD compilers.");
        return DISABLED;
    }

    version(Windows)
    {
        writeln("Skipping dwarf.d for Windows.");
        return DISABLED;
    }
    version(OSX)
    {
        writeln("Skipping dwarf.d for MacOS X.");
        return DISABLED;
    }

    version(Windows)
        immutable slash = "\\";
    else
        immutable slash = "/";

    // If the Unix system doesn't have objdump, disable the tests
    auto sysObjdump = executeShell("objdump --version");
    if (sysObjdump.status)
        return DISABLED;

    double objdumpVersion;
    try
    {
        // output examples :
        // GNU objdump 2.15
        // GNU objdump 2.17.50 [FreeBSD] 2007-07-03
        // GNU objdump (GNU Binutils) 2.36.1
        string strVer = sysObjdump.output.split("\n")[0];
        writeln("Objdump version of the machine : ", strVer);

        auto cap = matchFirst(strVer, `[0-9]+\.[0-9]+`);
        assert(cap);
        objdumpVersion = cap.hit.to!double;
    }
    catch (ConvException ce)
    {
        // The conversion failed
        return DISABLED;
    }
    writeln("Parsed objdump version of the machine : ", objdumpVersion);

    // DWARF 3 (and 4) support has been implemented in version 2.20 (2.20.51.0.1)
    if(objdumpVersion < 2.20)
        return DISABLED;

    immutable extra_dwarf_dir = EXTRA_FILES ~ slash ~ "dwarf" ~ slash;
    bool failed;

    // test them all
    foreach (string path; dirEntries(extra_dwarf_dir, SpanMode.shallow))
    {
        if (!isFile(path) || extension(path) != ".d")
            continue;

        // retrieve the filename without the extension
        auto filename = baseName(stripExtension(path));
        string[string] requirements = getRequirements(path);

        try
        {
            if (objdumpVersion < requirements["MIN_OBJDUMP_VERSION"].to!double)
            {
                writeln("Warning: test " ~ path ~ " skipped.");
                continue ;
            }
        }
        catch(Exception e) { }


        string exe = OUTPUT_BASE ~ slash ~ filename;
        run("$DMD -m$MODEL -of" ~ exe ~ "$EXE -conf= -fPIE -g " ~ requirements["EXTRA_ARGS"]
            ~ " -I" ~ extra_dwarf_dir ~ " " ~ path);

        // Write objdump result to a file
        auto objdump = exe ~ ".objdump";

        auto objdump_file = File(objdump, "w");
        run("objdump -W " ~ exe, objdump_file);
        objdump_file.close();

        auto llvmDwarfdump = executeShell("llvm-dwarfdump --version");
        try {
            if (!llvmDwarfdump.status && requirements["DWARF_VERIFY"].to!bool)
                run("llvm-dwarfdump --verify " ~ exe);
        } catch (ConvException e) { }

        // Objdump excepted results
        string excepted_results_file = extra_dwarf_dir ~ "excepted_results"
            ~ slash ~ filename ~ ".txt";

        if (!exists(excepted_results_file))
            assert(0, "DWARF tests must have a .txt file in the `excepted_results`"
                ~ " folder which contains the DWARF dump info");

        // Read file result as a string
        auto result = cast(string)readText(objdump);

        string failmsg;

        foreach (line; File(excepted_results_file).byLine)
        {
            if (!canFind(result, line))
            {
                failmsg ~= filename ~ ": Couln't find `" ~ line
                    ~ "` in the DWARF dump info.\n";
                failed = true;
            }
        }

        if (failmsg)
        {
            // Writes the result into stdout for the CI machines.
            writeln(result);
            write(failmsg);
        }
    }

    return failed;
}

string[string] getRequirements(string dfile)
{
    string[string] result;
    // Initialize to an empty string to prevent RangeViolation exceptions.
    foreach (req; ["EXTRA_ARGS", "MIN_OBJDUMP_VERSION"])
        result[req] = "";
    result["DWARF_VERIFY"] = "true";

    bool begin;

    auto f = File(dfile, "r");

    foreach (line; f.byLine)
    {
        if (line.length < 2)
            continue;

        if (line.startsWith("/*"))
        {
            begin = true;
        }

        if (begin)
        {
            string[] args = line.split(":").to!(string[]);
            if (args.length == 2)
            {
                result[args[0]] = args[1].strip();
            }

            if (line.indexOf("*/") != -1)
            {
                break;
            }
        }
    }
    f.close();
    return result;
}
