#!/usr/bin/env bash
# shellcheck disable=SC2155 enable=require-variable-braces

set -Exeo pipefail
auth_header=()
if [ -n "${GITHUB_TOKEN}" ]; then
	auth_header=("-H" "Authorization: token ${GITHUB_TOKEN}")
fi

# set CWD to the root of filecoin-ffi
#
cd "$(dirname "${BASH_SOURCE[0]}")"

# tracks where the Rust sources are were we to build locally instead of
# downloading from GitHub Releases
#
rust_sources_dir="rust"

# an array of values passed as 'target-feature' to the Rust compiler if we're
# building an optimized libfilcrypto (which takes advantage of some perf-boosting
# instruction sets)
#
#optimized_release_rustc_target_features=$(jq -r '.[].rustc_target_feature' < "${rust_sources_dir}/rustc-target-features-optimized.json")

# each value in this area is checked against the "features" of the hosts CPU
# in order to determine if the host is suitable for an optimized release
#
cpu_features_required_for_optimized_release=$(jq -r '.[].check_cpu_for_feature | select(. != null)' < "${rust_sources_dir}/rustc-target-features-optimized.json")

main() {
    local __release_flags=$(get_release_flags)
    if [ "${FFI_BUILD_FROM_SOURCE}" != "1" ] && download_release_tarball __tarball_path "${rust_sources_dir}" "filecoin-ffi" "${__release_flags}"; then
        local __tmp_dir=$(mktemp -d)

        # silence shellcheck warning as the assignment happened in
        # `download_release_tarball()`
        # shellcheck disable=SC2154
        # extract downloaded tarball to temporary directory
        #
        tar -C "${__tmp_dir}" -xzf "${__tarball_path}"

        # copy build assets into root of filecoin-ffi
        #
        find -L "${__tmp_dir}" -type f -name filcrypto.h -exec cp -- "{}" . \;
        find -L "${__tmp_dir}" -type f -name libfilcrypto.a -exec cp -- "{}" . \;
        find -L "${__tmp_dir}" -type f -name filcrypto.pc -exec cp -- "{}" . \;

        check_installed_files

        (>&2 echo "[install-filcrypto/main] successfully installed prebuilt libfilcrypto")
    else
        (>&2 echo "[install-filcrypto/main] building libfilcrypto from local sources (dir = ${rust_sources_dir})")

        # build libfilcrypto (and corresponding header and pkg-config)
        #
        build_from_source "filcrypto" "${rust_sources_dir}" "${__release_flags}"

        # copy from Rust's build directory (target) to root of filecoin-ffi
        #
        find -L "${rust_sources_dir}/target/release" -type f -name filcrypto.h -exec cp -- "{}" . \;
        find -L "${rust_sources_dir}/target/release" -type f -name libfilcrypto.a -exec cp -- "{}" . \;
        find -L "${rust_sources_dir}" -type f -name filcrypto.pc -exec cp -- "{}" . \;

        check_installed_files

        (>&2 echo "[install-filcrypto/main] successfully built and installed libfilcrypto from source")
    fi
}

download_release_tarball() {
    local __resultvar=$1
    local __rust_sources_path=$2
    local __repo_name=$3
    local __release_flags=$4
    local __release_sha1=$(git rev-parse HEAD)
    local __release_tag="${__release_sha1:0:16}"
    local __release_tag_url="https://api.github.com/repos/filecoin-project/${__repo_name}/releases/tags/${__release_tag}"

    # Download either the non-optimized standard or standard-blst release.
    if [ "${FFI_USE_BLST}" == "1" ]; then
        release_flag_name="standard-blst"
    else
        release_flag_name="standard-pairing"
    fi

    # TODO: This function shouldn't make assumptions about how these releases'
    # names are constructed. Marginally less-bad would be to require that this
    # function's caller provide the release name.
    #
    local __release_name="${__repo_name}-$(uname)-${release_flag_name}"

    (>&2 echo "[download_release_tarball] acquiring release @ ${__release_tag}")

    local __release_response=$(curl "${auth_header[@]}" \
        --retry 3 \
        --location "${__release_tag_url}")

    local __release_url=$(echo "${__release_response}" | jq -r ".assets[] | select(.name | contains(\"${__release_name}\")) | .url")

    local __tar_path="/tmp/${__release_name}_$(basename "${__release_url}").tar.gz"

    if [[ -z "${__release_url}" ]]; then
        (>&2 echo "[download_release_tarball] failed to download release (tag URL: ${__release_tag_url}, response: ${__release_response})")
        return 1
    fi

    local __asset_url=$(curl "${auth_header[@]}" \
        --head \
        --retry 3 \
        --header "Accept:application/octet-stream" \
        --location \
        --output /dev/null \
        -w "%{url_effective}" \
        "${__release_url}")

    if ! curl --retry 3 --output "${__tar_path}" "${__asset_url}"; then
        (>&2 echo "[download_release_tarball] failed to download release asset (tag URL: ${__release_tag_url}, asset URL: ${__asset_url})")
        return 1
    fi

    # set $__resultvar (which the caller provided as $1), which is the poor
    # man's way of returning a value from a function in Bash
    #
    eval "${__resultvar}='${__tar_path}'"
}

