"""Build DAOS Control Plane"""
# pylint: disable=too-many-locals
import os
from os.path import join
from os import urandom
from binascii import b2a_hex


def is_firmware_mgmt_build(benv):
    "Check whether this build has firmware management enabled."
    return benv["FIRMWARE_MGMT"] == 1


def get_build_tags(benv):
    "Get custom go build tags."
    tags = ["ucx", "spdk"]
    if is_firmware_mgmt_build(benv):
        tags.append("firmware")
    if not is_release_build(benv):
        tags.append("pprof")
    else:
        tags.append("release")
    return f"-tags {','.join(tags)}"


def is_release_build(benv):
    "Check whether this build is for release."
    return benv.get("BUILD_TYPE") == "release"


def get_build_flags(benv):
    """Return string of build flags"""
    if is_release_build(benv):
        return '-buildmode=pie'
    # enable race detector for non-release builds
    return '-race'


def gen_build_id():
    """generate a unique build id per binary for use by RPM
       https://fedoraproject.org/wiki/PackagingDrafts/Go#Build_ID"""
    buildid = b2a_hex(urandom(20))
    return '0x' + buildid.decode()


def go_ldflags():
    "Create the ldflags option for the Go build."

    Import('daos_version', 'conf_dir')
    path = 'github.com/daos-stack/daos/src/control/build'
    return ' '.join([f'-X {path}.DaosVersion={daos_version}',
                     f'-X {path}.ConfigDir={conf_dir}',
                     f'-B $({gen_build_id()}$)'])


def install_go_bin(env, name, libs=None, install_man=False):
    """
    Build a Go binary whose source is under directory 'name' and install it
    libs should be a list of scons-built libraries, or None if none are needed.
    """

    gosrc = Dir('.').srcnode().abspath
    sources = Glob(f'cmd/{name}/*.go')
    sources.append(env.d_go_bin)

    install_src = f'github.com/daos-stack/daos/src/control/cmd/{name}'
    build_bin = join('$BUILD_DIR/src/control', name)

    if libs is None:
        libs = []
    libs.extend(['daos_common', 'cart', 'gurt'])

    target = env.d_run_command(name, sources, libs,
                               f'cd {gosrc}; {env.d_go_bin} build -mod vendor '
                               + f'-ldflags "{go_ldflags()}" '
                               + f'{get_build_flags(env)} '
                               + f'{get_build_tags(env)} '
                               + f'-o {build_bin} {install_src}')
    env.Install('$PREFIX/bin', target)
    if install_man:
        gen_bin = join('$BUILD_DIR/src/control', name)
        build_path = join('$BUILD_DIR/src/control', f'{name}.8')
        env.Command(build_path, target, f'{gen_bin} manpage -o {build_path}')
        env.Install('$PREFIX/share/man/man8', build_path)


def scons():
    """Execute build"""

    Import('env', 'prereqs')

    denv = env.Clone()

    if denv.get("COMPILER") == 'covc':
        denv.Replace(CC='gcc', CXX='g++')

    # if SPDK_PREFIX differs from PREFIX, copy dir so files can be accessed at runtime
    prefix = denv.subst("$PREFIX")
    sprefix = denv.subst("$SPDK_PREFIX")
    if sprefix not in ["", prefix]:
        def install_dir(srcdir):
            """walk a directory and install targets"""
            for root, _dirs, files in os.walk(srcdir):
                dest_root = os.path.join(prefix, root, sprefix)
                for fname in files:
                    denv.Install(dest_root, join(root, fname))

        install_dir('share/spdk/scripts')
        install_dir('include/spdk')

    denv.Tool('go_builder')

    denv.require('ofi', 'ucx')
    # Sets CGO_LDFLAGS for rpath options
    denv.d_add_rpaths("..", True, True)
    denv.AppendENVPath("CGO_CFLAGS", denv.subst("$_CPPINCFLAGS"), sep=" ")
    if prereqs.client_requested():
        install_go_bin(denv, "daos_agent")
        install_go_bin(denv, "dmg", install_man=True)
        if prereqs.test_requested():
            install_go_bin(denv, "hello_drpc")

        dbenv = denv.Clone()
        dblibs = dbenv.subst("-L$BUILD_DIR/src/gurt "
                             "-L$BUILD_DIR/src/cart "
                             "-L$BUILD_DIR/src/common "
                             "-L$BUILD_DIR/src/client/dfs "
                             "-L$BUILD_DIR/src/utils $_RPATH")
        dbenv.AppendENVPath("CGO_LDFLAGS", dblibs, sep=" ")
        install_go_bin(dbenv, 'daos', libs=['daos_cmd_hdlrs', 'dfs', 'duns', 'daos'],
                       install_man=True)

    if not prereqs.server_requested():
        return

    senv = denv.Clone()

    denv.AppendENVPath("CGO_CFLAGS", denv.subst("$_CPPINCFLAGS"), sep=" ")

    SConscript('lib/spdk/SConscript', exports='denv')

    denv.d_add_rpaths("..", True, True)

    # Copy setup_spdk.sh script to be executed at daos_server runtime.
    senv.Install('$PREFIX/share/daos/control', 'server/init/setup_spdk.sh')

    install_go_bin(senv, "daos_server")

    aenv = denv.Clone()
    aenv.require('spdk', 'pmdk', 'ofi')

    aenv.AppendUnique(LINKFLAGS=["-Wl,--no-as-needed"])
    aenv.Replace(RPATH=[])
    cgolibdirs = aenv.subst("-L$BUILD_DIR/src/control/lib/spdk "
                            "-L$BUILD_DIR/src/gurt "
                            "-L$BUILD_DIR/src/cart "
                            "-L$BUILD_DIR/src/common "
                            "-L$SPDK_PREFIX/lib "
                            "-L$OFI_PREFIX/lib $_RPATH")
    # Explicitly link RTE & SPDK libs for CGO access
    ldopts = cgolibdirs + " -lspdk_env_dpdk -lspdk_nvme -lspdk_vmd -lrte_mempool" + \
        " -lrte_mempool_ring -lrte_bus_pci -lnvme_control -lnuma -ldl"
    aenv.AppendENVPath("CGO_LDFLAGS", ldopts, sep=" ")

    aenv.AppendENVPath("CGO_CFLAGS",
                       senv.subst("-I$SPDK_PREFIX/include -I$OFI_PREFIX/include"),
                       sep=" ")

    # Sets CGO_LDFLAGS for rpath
    aenv.d_add_rpaths(None, True, True)
    install_go_bin(aenv, 'daos_server_helper', libs=['nvme_control'])

    if is_firmware_mgmt_build(aenv):
        print("(EXPERIMENTAL) Building DAOS firmware tools")
        install_go_bin(aenv, "daos_firmware_helper", libs=['nvme_control'])


if __name__ == "SCons.Script":
    scons()
