import os
import re
import shutil
from datetime import datetime
from SCons.Platform.virtualenv import ImportVirtualenv
from SCons.Errors import UserError


EnsurePythonVersion(3, 7)
EnsureSConsVersion(3, 0)


def extract_version():
    # Hold my beer...
    gl = {}
    exec(open("pythonscript/pandemonium/_version.py").read(), gl)
    return gl["__version__"]


def pandemonium_binary_converter(val, env):
    file = File(val)
    if file.exists():
        # Note here `env["pandemonium_binary_download_version"]` is not defined, this is ok given
        # this variable shouldn't be needed if Pandemonium doesn't have to be downloaded
        return file
    # Provided value is version information with format <major>.<minor>.<patch>[-<extra>]
    match = re.match(r"^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-(\w+))?$", val)
    if match:
        major, minor, patch, extra = match.groups()
    else:
        raise UserError(
            f"`{val}` is neither an existing file nor a valid <major>.<minor>.<patch>[-<extra>] Pandemonium version format"
        )
    env["pandemonium_binary_download_version"] = (major, minor, patch, extra or "stable")
    # `pandemonium_binary` is set to None to indicate it should be downloaded
    return None


vars = Variables("custom.py")
vars.Add(
    EnumVariable(
        "platform",
        "Target platform",
        "",
        allowed_values=("x11-64", "x11-32", "windows-64", "windows-32", "osx-64"),
    )
)
vars.Add("pytest_args", "Pytest arguments passed to tests functions", "")
vars.Add(
    "pandemonium_args", "Additional arguments passed to pandemonium binary when running tests&examples", ""
)
vars.Add("release_suffix", "Suffix to add to the release archive", extract_version())
vars.Add(
    "pandemonium_binary",
    "Path to Pandemonium binary or version of Pandemonium to use",
    default="3.2.2",
    converter=pandemonium_binary_converter,
)
vars.Add("pandemonium_headers", "Path to Pandemonium GDnative headers", "")
vars.Add("debugger", "Run test with a debugger", "")
vars.Add(BoolVariable("debug", "Compile with debug symbols", False))
vars.Add(BoolVariable("headless", "Run tests in headless mode", False))
vars.Add(BoolVariable("compressed_stdlib", "Compress Python std lib as a zip to save space", True))
vars.Add(
    BoolVariable(
        "bindings_generate_sample",
        "Generate only a subset of the bindings (faster build time)",
        False,
    )
)
vars.Add("CC", "C compiler")
vars.Add("CFLAGS", "Custom flags for the C compiler")
vars.Add("LINK", "linker")
vars.Add("LINKFLAGS", "Custom flags for the linker")
vars.Add("CPYTHON_CFLAGS", "Custom flags for the C compiler used to compile CPython")
vars.Add("CPYTHON_LINKFLAGS", "Custom flags for the linker used to compile CPython")
vars.Add("OPENSSL_PATH", "Path to the root of openssl installation to link CPython against")
vars.Add(
    "MSVC_VERSION",
    "MSVC version to use (Windows only) -- version num X.Y. Default: highest installed.",
)
vars.Add(
    BoolVariable(
        "MSVC_USE_SCRIPT",
        (
            "Set to True to let SCons find compiler (with MSVC_VERSION and TARGET_ARCH), "
            "False to use cmd.exe env (MSVC_VERSION and TARGET_ARCH will be ignored), "
            "or vcvarsXY.bat script name to use."
        ),
        True,
    )
)


# Set Visual Studio arch according to platform target
vanilla_vars_update = vars.Update


def _patched_vars_update(env, args=None):
    vanilla_vars_update(env, args=None)
    if env["platform"] == "windows-64":
        env["TARGET_ARCH"] = "x86_64"
    elif env["platform"] == "windows-32":
        env["TARGET_ARCH"] = "x86"


vars.Update = _patched_vars_update


env = Environment(
    variables=vars,
    tools=["default", "cython", "symlink", "virtual_target", "download"],
    ENV=os.environ,
    # ENV = {'PATH' : os.environ['PATH']},
)


# Detect compiler
env["CC_IS_MSVC"] = env.get("CC") in ("cl", "cl.exe")
env["CC_IS_GCC"] = "gcc" in env.get("CC")
env["CC_IS_CLANG"] = "clang" in env.get("CC")


