module BinaryPlatformsCompat

    export platform_key_abi, platform_dlext, valid_dl_path, arch, libc,
        libgfortran_version, libstdcxx_version, cxxstring_abi, parse_dl_name_version,
        detect_libgfortran_version, detect_libstdcxx_version, detect_cxxstring_abi,
        call_abi, wordsize, triplet, select_platform, platforms_match,
        CompilerABI, Platform, UnknownPlatform, Linux, MacOS, Windows, FreeBSD

    using Base.BinaryPlatforms: parse_dl_name_version,
        detect_libgfortran_version, detect_libstdcxx_version, detect_cxxstring_abi,
        os, call_abi, select_platform, platforms_match,
        AbstractPlatform, Platform, HostPlatform

    import Base.BinaryPlatforms: libgfortran_version, libstdcxx_version, platform_name,
        wordsize, platform_dlext, tags, arch, libc, call_abi,
        cxxstring_abi

    struct UnknownPlatform <: AbstractPlatform
        UnknownPlatform(args...; kwargs...) = new()
    end
    tags(::UnknownPlatform) = Dict{String, String}("os" => "unknown")


    struct CompilerABI
        libgfortran_version::Union{Nothing, VersionNumber}
        libstdcxx_version::Union{Nothing, VersionNumber}
        cxxstring_abi::Union{Nothing, Symbol}

        function CompilerABI(;
                libgfortran_version::Union{Nothing, VersionNumber} = nothing,
                libstdcxx_version::Union{Nothing, VersionNumber} = nothing,
                cxxstring_abi::Union{Nothing, Symbol} = nothing
            )
            return new(libgfortran_version, libstdcxx_version, cxxstring_abi)
        end
    end

    # Easy replacement constructor
    function CompilerABI(
            cabi::CompilerABI; libgfortran_version = nothing,
            libstdcxx_version = nothing,
            cxxstring_abi = nothing
        )
        return CompilerABI(;
            libgfortran_version = something(libgfortran_version, Some(cabi.libgfortran_version)),
            libstdcxx_version = something(libstdcxx_version, Some(cabi.libstdcxx_version)),
            cxxstring_abi = something(cxxstring_abi, Some(cabi.cxxstring_abi)),
        )
    end

    libgfortran_version(cabi::CompilerABI) = cabi.libgfortran_version
    libstdcxx_version(cabi::CompilerABI) = cabi.libstdcxx_version
    cxxstring_abi(cabi::CompilerABI) = cabi.cxxstring_abi

    for T in (:Linux, :Windows, :MacOS, :FreeBSD)
        @eval begin
            struct $(T) <: AbstractPlatform
                p::Platform
                function $(T)(arch::Symbol; compiler_abi = nothing, kwargs...)
                    if compiler_abi !== nothing
                        kwargs = (;
                            kwargs...,
                            :libgfortran_version => libgfortran_version(compiler_abi),
                            :libstdcxx_version => libstdcxx_version(compiler_abi),
                            :cxxstring_abi => cxxstring_abi(compiler_abi),
                        )
                    end
                    return new(Platform(string(arch), $(string(T)); kwargs..., validate_strict = true))
                end
            end
        end
    end

    const PlatformUnion = Union{Linux, MacOS, Windows, FreeBSD}

    # First, methods we need to coerce to Symbol for backwards-compatibility
    for f in (:arch, :libc, :call_abi, :cxxstring_abi)
        @eval begin
            function $(f)(p::PlatformUnion)
                str = $(f)(p.p)
                if str === nothing
                    return nothing
                end
                return Symbol(str)
            end
        end
    end

    # Next, things we don't need to coerce
    for f in (:libgfortran_version, :libstdcxx_version, :platform_name, :wordsize, :platform_dlext, :tags, :triplet)
        @eval begin
            $(f)(p::PlatformUnion) = $(f)(p.p)
        end
    end

    # Finally, add equality testing between these wrapper types and other AbstractPlatforms
    @eval begin
        Base.:(==)(a::PlatformUnion, b::AbstractPlatform) = b == a.p
    end

    # Add one-off functions
    MacOS(; kwargs...) = MacOS(:x86_64; kwargs...)
    FreeBSD(; kwargs...) = FreeBSD(:x86_64; kwargs...)

    function triplet(p::AbstractPlatform)
        # We are going to sub off to `Base.BinaryPlatforms.triplet()` here,
        # with the important exception that we override `os_version` to better
        # mimic the old behavior of `triplet()`
        if Sys.isfreebsd(p)
            p = deepcopy(p)
            p["os_version"] = "11.1.0"
        elseif Sys.isapple(p)
            p = deepcopy(p)
            p["os_version"] = "14.0.0"
        end
        return Base.BinaryPlatforms.triplet(p)
    end

    """
        platform_key_abi(machine::AbstractString)

    Returns the platform key for the current platform, or any other though the
    the use of the `machine` parameter.

    This method is deprecated, import `Base.BinaryPlatforms` and use either `HostPlatform()`
    to get the current host platform, or `parse(Base.BinaryPlatforms.Platform, triplet)`
    to parse the triplet for some other platform instead.
    """
    platform_key_abi() = HostPlatform()
    platform_key_abi(triplet::AbstractString) = parse(Platform, triplet)

    """
        valid_dl_path(path::AbstractString, platform::Platform)

    Return `true` if the given `path` ends in a valid dynamic library filename.
    E.g. returns `true` for a path like `"usr/lib/libfoo.so.3.5"`, but returns
    `false` for a path like `"libbar.so.f.a"`.

    This method is deprecated and will be removed in Julia 2.0.
    """
    function valid_dl_path(path::AbstractString, platform::AbstractPlatform)
        try
            parse_dl_name_version(path, string(os(platform))::String)
            return true
        catch e
            if isa(e, ArgumentError)
                return false
            end
            rethrow(e)
        end
    end

end # module BinaryPlatformsCompat

const BinaryPlatforms = BinaryPlatformsCompat
