using Dates

# Change this so e.g. `julia_path("1.1")` gives the right binary for Julia
julia_path(v) = "julia" * replace(v, "." => "p")

println_flush(io, x) = (println(io, x); flush(io))
println_flush(x) = io->println_flush(io, x)

function print_code(cmd)
    return io->begin
        println_flush(io, "\n" * raw"```julia")
        r = run(pipeline(cmd, stdout = io, stderr = io); wait = false)
        wait(r)
        flush(io)
        println_flush(io, raw"```" * "\n")
    end
end

mutable struct LazyBuffer
    buffer::IOBuffer
    fill!
    resolved::Bool
    LazyBuffer(f) = new(IOBuffer(), f, false)
end

LazyBuffer(s::AbstractString) = LazyBuffer(io->println(io, s))

function resolve!(L::LazyBuffer)
    L.resolved && return
    L.fill!(L.buffer)
    L.resolved = true
    return
end

let
    versions = ["1.1", "1.2", "1.3"]
    envs = [("ZygoteMaster", "Zygote master"), ("ZygoteRelease", "Zygote release")]
    methods = readdir("Methods")
    buffers = LazyBuffer[]

    setup_dict = Dict()
    push!(buffers, LazyBuffer("# Attempts to differentiate `eigmin` in Julia\n"))
    push!(buffers, LazyBuffer("Generated by `run.jl`; uses OS-dependent paths that likely need to be updated."))

    # populate buffers
    @info "Lazily defining buffers"  Dates.now()
    for method in methods
        method_path = joinpath("Methods", method)
        push!(buffers, LazyBuffer("## Method $method\n"))
        push!(buffers, LazyBuffer("Code:"))
        push!(buffers, LazyBuffer(print_code(`cat $method_path`)))
        push!(buffers, LazyBuffer("Results:"))

        for (env, envname) in envs
            for version in versions
                binary = julia_path(version)
                push!(buffers, LazyBuffer("\n### Julia $version, with $envname"))
                B = LazyBuffer(print_code(`$binary --project=$env $method_path`))
                push!(buffers, B)
                list = get!(setup_dict, (env, version), [])
                push!(list, B)
            end
        end
    end
    push!(buffers, LazyBuffer("\n## Version information\n"))

    for (env, envname) in envs
        for version in versions
            binary = julia_path(version)
            push!(buffers, LazyBuffer("\n### Julia $version, with $envname\n"))
            push!(buffers, LazyBuffer("Version information:"))
            B = LazyBuffer(print_code(`$binary --project=$env verinfo.jl`))
            push!(buffers, B)
            list = get!(setup_dict, (env, version), [])
            push!(list, B)
        end
    end

    @info "Done defining buffers. Starting to resolve buffers"  Dates.now()

    # Resolve in parallel without overlapping setups
    for (env, envname) in envs
        for version in versions
            binary = julia_path(version)
            @info "Starting to resolve environment" env version Dates.now()
            run(`$binary --project=$env precompile.jl`)
            @info "Done precompile" env version Dates.now()
            list = setup_dict[(env, version)]
            @sync begin
                for B in list
                    @async resolve!(B)
                end
            end
            @info "Finished resolving environment" env version  Dates.now()
        end
    end

    @info "Resolving the remaining buffers" Dates.now()
    resolve!.(buffers) # resolve the rest

    @info "Writing to `README.md`"  Dates.now()
    # write buffers to file in order
    open("README.md", "w") do file
        for L in buffers
            print(file, String(take!(L.buffer)))
        end
    end
    @info "Done!" Dates.now()
end