build_from_source() {
    local __library_name=$1
    local __rust_sources_path=$2
    local __release_flags=$3
    local __repo_sha1=$(git rev-parse HEAD)
    local __repo_sha1_truncated="${__repo_sha1:0:16}"

    (>&2 echo "building from source @ ${__repo_sha1_truncated}")

    if ! [ -x "$(command -v cargo)" ]; then
        (>&2 echo '[build_from_source] Error: cargo is not installed.')
        (>&2 echo '[build_from_source] install Rust toolchain to resolve this problem.')
        exit 1
    fi

    if ! [ -x "$(command -v rustup)" ]; then
        (>&2 echo '[build_from_source] Error: rustup is not installed.')
        (>&2 echo '[build_from_source] install Rust toolchain installer to resolve this problem.')
        exit 1
    fi

    pushd "${__rust_sources_path}"

    cargo --version

    # Default to use gpu flags, unless specified to disable
    gpu_flags=",gpu"
    if [ "${FFI_USE_GPU}" == "0" ]; then
        gpu_flags=""
    fi
    # Default to use multicore_sdr flags, unless specified to disable
    use_multicore_sdr="--features multicore-sdr"
    if [ "${FFI_USE_MULTICORE_SDR}" == "0" ]; then
        use_multicore_sdr=""
    fi

    # Add feature specific rust flags as needed here.
    if [ "${FFI_USE_BLST_PORTABLE}" == "1" ]; then
        additional_flags="--no-default-features ${use_multicore_sdr} --features blst --features blst-portable${gpu_flags}"
    elif [ "${FFI_USE_BLST}" == "1" ]; then
        additional_flags="--no-default-features ${use_multicore_sdr} --features blst${gpu_flags}"
    else
        additional_flags="--no-default-features ${use_multicore_sdr} --features pairing${gpu_flags}"
    fi

    if [ -n "${__release_flags}" ]; then
        RUSTFLAGS="-C target-feature=${__release_flags}" ./scripts/build-release.sh "${__library_name}" "$(cat rust-toolchain)" "${additional_flags}"
    else
        ./scripts/build-release.sh "${__library_name}" "$(cat rust-toolchain)" "${additional_flags}"
    fi

    popd
}

get_release_flags() {
    local __features=""

    # determine where to look for CPU features
    #
    if [[ ! -f "/proc/cpuinfo" ]]; then
        (>&2 echo "[get_release_flags] no /proc/cpuinfo file; falling back to Darwin feature detection")
        __features=$(sysctl -a | grep machdep.cpu | tr '[:upper:]' '[:lower:]' | grep features)
    else
        #aarch64_uname=$(uname -a | grep aarch64)
        x86_64_uname=$(uname -a | grep x86_64)
        # shellcheck disable=SC2002
        if [ -n "${x86_64_uname}" ]; then
            __features=$(cat /proc/cpuinfo | grep flags | head -n 1)
        else
            # For now we assume aarch64.  If another supported platform is added, explicitly check for it
            __features=$(cat /proc/cpuinfo | grep Features | head -n 1)
        fi
    fi

    # Maps cpu flag to rust flags (related to entries in rust/rustc-target-features-optimized.json)
    feature_map=("adx:+adx" "sha_ni:+sha" "sha2:+sha2" "sse2:+sse2" "avx2:+avx2" "avx:+avx" "sse4_2:+sse4.2" "sse4_1:+sse4.1")

    target_features=""
    # check for the presence of each required CPU feature
    #
    # shellcheck disable=SC2068 # the splitting is intentional
    for x in ${cpu_features_required_for_optimized_release[@]}; do
        current_feature=$(echo "${__features}" | grep -c "${x}")
        if [ "1" = "${current_feature}" ]; then
            for feature in "${feature_map[@]}"; do
                key=${feature%%:*}
                if [ "${key}" == "${x}" ]; then
                    val=${feature#*:}
                    if [ -z "${target_features}" ]; then
                        target_features="${val}"
                    else
                        target_features="${target_features},${val}"
                    fi
                fi
            done
        fi
    done

    echo "${target_features}"
}

check_installed_files() {
    if [[ ! -f "./filcrypto.h" ]]; then
        (>&2 echo "[check_installed_files] failed to install filcrypto.h")
        exit 1
    fi

    if [[ ! -f "./libfilcrypto.a" ]]; then
        (>&2 echo "[check_installed_files] failed to install libfilcrypto.a")
        exit 1
    fi

    if [[ ! -f "./filcrypto.pc" ]]; then
        (>&2 echo "[check_installed_files] failed to install filcrypto.pc")
        exit 1
    fi
}

main "$@"; exit