Help(vars.GenerateHelpText(env))
# if env["HOST_OS"] == "win32":
#   # Fix ImportVirtualenv raising error if PATH make reference to other drives
#   from SCons.Platform import virtualenv
#   vanilla_IsInVirtualenv = virtualenv.IsInVirtualenv
#   def patched_IsInVirtualenv(path):
#       try:
#           return vanilla_IsInVirtualenv(path)
#       except ValueError:
#           return False
#   virtualenv.IsInVirtualenv = patched_IsInVirtualenv
# ImportVirtualenv(env)


if env["pandemonium_headers"]:
    env["pandemonium_headers"] = Dir(env["pandemonium_headers"])
else:
    env["pandemonium_headers"] = Dir("pandemonium_headers")
env.AppendUnique(CPPPATH=["$pandemonium_headers"])
# TODO: not sure why, but CPPPATH scan result for cython modules change between
# first and subsequent runs of scons (module is considered to no longer depend
# on pandemonium_headers on subsequent run, so the build redone)
SetOption("implicit_cache", 1)


### Save my eyes plz ###

env["ENV"]["TERM"] = os.environ.get("TERM", "")
if env["CC_IS_CLANG"]:
    env.Append(CCFLAGS=["-fcolor-diagnostics"])
if env["CC_IS_GCC"]:
    env.Append(CCFLAGS=["-fdiagnostics-color=always"])


### Default compile flags ###

if not env["CC_IS_MSVC"]:
    if env["debug"]:
        env.Append(CFLAGS=["-g", "-ggdb"])
        env.Append(LINKFLAGS=["-g", "-ggdb"])
    else:
        env.Append(CFLAGS=["-O2"])
else:
    if env["debug"]:
        env.Append(CFLAGS=["/DEBUG:FULL"])
        env.Append(LINKFLAGS=["/DEBUG:FULL"])
    else:
        env.Append(CFLAGS=["/WX", "/W2"])


env["DIST_ROOT"] = Dir(f"build/dist")
env["DIST_PLATFORM"] = Dir(f"{env['DIST_ROOT']}/addons/pythonscript/{env['platform']}")
VariantDir(f"build/{env['platform']}/platforms", f"platforms")
VariantDir(f"build/{env['platform']}/pythonscript", "pythonscript")


### Load sub scons scripts ###


Export(env=env)
SConscript(
    [
        f"build/{env['platform']}/platforms/SConscript",  # Must be kept first
        f"build/{env['platform']}/pythonscript/SConscript",
        "tests/SConscript",
        "examples/SConscript",
    ]
)


### Define default target ###


env.Default(env["DIST_ROOT"])
env.Alias("build", env["DIST_ROOT"])


### Static files added to dist ###


env.VanillaInstallAs(
    target="$DIST_ROOT/pythonscript.gdnlib", source="#/misc/release_pythonscript.gdnlib"
)
env.VanillaInstallAs(
    target="$DIST_ROOT/addons/pythonscript/LICENSE.txt", source="#/misc/release_LICENSE.txt"
)
env.Command(target="$DIST_ROOT/addons/pythonscript/.gdignore", source=None, action=Touch("$TARGET"))
# SCons install on directory doesn't check for file changes
for item in env.Glob("addons/pythonscript_repl/*"):
    env.VanillaInstall(target="$DIST_ROOT/addons/pythonscript_repl", source=item)


### Release archive ###


def generate_release(target, source, env):
    for suffix, format in [(".zip", "zip"), (".tar.bz2", "bztar")]:
        if target[0].name.endswith(suffix):
            base_name = target[0].abspath[: -len(suffix)]
            break
    shutil.make_archive(base_name, format, root_dir=source[0].abspath)


# Zip format doesn't support symlinks that are needed for Linux&macOS
if env["platform"].startswith("windows"):
    release_target = "build/pandemonium-python-${release_suffix}-${platform}.zip"
else:
    release_target = "build/pandemonium-python-${release_suffix}-${platform}.tar.bz2"
release = env.Command(release_target, env["DIST_ROOT"], generate_release)
env.Alias("release", release)
env.AlwaysBuild("release")