#!/bin/bash -eu
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

#
# fdb-go-install.sh
#
# Installs the FoundationDB Go bindings for a client. This will download
# the repository from the remote repo either into the go directory
# with the appropriate semantic version. It will then build a few
# generated files that need to be present for the go build to work.
# At the end, it has some advice for flags to modify within your
# go environment so that other packages may successfully use this
# library.
#

DESTDIR="${DESTDIR:-}"
FDBVER="${FDBVER:-5.2.4}"
REMOTE="${REMOTE:-github.com}"
FDBREPO="${FDBREPO:-apple/foundationdb}"

status=0

platform=$(uname)
if [[ "${platform}" == "Darwin" ]] ; then
    FDBLIBDIR="${FDBLIBDIR:-/usr/local/lib}"
    libfdbc="libfdb_c.dylib"
elif [[ "${platform}" == "Linux" ]] ; then
    libfdbc="libfdb_c.so"
    custom_libdir="${FDBLIBDIR:-}"
    FDBLIBDIR=""

    if [[ -z "${custom_libdir}" ]]; then
	search_libdirs=( '/usr/lib' '/usr/lib64' )
    else
	search_libdirs=( "${custom_libdir}" )
    fi

    for libdir in "${search_libdirs[@]}" ; do
        if [[ -e "${libdir}/${libfdbc}" ]]; then
            FDBLIBDIR="${libdir}"
            break
        fi
    done

    if [[ -z "${FDBLIBDIR}" ]]; then
        echo "The FoundationDB C library could not be found in any of:"
        for libdir in "${search_libdirs[@]}" ; do
            echo "   ${libdir}"
        done
        echo "Your installation may be incomplete, or you need to set a custom FDBLIBDIR."
        let status="${status} + 1"
    fi

else
    echo "Unsupported platform ${platform}".
    echo "At the moment, only macOS and Linux are supported by this script."
    let status="${status} + 1"
fi

filedir=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
destdir=""

function printUsage() {
    echo "Usage: fdb-go-install.sh <cmd>"
    echo
    echo "cmd: One of the commands to run. The options are:"
    echo "     install         Download the FDB go bindings and install them"
    echo "     localinstall    Install a into the go path a local copy of the repo"
    echo "     download        Download but do not prepare the FoundationDB bindings"
    echo "     help            Print this help message and then quit"
    echo
    echo "Command Line Options:"
    echo "     --fdbver <version>    FoundationDB semantic version (default is ${FDBVER})"
    echo "     -d/--dest-dir <dest>  Local location for the repo (default is to place in go path)"
    echo
    echo "Environment Variable Options:"
    echo "     REMOTE          Remote repository to download from (currently ${REMOTE})"
    echo "     FDBREPO         Repository of FoundationDB library to download (currently ${FDBREPO})"
    echo "     FDBLIBDIR       Directory within which should be the FoundationDB c library (currently ${FDBLIBDIR})"
}

function parseArgs() {
    local status=0

    if [[ "${#}" -lt 0 ]] ; then
        printUsage
        let status="${status} + 1"
    else
        operation="${1}"
        shift
        if [[ "${operation}" != "install" ]] && [[ "${operation}" != "localinstall" ]] && [[ "${operation}" != "download" ]] && [[ "${operation}" != "help" ]] ; then
            echo "Unknown command: ${operation}"
            printUsage
            let status="${status} + 1"
        fi
    fi

    while [[ "${#}" -gt 0 ]] && [[ "${status}" -eq 0 ]] ; do
        local key="${1}"
        case "${key}" in
            --fdbver)
            if [[ "${#}" -lt 2 ]] ; then
                echo "No version specified with --fdbver flag"
                printUsage
                let status="${status} + 1"
            else
                FDBVER="${2}"
            fi
            shift
            ;;

            -d|--dest-dir)
            if [[ "${#}" -lt 2 ]] ; then
                echo "No destination specified with ${key} flag"
                printUsage
                let status="${status} + 1"
            else
                destdir="${2}"
            fi
            shift
            ;;

            *)
            echo "Unrecognized argument ${key}"
            printUsage
            let status="${status} + 1"
        esac
        shift
    done

    return "${status}"
}

