__all__ = ("UnknownData", "FixLibtoolArchivesTrigger")

import re
from functools import partial
from os.path import basename, dirname

from snakeoil.currying import post_curry
from snakeoil.sequences import stable_unique

from ..exceptions import PkgcoreException
from ..merge import triggers

x11_sub = post_curry(partial(re.compile(r"X11R6/+lib").sub, "lib"), 1)
local_sub = post_curry(partial(re.compile(r"local/+lib").sub, "lib"), 1)
pkgconfig1_sub = post_curry(
    partial(re.compile(r"usr/+lib[^/]*/+pkgconfig/+\.\./\.\.").sub, "usr"), 1
)
pkgconfig2_sub = post_curry(
    partial(re.compile(r"usr/+lib[^/]*/+pkgconfig/+\.\.").sub, "usr"), 1
)
flags_match = re.compile(
    r"-(?:mt|mthreads|kthread|Kthread|pthread|pthreads|-thread-safe|threads)"
).match

template = """# %(file)s - a libtool library file
# Generated by ltmain.sh - GNU libtool 1.5.10 (1.1220.2.130 2004/09/19 12:13:49)
%(content)s
"""


class UnknownData(PkgcoreException):
    def __init__(self, line, token=None):
        self.token, self.line = token, line

    def __str__(self):
        s = f"we don't know how to parse line {self.line!r}"
        if self.token:
            s += f" specifically token {self.token!r}"
        return s


# libtiff.la - a libtool library file
# Generated by ltmain.sh - GNU libtool 1.5.10 (1.1220.2.130 2004/09/19 12:13:49)
def parse_lafile(handle):
    d = {}
    for line in handle:
        line = line.rstrip()
        if not line or line.startswith("#"):
            continue
        try:
            key, val = line.split("=", 1)
        except ValueError:
            raise UnknownData(line)
        # ' is specifically forced by libtdl implementation
        if len(val) >= 2 and val[0] == val[-1] and val[0] == "'":
            val = val[1:-1]
        d[key] = val
    return d


def rewrite_lafile(handle, filename):
    data = parse_lafile(handle)
    raw_dep_libs = data.get("dependency_libs", False)
    if not raw_dep_libs:
        return False, None

    original_libs = raw_dep_libs.split()
    rpaths, libs, libladirs, inherited_flags = [], [], [], []
    original_inherited_flags = data.get("inherited_linker_flags", [])

    for item in stable_unique(original_libs):
        if item.startswith("-l"):
            libs.append(item)
        elif item.endswith(".la"):
            base = basename(item)
            if base.startswith("lib"):
                # convert to -l; punt .la, and 'lib' prefix
                libs.append("-l" + base[3:-3])
                libladirs.append("-L" + dirname(item))
            else:
                libs.append(item)
        elif item.startswith("-L"):
            # this is heinous, but is what the script did.
            item = x11_sub(item)
            item = local_sub(item)
            item = pkgconfig1_sub(item)
            item = pkgconfig2_sub(item)
            libladirs.append(item)
        elif item.startswith("-R"):
            rpaths.append(item)
        elif flags_match(item):
            if inherited_flags:
                inherited_flags.append(item)
            else:
                libs.append(item)
        else:
            raise UnknownData(raw_dep_libs, item)
    libs = stable_unique(rpaths + libladirs + libs)
    inherited_flags = stable_unique(inherited_flags)
    if libs == original_libs and inherited_flags == original_inherited_flags:
        return False, None

    # must be prefixed with a space
    data["dependency_libs"] = " " + (" ".join(libs))
    if inherited_flags:
        # must be prefixed with a space
        data["inherited_flags"] = " " + (" ".join(inherited_flags))
    content = "\n".join(f"{k}='{v}'" for k, v in sorted(data.items()))
    return True, template % {"content": content, "file": filename}


def fix_fsobject(location):
    from ..fs import fs, livefs

    for obj in livefs.iter_scan(location):
        if not fs.isreg(obj) or not obj.basename.endswith(".la"):
            continue

        with open(obj.location, "r") as f:
            updated, content = rewrite_lafile(f, obj.basename)
        if updated:
            with open(obj.location, "w") as f:
                f.write(content)


class FixLibtoolArchivesTrigger(triggers.base):
    required_csets = ("install",)
    _engine_types = triggers.INSTALLING_MODES
    _hooks = ("pre_merge",)

    def trigger(self, engine, cset):
        updates = []
        for obj in cset.iterfiles():
            if not obj.basename.endswith(".la"):
                continue
            handle = obj.data.text_fileobj()
            updated, content = rewrite_lafile(handle, obj.basename)
            if not updated:
                continue

            engine.observer.info(f"rewriting libtool archive {obj.location}")
            source = engine.get_writable_fsobj(obj, empty=True)
            source.text_fileobj(True).write(content)
            # force chksums to be regenerated
            updates.append(obj.change_attributes(data=source, chksums=None))
        cset.update(updates)
