#!/usr/bin/env python3

import difflib
import os
import re
import shutil
import subprocess
import sys

from check_helpers import CHECK_SYMBOL_LIBS

os.environ["LC_ALL"] = "C"  # otherwise 'nm' prints in wrong order

srcdir = sys.argv[1]
base_srcdir = sys.argv[2]
builddir = sys.argv[3]

IGNORED_SYMBOLS = [
    "_fini",
    "_init",
    "_fdata",
    "_ftext",
    "_fbss",
    "__bss_start",
    "__bss_start__",
    "__bss_end__",
    "_edata",
    "_end",
    "_bss_end__",
    "__end__",
    "__gcov_.*",
    "llvm_.*",
    "flush_fn_list",
    "writeout_fn_list",
    "mangle_path",
    "lprofDirMode",
    "reset_fn_list",
]

# Rust
IGNORED_SYMBOLS += [
    "rust_eh_personality",
    "_.*3std9panicking11EMPTY_PANIC.*",  # 'std.*::panicking::EMPTY_PANIC::.*'
    "_.*3std3sys3pal4unix4args3imp15ARGV_INIT_ARRAY.*",  # 'std.*::sys::pal::unix::args::imp::ARGV_INIT_ARRAY::.*'
    "_.*3std3sys4args4unix3imp15ARGV_INIT_ARRAY.*",  # std.*::sys::args::unix::imp::ARGV_INIT_ARRAY::.*
    "_.*17compiler_builtins.*",  # 'compiler_builtins.*::.*'
    ".*__rustc.*",  # '.*__rustc.*' # Eg. _RNvCsgSLETaxrkfn_7___rustc17___rust_probestack
    '_hb_harfrust_.*_rs',
]

IGNORED_SYMBOLS = "|".join(IGNORED_SYMBOLS)

nm = os.getenv("NM", shutil.which("nm"))
if not nm:
    print("check-symbols.py: 'nm' not found; skipping test")
    sys.exit(77)

tested = False
stat = 0

for soname in CHECK_SYMBOL_LIBS:
    for suffix in ["so", "dylib"]:
        so = os.path.join(builddir, "lib%s.%s" % (soname, suffix))
        if not os.path.exists(so):
            continue

        # On macOS, C symbols are prefixed with _
        symprefix = "_" if suffix == "dylib" else ""

        EXPORTED_SYMBOLS = [
            s.split()[2]
            for s in re.findall(
                r"^.+ [BCDGIRSTWu] .+$",
                subprocess.check_output(nm.split() + [so]).decode("utf-8"),
                re.MULTILINE,
            )
            if not re.match(r".* %s(%s)\b" % (symprefix, IGNORED_SYMBOLS), s)
        ]

        prefix = (
            (symprefix + os.path.basename(so))
            .replace("libharfbuzz", "hb")
            .replace("-", "_")
            .split(".")[0]
        )

        print("Checking that %s does not expose internal symbols" % so)
        suspicious_symbols = [
            x for x in EXPORTED_SYMBOLS if not re.match(r"^%s(_|$)" % prefix, x)
        ]
        # libharfbuzz-subset also exports hb_subset_depend_* when depend_api is enabled
        if soname == "harfbuzz-subset":
            suspicious_symbols = [x for x in suspicious_symbols if not re.match(r"^%shb_subset_depend_" % symprefix, x)]
        if suspicious_symbols:
            print("Ouch, internal symbols exposed:", suspicious_symbols)
            stat = 1

        def_path = os.path.join(builddir, soname + ".def")
        if not os.path.exists(def_path):
            print("'%s' not found; skipping" % def_path)
        else:
            print("Checking that %s has the same symbol list as %s" % (so, def_path))
            with open(def_path, "r", encoding="utf-8") as f:
                def_file = f.read()
            diff_result = list(
                difflib.context_diff(
                    def_file.splitlines(),
                    ["EXPORTS"]
                    + [re.sub("^%shb" % symprefix, "hb", x) for x in EXPORTED_SYMBOLS]
                    +
                    # cheat: copy the last line from the def file!
                    [def_file.splitlines()[-1]],
                )
            )

            if diff_result:
                print("\n".join(diff_result))
                stat = 1

            tested = True

if not tested:
    print("check-symbols.py: no shared libraries found; skipping test")
    sys.exit(77)

sys.exit(stat)