function checkBin() {
    if [[ "${#}" -lt 1 ]] ; then
        echo "Usage: checkBin <binary>"
        return 1
    else
        if [[ -n $(which "${1}") ]] ; then
            return 0
        else
            return 1
        fi
    fi
}

if [[ "${status}" -gt 0 ]] ; then
    # We have already failed.
    :
elif [[ "${#}" -lt 1 ]] ; then
    printUsage
else
    required_bins=( 'go' 'git' 'make' 'mono' )

    missing_bins=()
    for bin in "${required_bins[@]}" ; do
        if ! checkBin "${bin}" ; then
            missing_bins+=("${bin}")
            let status="${status} + 1"
        fi
    done

    if [[ "${status}" -gt 0 ]] ; then
        echo "Missing binaries: ${missing_bins[*]}"
    elif ! parseArgs ${@} ; then
        let status="${status} + 1"
    elif [[ "${operation}" == "help" ]] ; then
        printUsage
    else
        # Add go-specific environment variables.
        eval $(go env)

        golibdir=$(dirname "${GOPATH}/src/${REMOTE}/${FDBREPO}")
        if [[ -z "${destdir}" ]] ; then
            if [[ "${operation}" == "localinstall" ]] ; then
                # Assume its the local directory.
                destdir=$(cd "${filedir}/../../.." && pwd)
            else
                destdir="${golibdir}"
            fi
        fi

        if [[ ! -d "${destdir}" ]] ; then
            cmd=( 'mkdir' '-p' "${destdir}" )
            echo "${cmd[*]}"
            if ! "${cmd[@]}" ; then
                let status="${status} + 1"
                echo "Could not create destination directory ${destdir}."
            fi
        fi

        # Step 1: Make sure repository is present.

        if [[ "${status}" -eq 0 ]] ; then
            destdir=$( cd "${destdir}" && pwd ) # Get absolute path of destination dir.
            fdbdir="${destdir}/foundationdb"

            if [[ ! -d "${destdir}" ]] ; then
                cmd=("mkdir" "-p" "${destdir}")
                echo "${cmd[*]}"
                if ! "${cmd[@]}" ; then
                    echo "Could not create destination directory ${destdir}."
                    let status="${status} + 1"
                fi
            fi
        fi

        if [[ "${operation}" == "localinstall" ]] ; then
            # No download occurs in this case.
            :
        else
            if [[ -d "${fdbdir}" ]] ; then
                echo "Directory ${fdbdir} already exists ; checking out appropriate tag"
                cmd1=( 'git' '-C' "${fdbdir}" 'fetch' 'origin' )
                cmd2=( 'git' '-C' "${fdbdir}" 'checkout' "${FDBVER}" )

                if ! echo "${cmd1[*]}" || ! "${cmd1[@]}" ; then
                    let status="${status} + 1"
                    echo "Could not pull latest changes from origin"
                elif ! echo "${cmd2[*]}" ||  ! "${cmd2[@]}" ; then
                    let status="${status} + 1"
                    echo "Could not checkout tag ${FDBVER}."
                fi
            else
                echo "Downloading foundation repository into ${destdir}:"
                cmd=( 'git' '-C' "${destdir}" 'clone' '--branch' "${FDBVER}" "https://${REMOTE}/${FDBREPO}.git" )

                echo "${cmd[*]}"
                if ! "${cmd[@]}" ; then
                    let status="${status} + 1"
                    echo "Could not download repository."
                fi
            fi
        fi

        # Step 2: Build generated things.

        if [[ "${operation}" == "download" ]] ; then
            # The generated files are not created under a strict download.
            :
        elif [[ "${status}" -eq 0 ]] ; then
            echo "Building generated files."
	    # FoundationDB starting with 6.0 can figure that out on its own
	    if [ -e '/usr/bin/mcs' ]; then
		MCS_BIN=/usr/bin/mcs
	    else
		MCS_BIN=/usr/bin/dmcs
	    fi
            cmd=( 'make' '-C' "${fdbdir}" 'bindings/c/foundationdb/fdb_c_options.g.h' "MCS=$MCS_BIN" )

            echo "${cmd[*]}"
            if ! "${cmd[@]}" ; then
                let status="${status} + 1"
                echo "Could not generate required c header"
            else
                infile="${fdbdir}/fdbclient/vexillographer/fdb.options"
                outfile="${fdbdir}/bindings/go/src/fdb/generated.go"
                cmd=( 'go' 'run' "${fdbdir}/bindings/go/src/_util/translate_fdb_options.go" )
                echo "${cmd[*]} < ${infile} > ${outfile}"
                if ! "${cmd[@]}" < "${infile}" > "${outfile}" ; then
                    let status="${status} + 1"
                    echo "Could not generate generated go file."
                fi
            fi
        fi

        # Step 3: Add to go path.

        if [[ "${operation}" == "download" ]] ; then
            # The files are not moved under a strict download.
            :
        elif [[ "${status}" -eq 0 ]] ; then
            linkpath="${GOPATH}/src/${REMOTE}/${FDBREPO}"
            if [[ "${linkpath}" == "${fdbdir}" ]] ; then
                # Downloaded directly into go path. Skip making the link.
                :
            elif [[ -e "${linkpath}" ]] ; then
                echo "Warning: link path (${linkpath}) already exists. Leaving in place."
            else
                dirpath=$(dirname "${linkpath}")
                if [[ ! -d "${dirpath}" ]] ; then
                    cmd=( 'mkdir' '-p' "${dirpath}" )
                    echo "${cmd[*]}"
                    if ! "${cmd[@]}" ; then
                        let status="${status} + 1"
                        echo "Could not create directory for link."
                    fi
                fi

                if [[ "${status}" -eq 0 ]] ; then
                    cmd=( 'ln' '-s' "${fdbdir}" "${linkpath}" )
                    echo "${cmd[*]}"
                    if ! "${cmd[@]}" ; then
                        let status="${status} + 1"
                        echo "Could not create link within go path."
                    fi
                fi
            fi
        fi

        # Step 4: Build the binaries.

        if [[ "${operation}" == "download" ]] ; then
            # Do not install if only downloading
            :
        elif [[ "${status}" -eq 0 ]] ; then
            cgo_cppflags="-I${linkpath}/bindings/c"
            cgo_cflags="-g -O2"
            cgo_ldflags="-L${FDBLIBDIR}"
            fdb_go_path="${REMOTE}/${FDBREPO}/bindings/go/src"

            if ! CGO_CPPFLAGS="${cgo_cppflags}" CGO_CFLAGS="${cgo_cflags}" CGO_LDFLAGS="${cgo_ldflags}" go install "${fdb_go_path}/fdb" "${fdb_go_path}/fdb/tuple" "${fdb_go_path}/fdb/subspace" "${fdb_go_path}/fdb/directory" ; then
                let status="${status} + 1"
                echo "Could not build FoundationDB go libraries."
            fi
        fi

        # Step 5: Explain CGO flags.

        if [[ "${status}" -eq 0 && ("${operation}" == "localinstall" || "${operation}" == "install" ) ]] ; then
            echo
            echo "The FoundationDB go bindings were successfully installed."
            echo "To build packages which use the go bindings, you will need to"
            echo "set the following environment variables:"
            echo "   CGO_CPPFLAGS=\"${cgo_cppflags}\""
            echo "   CGO_CFLAGS=\"${cgo_cflags}\""
            echo "   CGO_LDFLAGS=\"${cgo_ldflags}\""
        fi
    fi
fi

exit "${status}"
