using Clang
using Clang.LibClang.Clang_jll

const LBT_ROOT = abspath(dirname(dirname(@__DIR__)))

# Steal default include path from `cc`
function get_default_include_path()
    io = IOBuffer()
    run(pipeline(`cc -E -Wp,-v -`, stdout=devnull, stderr=io, stdin=devnull))
    lines = split(String(take!(io)), "\n")
    start_idx = findfirst(l -> startswith(l, "#include <") && endswith(l, "search starts here:"), lines)
    stop_idx = findfirst(l -> startswith(l, "End of search list"), lines[start_idx+1:end])
    return strip.(lines[start_idx+1:start_idx+stop_idx-1])
end

function extract_symbol_names(include_dir::String = joinpath(LBT_ROOT, "include", "LP64", "x86_64-linux-gnu"))
    # Collect all headers in that directory (nonrecursive because I'm lazy)
    headers = [joinpath(include_dir, header) for header in readdir(include_dir) if endswith(header, ".h")]

    # Build `-I` arguments to all the places we'll need
    clang_args = String[
        # Include standard locations
        ["-I$(inc)" for inc in get_default_include_path()]...,
        
        # Include our own include directory
        "-I$(include_dir)",
    ]

    # Parse the headers into a `Clang.jl` context
    @info("Extracting symbols from $(length(headers)) headers in $(include_dir)")
    ctx = DefaultContext()
    parse_headers!(ctx, headers, args=clang_args, includes=headers)

    # Collect all symbol names defined within these translation units
    symbol_names = String[]
    for trans_unit in ctx.trans_units
        root_cursor = getcursor(trans_unit)
        push!(ctx.cursor_stack, root_cursor)
        header = spelling(root_cursor)
        # loop over all of the child cursors and wrap them, if appropriate.
        ctx.children = children(root_cursor)
        for (i, child) in enumerate(ctx.children)
            # Skip everything except function declarations
            if !isa(child, CLFunctionDecl)
                continue
            end
            # Skip functions that aren't defined in this header
            if filename(child) != header
                continue
            end

            push!(symbol_names, spelling(child))
        end
        empty!(ctx.api_buffer)  # clean up api_buffer for the next header
    end

    # Drop duplicate `__`-prefixed functions
    @info("Extracted $(length(symbol_names)) symbols")
    return sort(symbol_names)
end

# By default, we extract symbols corresponding to an LP64 (32-bit) x86_64-linux-gnu build of OpenBLAS
symbol_names = extract_symbol_names()

open(joinpath(LBT_ROOT, "src", "exported_funcs.inc"), "w") do io
    println(io, "// This file generated by `ext/clang/generate_func_list.jl`")
    println(io, "#ifndef EXPORTED_FUNCS")
    println(io, "#define EXPORTED_FUNCS(XX) \\")
    for name in symbol_names
        println(io, "    XX($(name)) \\")
    end
    println(io)
    println(io, "#endif")
end