import os
import re
try:
    import ln_scons
except Exception as e:
    fn = os.path.join("site_scons", "ln_scons.py")
    if not os.path.isfile(fn):
        msg = "file %r does not exist -- did you clone with `--recursive` ? if not do `git submodule update --init --recursive` now!" % fn
    else:
        msg = str(e)
    raise Exception("could not import `ln_scons` python package: %s" % msg)

from SCons.Script.SConscript import SConsEnvironment
def CloneConfigureEnv(env):
    conf_env = env.Clone()
    conf_env.Replace(LIBS=[lib for lib in env["LIBS"] if lib != "ln" and not "libtom" in str(lib)])
    return conf_env
SConsEnvironment.CloneConfigureEnv = CloneConfigureEnv

# https://github.com/SCons/scons/wiki/SconsRecipes
# inspired by https://stackoverflow.com/questions/1762044/how-to-do-an-out-of-source-build-with-scons
# https://bitbucket.org/scons/scons/wiki/GenerateConfig

env = Environment(
    LN_GENERATE="$PYTHON %s" % File("#python/links_and_nodes_base/ln_generate"),
    DATADIR="$PREFIX/share",
    CPPPATH="$DESTDIR$PREFIX/include",
    LIBPATH=[ "$DESTDIR$PREFIX/lib64", "$DESTDIR$PREFIX/lib" ],
    tools=["default", sphinx_build_tool]
)
ln_scons.update_env(env)

AddOption(
    "--crosscompiling",
    action="store_true",
    help="we are cross compiling. do not try to load/run generated binaries",
    default=False
)
AddOption(
    '--without-debug',
    action='store_true',
    help='build without debug information',
    default=False
)
AddOption(
    '--without-opt',
    action='store_true',
    help='build without optimizing',
    default=False
)
AddOption(
    '--without-lnrecorder',
    action='store_true',
    help='do not build lnrecorder',
    default=False
)

AddOption(
    '--without-daemon-authentication',
    dest="no_daemon_auth",
    action="store_true",
    default=False,
    help="do not use libtomcrypt DSA to do daemon<->manager authentication checks"
)
AddOption(
    "--use-private-libtomcrypt",
    dest="use_private_libtomcrypt",
    action="store_true",
    default=False,
    help="compile & use tomcrypt distributed with this project"
)
AddOption(
    '--python',
    dest="PYTHON",
    type="string",
    metavar="INTERPRETER",
    help="python interpreter to use for ln_generate and to build LN extension for. default is to search path for `python3` then `python`"
)
AddOption(
    '--write-env',
    action="store_true",
    help="place an `ln_env.sh` file below $PREFIX which sets $LD_LIBRARY_PATH, $PYTHONPATH and $PATH"
)
ln_scons.env_lines = []

#todo: use platform independent flags/mechanisms to achieve those:
if not GetOption('without_debug'):
    env.Append(
        CFLAGS="-g",
        CXXFLAGS="-g",
    )
if GetOption('without_opt'):
    env.Append(
        CFLAGS="-O0",
        CXXFLAGS="-O0",
    )
env.Append(
    CFLAGS="-Wall",
    CXXFLAGS=["-Wall"],
    ENV=dict(
        PYTHONPATH=Dir("#python"),
    )
)

# decide python interpreter to use to start helper python scripts like ln_generate and to compile python-extension against
env["PYTHON"] = GetOption("PYTHON")
if env["PYTHON"] is None:
    for test in "python3", "python":
        result = env.popen(["which", test]).strip()
        if result:
            env["PYTHON"] = test
            break
if not env["PYTHON"]:
    print("Error: can't find python interpreter!")
    print("on debian install package `python` or try to use the `--python=/path/to/my/python` option.")
    Exit(1)
print("using python: %s" % env["PYTHON"])

#if env['PLATFORM'] == "posix":
#    env.Append(CPPDEFINES="__LINUX__")
    
env.pass_environment("DLRRM_HOST_PLATFORM")
env.SetDefault(DLRRM_HOST_PLATFORM="unknown-" + env['PLATFORM'])
if GetOption('from_env'):
    env["ENV"] = os.environ

build_dir = GetOption('build')

import SCons
SConsignFile(os.path.join(build_dir, ".sconsign.%s" % (SCons.__version__)))

