#!/usr/bin/env python3
#
# Copyright: 2016, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Generic functions used by the parsers."""

from __future__ import annotations

# Local modules
from decopy.licensing.license_expr import license_atoms, parse_dep5_expression
from decopy.licensing.license_parsers import parse_exceptions
from decopy.licensing.license_prefilter import candidate_indices
from decopy.licensing.license_subs import COMMENTS_SUBS
from decopy.licensing.res import COMPILED_RES, LICENSES_RES


def clean_comments(text: str) -> str:
    # replace it's way cheaper than re.sub
    for comment in (
        # C
        "/*",
        "*/",
        # Lisp
        ";;",
        # (HT|X)ML
        "<!--",
        "-->",
    ):
        text = text.replace(comment, " ")
    for comment_sub in COMMENTS_SUBS:
        text = comment_sub.pattern.sub(comment_sub.repl, text)

    return text


def _atoms_of(name: str) -> list[str]:
    """The atomic licence keys of a detected name.

    A rule may resolve to a compound expression (e.g. an SPDX-License-Identifier
    "MIT or Apache-2.0" -> "Expat or Apache-2"); split it into its atoms so each
    gets its own standalone License stanza. A `with` exception stays one atom
    (license_atoms keys it as the full "NAME with LABEL exception"). A plain name
    that is not an expression is returned unchanged.
    """
    try:
        return license_atoms(parse_dep5_expression(name))
    except ValueError:
        return [name]


def find_licenses(text: str) -> list[str]:
    """Scan the text for presence of any of the supported licenses.

    Returns:
        A list of the atomic license ids found in the text.
    """
    licenses: dict[str, str] = {}
    suppressed: set[str] = set()

    # Only run rules whose necessary literal is present: the rest would fail
    # their search anyway, so the result is identical. Iterate the candidate
    # indices in ascending order (same order as the full rule table) to keep the
    # family dedup/first-seen behaviour intact. None -> prefilter unavailable,
    # run every rule.
    candidates = candidate_indices(text)
    indices = sorted(candidates) if candidates is not None else range(len(LICENSES_RES))

    for i in indices:
        license_re = LICENSES_RES[i]
        # Keep the license name in this frame for easier debugging
        license_: str | None = license_re.license
        family = license_re.license

        if family in licenses or family in suppressed:
            continue
        match = COMPILED_RES[i].search(text)
        if not match:
            continue

        if license_re.get_detail:
            license_ = license_re.get_detail(text, match, family)

        # A rule may suppress its whole family (return None) when it recognises
        # an aggregate that should be reported under a single id from another
        # family (e.g. Net-SNMP). No later rule for this family is consulted.
        if license_ is None:
            suppressed.add(family)
            continue

        # Apply the matched rule's exception SUBS (carried as row data in res.py).
        if license_re.exception_subs is not None:
            license_ = parse_exceptions(text, license_, license_re.exception_subs)

        # A matcher may decline (return "") when its anchor fired but the text
        # lacks the distinctive content; keep looking rather than record a blank.
        if license_:
            licenses[family] = license_

    # Different families are keyed separately, so two of them can resolve to the
    # same DEP-5 name (e.g. a CC-BY text matcher and an embedded
    # SPDX-License-Identifier tag both yielding CC-BY-4). A rule may also resolve
    # to a compound expression; split each name into atoms so every member gets
    # its own stanza. The atoms are already canonical, so identical ones are the
    # same licence; collapse them, keeping first-seen order, so a disjunction
    # never repeats a member.
    return list(
        dict.fromkeys(atom for name in licenses.values() for atom in _atoms_of(name))
    )
