using BinaryBuilder, SHA
using BinaryBuilderBase: aatriplet

const YGGDRASIL_DIR = "../.."
include(joinpath(YGGDRASIL_DIR, "fancy_toys.jl"))

function build_csl(ARGS, version::VersionNumber;
                   # Note: use preferred_gcc_version=v"100" to always force
                   # latest compatible version.
                   preferred_gcc_version::VersionNumber,
                   windows_staticlibs::Bool,
                   unix_staticlibs::Bool,
                   julia_compat::String,
                   )
    name = "CompilerSupportLibraries"

    # We are going to need to extract the latest libstdc++ and libgomp from BB
    # So let's grab them into tarballs by using preferred_gcc_version:
    extraction_script = raw"""
mkdir -p ${libdir}
for d in /opt/${target}/${target}/lib*; do
    # Copy all the libstdc++ and libgomp files:
    cp -av ${d}/libstdc++*.${dlext}* ${libdir} || true
    cp -av ${d}/libgomp*.${dlext}* ${libdir} || true
    # `libgcc_s` changed ABI for darwin platforms in GCC 12, so we have `libgcc_s.1.dylib`
    # until GCC 11, and `libgcc_s.1.1.dylib` after that.  Since `libstdc++` links to
    # `libgcc_s` we need to make sure it's able to find its depedency, also in the
    # libgfortran{3,4} packages.
    if [[ ${target} == x86_64-apple-darwin* ]]; then
        cp -av ${d}/libgcc_s.*.${dlext}* ${libdir} || true
    fi
    # Don't copy `.a` or `.py` files.  >:[
    rm -f ${libdir}/*.a ${libdir}/*.py
done
"""

    extraction_platforms = supported_platforms(;experimental=true)
    extraction_products = [
        LibraryProduct("libstdc++", :libstdcxx),
        LibraryProduct("libgomp", :libgomp),
    ]

    # Don't actually run extraction if we're asking for a JSON, but don't print it either
    if any(startswith(a, "--meta-json") for a in ARGS)
        # How delightfully meta, for when we're calculating the meta!  ;D
        self_url = @__FILE__
        self_hash = open(io -> bytes2hex(sha256(io)), self_url)
        build_info = Dict(p => (self_url, self_hash) for p in BinaryBuilder.BinaryBuilderBase.abi_agnostic.(extraction_platforms))
    else
        build_info = autobuild(joinpath(@__DIR__, "build", "extraction"),
                               "LatestLibraries",
                               version,
                               FileSource[],
                               extraction_script,
                               # Only extract for platforms we're actually going to use
                               filter(should_build_platform, extraction_platforms),
                               extraction_products,
                               Dependency[];
                               skip_audit=true,
                               # Force latest compatible version.
                               preferred_gcc_version,
                               verbose="--verbose" in ARGS,
                               debug="--debug" in ARGS,
                               )
    end

    ## Now that we've got those tarballs, we're going to use them as sources to overwrite
    ## the libstdc++ and libgomp that we would otherwise get from our compiler shards:
    script = "WINDOWS_STATICLIBS=$(windows_staticlibs)\nUNIX_STATICLIBS=$(unix_staticlibs)\nPREFERRED_GCC_VERSION=$(preferred_gcc_version)\nVERSION=$(version.major * 100 ^ 2 + version.minor * 100 + version.patch)\n" * raw"""
# Start by extracting LatestLibraries
tar -zxvf ${WORKSPACE}/srcdir/LatestLibraries*.tar.gz -C ${prefix}

# Make sure expansions aren't empty
shopt -s nullglob

# copy out all the libraries we can find except libstdc++ and libgomp,
# which we copied out in the extraction step above.
for lib in /opt/${target}/${target}/lib*/*.${dlext}*; do
    if [[ "${lib}" != *libstdc++* ]] && [[ "${lib}" != *libgomp* ]]; then
        cp -uav "${lib}" "${libdir}/"
    fi
done

# libwinpthread is a special snowflake and is only within `bin` for some reason
if [[ ${target} == *mingw* ]]; then
    cp -uav /opt/${target}/${target}/sys-root/bin/*.${dlext}* ${libdir}/
    if [[ "${WINDOWS_STATICLIBS}" == "true" ]]; then
        # From v1.1.0 of CSL we move the Windows `*.a` files to a subdir of `${prefix}/lib`.
        # See <https://github.com/JuliaLang/julia/issues/48081>.
        if [[ "${VERSION}" -ge 10100 ]]; then
           destdir="${prefix}/lib/gcc/${AATRIPLET}/${PREFERRED_GCC_VERSION%.*.*}"
        else
           destdir="${prefix}/lib"
        fi
        # Install also some static and import libraries, needed for linking of pkgimages
        # and for direct ld links of Windows executables and DLLs.
        for lib in libadvapi32.a libdbghelp.a libgcc.a libgcc_s.a libiphlpapi.a libkernel32.a libmingwex.a \
                   libmingw32.a libmsvcrt.a libmsvcrt-os.a libmoldname.a libntdll.a libole32.a libpsapi.a libsecur32.a \
                   libshell32.a libssp.a libsynchronization.a libuser32.a libuserenv.a libuuid.a libwinmm.a libws2_32.a \
                   libpthread.dll.a libssp.dll.a dllcrt2.o crt2.o crt2u.o crtbegin.o crtend.o; do
            qfind "/opt/${target}" -name "${lib}" -exec install -Dvm 0644 '{}' "${destdir}/${lib}" \;
        done
    fi
fi
if [[ "${UNIX_STATICLIBS}" == "true" ]] && [[ ${target} != *mingw* ]]; then
    destdir="${prefix}/lib/gcc/${AATRIPLET}/${PREFERRED_GCC_VERSION%.*.*}"
    qfind "/opt/${target}" -name "libgcc.a" -exec install -Dvm 0644 '{}' "${destdir}/libgcc.a" \;
    if [[ ${target} == *linux* ]] || [[ ${target} == *freebsd* ]]; then
        for lib in crti.o crtn.o crtbeginS.o crtendS.o; do
            qfind "/opt/${target}" -name "${lib}" -exec install -Dvm 0644 '{}' "${destdir}/${lib}" \;
        done
        if [[ ${target} == *-linux-gnu* ]]; then
            qfind "/opt/${target}" -name "libc_nonshared.a" -exec install -Dvm 0644 '{}' "${destdir}/libc_nonshared.a" \;
        fi
    fi
fi

# Delete .a and .py files, we don't want those.
rm -f ${libdir}/*.a ${libdir}/*.py

# Delete any `.so` files that are not ELF files, since they're mostly likely linker scripts.
# Keep the `libgcc_s.so` linker script though so that `gcc -shared` can pick up `libgcc.a`
# via `-shared-libgcc` / `-lgcc_s` (c.f. JuliaLang/julia#61824)
for f in ${libdir}/*.so; do
    if [[ "$(basename "$f")" == "libgcc_s.so" ]]; then
        continue
    fi
    if [[ "$(file -b "$(realpath "$f")")" != ELF* ]]; then
        rm -vf "$f"
    fi
done

# change permissions so that rpath succeeds
for l in ${libdir}/*; do
    chmod 0755 "${l}"
done

# libgcc_s.X.dylib receives special treatment for now.  We need to reset the dylib id,
# but we don't want to run a full audit, so we do it ourselves.
if [[ ${target} == *apple* ]]; then
    for libgcc_s in ${libdir}/libgcc_s.*.dylib; do
        LIBGCC_NAME=$(basename "${libgcc_s}")
        install_name_tool -id @rpath/${LIBGCC_NAME} "${libdir}/${LIBGCC_NAME}"
        if [[ "${LIBGCC_NAME}" == "libgcc_s.1.dylib" ]] && [[ "${PREFERRED_GCC_VERSION%.*.*}" -ge 12 ]]; then
            # GCC 12 on x86_64-apple-darwin changed the ABI of libgcc_s from 1 to 1.1, but keeping a compatibility shim
            # called libgcc_s.1.dylib which forwards all calls to libgcc_s.1.1.dylib.  We have to manually fix the path
            # of the dependency because the auditor won't do it: <https://github.com/JuliaPackaging/BinaryBuilder.jl/issues/1243>.
            install_name_tool -change "/workspace/destdir/${target}/lib/libgcc_s.1.1.dylib" "@rpath/libgcc_s.1.1.dylib" "${libdir}/${LIBGCC_NAME}"
        fi
    done
fi

# Remove extraneous libraries
rm -f ${libdir}/{libiconv,libxml2,libz}*.${dlext}*

# Install license (we license these all as GPL3, since they're from GCC)
install_license /usr/share/licenses/GPL-3.0+
"""

    # These are the platforms we will build for by default, unless further
    # platforms are passed in on the command line
    platforms = expand_gfortran_versions(supported_platforms())

    # The products that we will ensure are always built
    common_products = [
        LibraryProduct(["libgcc_s", "libgcc_s_sjlj", "libgcc_s_seh"], :libgcc_s),
        LibraryProduct("libstdc++", :libstdcxx),
        LibraryProduct("libgfortran", :libgfortran),
        LibraryProduct("libgomp", :libgomp),
    ]

    for platform in platforms
        if should_build_platform(platform)
            # Find the corresponding source for this platform
            tarball_path, tarball_hash = build_info[BinaryBuilder.BinaryBuilderBase.abi_agnostic(platform)][1:2]
            sources = [
                FileSource(tarball_path, tarball_hash),
            ]
            # Windows and aarch64 Linux don't have a libatomic on older GCC's
            products = if libgfortran_version(platform).major != 3 || !(Sys.iswindows(platform) || arch(platform) == "aarch64")
                # Don't push to the common products, otherwise we'll keep
                # accumulating libatomic into it when looping over all platforms.
                vcat(common_products, LibraryProduct("libatomic", :libatomic))
            else
                common_products
            end
            if windows_staticlibs && Sys.iswindows(platform)
                destdir = version >= v"1.1.0" ? "lib/gcc/$(aatriplet(platform))/$(preferred_gcc_version.major)" : "lib"
                products = vcat(products,
                                [FileProduct("$(destdir)/libadvapi32.a", :libadvapi32_a),
                                 FileProduct("$(destdir)/libdbghelp.a", :libdbghelp_a),
                                 FileProduct("$(destdir)/libgcc.a", :libgcc_a),
                                 FileProduct("$(destdir)/libgcc_s.a", :libgcc_s_a),
                                 FileProduct("$(destdir)/libiphlpapi.a", :libiphlpapi_a),
                                 FileProduct("$(destdir)/libkernel32.a", :libkernel32_a),
                                 FileProduct("$(destdir)/libmingwex.a", :libmingwex_a),
                                 FileProduct("$(destdir)/libmingw32.a", :libmingw32_a),
                                 FileProduct("$(destdir)/libmsvcrt.a", :libmsvcrt_a),
                                 FileProduct("$(destdir)/libmsvcrt-os.a", :libmsvcrt_os_a),
                                 FileProduct("$(destdir)/libmoldname.a", :libmoldname_a),
                                 FileProduct("$(destdir)/libntdll.a", :libntdll_a),
                                 FileProduct("$(destdir)/libole32.a", :libole32_a),
                                 FileProduct("$(destdir)/libpsapi.a", :libpsapi_a),
                                 FileProduct("$(destdir)/libsecur32.a", :libsecur32_a),
                                 FileProduct("$(destdir)/libshell32.a", :libshell32_a),
                                 FileProduct("$(destdir)/libssp.a", :libssp_a),
                                 FileProduct("$(destdir)/libsynchronization.a", :libsynchronization_a),
                                 FileProduct("$(destdir)/libuser32.a", :libuser32_a),
                                 FileProduct("$(destdir)/libuserenv.a", :libuserenv_a),
                                 FileProduct("$(destdir)/libuuid.a", :libuuid_a),
                                 FileProduct("$(destdir)/libwinmm.a", :libwinmm_a),
                                 FileProduct("$(destdir)/libws2_32.a", :libws2_32_a),
                                 FileProduct("$(destdir)/libpthread.dll.a", :libpthread_dll_a),
                                 FileProduct("$(destdir)/libssp.dll.a", :libssp_dll_a),
                                 FileProduct("$(destdir)/dllcrt2.o", :dllcrt2_o),
                                 FileProduct("$(destdir)/crt2.o", :crt2_o),
                                 FileProduct("$(destdir)/crt2u.o", :crt2u_o),
                                 FileProduct("$(destdir)/crtbegin.o", :crtbegin_o),
                                 FileProduct("$(destdir)/crtend.o", :crtend_o),
                                 ])
            end
            if unix_staticlibs && !Sys.iswindows(platform)
                destdir = version >= v"1.1.0" ? "lib/gcc/$(aatriplet(platform))/$(preferred_gcc_version.major)" : "lib"
                products = vcat(products,
                                [FileProduct("$(destdir)/libgcc.a", :libgcc_a)])
                if Sys.islinux(platform) || Sys.isfreebsd(platform)
                    products = vcat(products,
                                    [FileProduct("$(destdir)/crti.o", :crti_o),
                                     FileProduct("$(destdir)/crtn.o", :crtn_o),
                                     FileProduct("$(destdir)/crtbeginS.o", :crtbeginS_o),
                                     FileProduct("$(destdir)/crtendS.o", :crtendS_o),
                                     ])
                    if libc(platform) == "glibc"
                        products = vcat(products,
                                        [FileProduct("$(destdir)/libc_nonshared.a", :libc_nonshared_a),
                                         FileProduct("lib/libgcc_s.so", :libgcc_s_linker_script)])
                    end
                end
            end
            if libc(platform) != "musl"
                products = vcat(products, LibraryProduct("libssp", :libssp))
            end
            platform_script = "AATRIPLET=$(aatriplet(platform))\n" * script
            build_tarballs(ARGS, name, version, sources, platform_script, [platform], products, []; preferred_gcc_version, julia_compat)
        end
    end
end