if not GetOption("help") and not GetOption("clean"):
    conf_env = env.Clone() # normal clone
    conf = conf_env.Configure()
    if conf.CheckCHeader("linux/version.h"):
        env.Append(CPPDEFINES="__LINUX__")
        env.set_stdflag = lambda std: "-std=%s" % std
    elif conf.CheckCHeader("sys/neutrino.h"):
        env.Append(CPPDEFINES="__QNX__")
        if "-Y" in env["CXXFLAGS"]:
            i = env["CXXFLAGS"].index("-Y")
            libcxx = env["CXXFLAGS"][i + 1]
            env.Append(LINKFLAGS=["-Y", libcxx])
        env.set_stdflag = lambda std: "-Wc,-std=%s" % std
        env.need_to_link_rt = False
        env.need_to_link_socket = True
        env.Append(LIBS=["socket"])
    elif conf.CheckCHeader("windows.h"):
        env.Append(CPPDEFINES=["__WIN32__", "USE_OWN_PROCESS_SHARED_IPC"])
        sources += Glob("src/*.cpp")
    else:
        print("error: could not detect your target's system type!")
        Exit(1)
    if conf.CheckFunc("geteuid", header="#include <unistd.h>\n#include <sys/types.h>\n"):
        env.Append(CPPDEFINES="HAVE_GETEUID")
    elif conf.CheckFunc("getuid", header="#include <unistd.h>\n"):
        env.Append(CPPDEFINES="HAVE_GETUID")

    if conf.CheckDeclaration("pthread_mutexattr_setrobust", includes="#include <pthread.h>\n"):
        env.Append(CPPDEFINES="HAVE_PTHREAD_MUTEXATTR_SETROBUST")

    if conf.CheckDeclaration("pthread_mutex_consistent", includes="#include <pthread.h>\n"):
        env.Append(CPPDEFINES="HAVE_PTHREAD_MUTEX_CONSISTENT")

    no_daemon_auth = GetOption("no_daemon_auth")
    if no_daemon_auth:
        env.Append(CFLAGS='-DNO_DAEMON_AUTH')
    elif GetOption("use_private_libtomcrypt"):
        env.Append(CPPPATH=[Dir("external/libtomcrypt/src/headers").abspath])
        env.Append(LIBPATH=[Dir("#%s/external/libtomcrypt" % build_dir).abspath])
    elif not conf.CheckCHeader("tomcrypt.h"):
        print("please install libtomcrypt-dev\n"
              "or use the --without-daemon-authentication option to disable dsa_dss1 check\n"
              "or use the --use-private-libtomcrypt to use tomcript distributed with LN")
        Exit(1)
    if not no_daemon_auth:
        ltm_flags = ["-DLTM_DESC", "-DUSE_LTM" ]
        conf_env.Append(CFLAGS=ltm_flags)
        if GetOption("use_private_libtomcrypt"):
            env.libtomcrypt = SConscript("external/SConscript", variant_dir=build_dir + "/external", duplicate=0, exports=dict(env=env))
            env.Append(LIBS=[env.libtomcrypt[0], env.libtomcrypt[1]])
            ltm_flags.append('-DHAVE_CRYPT_MP_INIT')
        else:
            env.Append(LIBS=["tomcrypt"])
            conf_env.Append(LIBS=["tomcrypt"])
            if conf.CheckFunc("crypt_mp_init", language="C"):
                ltm_flags.append('-DHAVE_CRYPT_MP_INIT')
            else:
                print("you have an older version of libtomcrypt installed. will try to compensate for that...")
        env.Append(CFLAGS=ltm_flags)
        env.Append(CXXFLAGS=ltm_flags)
    conf.Finish()

client_env = env.Clone()
ln_scons.link_to_libln(client_env)

runtimes = [
    "ln_daemon",
    "file_services",
]
if not GetOption('without_lnrecorder'):
    runtimes.append("lnrecorder")

runtime_program_env = client_env.Clone()
if "__LINUX__" in runtime_program_env.get("CPPDEFINES", []):
    # Runtime executables are packaged into manylinux wheels and rewritten by
    # auditwheel/patchelf. Build them as PIE so repaired executables remain
    # loadable ELF DYN artifacts instead of fragile non-PIE EXEC artifacts.
    runtime_program_env.Append(
        CFLAGS=["-fPIE"],
        CXXFLAGS=["-fPIE"],
        LINKFLAGS=["-pie"],
    )
runtime_program_subdirs = set(os.path.join("ln_runtime", runtime) for runtime in runtimes)

subdirs = [
    "libln",
    ("ln_runtime", runtimes),
    "python",
    "documentation"
]

base_env = env
for subdir in ln_scons.flatten_subdirs(subdirs):
    variant_dir = build_dir + "/" + subdir
    if subdir == "libln":
        env = base_env
    elif subdir in runtime_program_subdirs:
        env = runtime_program_env
    else:
        env = client_env
    SConscript(os.path.join(subdir, "SConscript"), variant_dir=variant_dir, duplicate=0, exports=dict(env=env))


if GetOption("write_env"):
    def write_ln_env(target, source, env):
        prefix = env.subst("$PREFIX")

        def relocatable_line(line):
            return line.replace(prefix, "$LN_PREFIX")

        lines = [
            "#!/bin/sh",
            "LN_PREFIX_DEFAULT='%s'" % prefix,
            'if [ -n "${BASH_SOURCE:-}" ]; then',
            '    LN_ENV_SOURCE=$BASH_SOURCE',
            '    LN_ENV_FILE=$(readlink -f -- "$LN_ENV_SOURCE" 2>/dev/null || printf "%s\\n" "$LN_ENV_SOURCE")',
            '    LN_PREFIX=$(CDPATH= cd -- "$(dirname -- "$LN_ENV_FILE")" && pwd -P)',
            "else",
            '    LN_PREFIX=$LN_PREFIX_DEFAULT',
            "fi",
            "export LN_PREFIX",
            'export LD_LIBRARY_PATH="$LN_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"',
            'export PATH="$LN_PREFIX/bin${PATH:+:$PATH}"'
        ] + [relocatable_line(line) for line in ln_scons.env_lines] + [
            "unset LN_ENV_FILE",
            "unset LN_ENV_SOURCE",
            "unset LN_PREFIX_DEFAULT"
        ]
        with open(str(target[0]), "w") as fp:
            fp.write("\n".join(lines) + "\n")
    write_env = env.Command(target="$PREFIX/ln_env.sh", source=[], action=write_ln_env)
    env.Alias("install-data", write_env)

# those two in combination with the _ln.so install cause this cycle message...
env.Alias("install-bin", "$PREFIX/bin")
env.Alias("install-headers", "$PREFIX/include")
env.Alias("install-lib", "$PREFIX/lib")
env.Alias("install-data", "$DATADIR")
env.Alias('install', ["install-bin", "install-lib", "install-data", "install-headers"])
