#!/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.

from __future__ import annotations

import sys
import typing as t
from collections.abc import Generator, Mapping
from contextlib import contextmanager
from pathlib import Path
from typing import IO

from decopy.common.core_types import LicenseRegistry, Options
from decopy.common.log import logger
from decopy.dep5doc.explain_renderer import ExplainRenderer
from decopy.dep5doc.renderer import Renderer
from decopy.fstree.tree import RootInfo
from decopy.grouping.group import TallyTracker
from decopy.grouping.group_contracts import GroupProtocol

if t.TYPE_CHECKING:
    from decopy.dep5doc.copyright_contracts import CopyrightProtocol
    from decopy.dep5doc.delta import DeltaReport


@contextmanager
def open_output_file(filename: str) -> Generator[IO[str], None, None]:
    if filename:
        with Path(filename).open("wt", encoding="utf-8") as f:
            yield f
    else:
        yield sys.stdout


def _tally_partial_parents(
    filetree: RootInfo, options: Options, tracker: TallyTracker
) -> None:
    """In a scoped run, mark the requested files' parents as tallied.

    This avoids emitting a 'Files: *' paragraph that would claim the whole tree.
    """
    if not options.scoped_to_subset:
        return
    for item in options.files:
        parent = filetree[item].parent
        if not parent:
            continue
        tracker.tally(parent)


def generate_explain(report: DeltaReport, options: Options) -> None:
    """Render the staleness report to stdout (the report destination is fixed)."""
    print(ExplainRenderer().explain(report, options), end="")


def generate_output(
    groups: Mapping[t.Any, GroupProtocol],
    filetree: RootInfo,
    copyright_: CopyrightProtocol | None,
    options: Options,
    renderer: Renderer,
) -> None:
    # The Copyright owns the license registry it populated while reading the
    # existing file; reuse it so stored license texts reach the stanzas.
    registry = copyright_.registry if copyright_ is not None else LicenseRegistry()

    # Track the licenses that are in use
    licenses: set[str] = set()

    # Shared across all groups so a later, more specific group does not
    # re-wildcard an ancestor an earlier group already covered.
    tracker = TallyTracker()

    def log_generated(label: str, text: str) -> None:
        # Only when writing to a real file: on stdout the text is already
        # visible, so echoing it into the log stream would just duplicate it.
        if options.output:
            logger.debug("Generated %s:\n%s", label, text)

    with open_output_file(options.output) as f:
        # Print header
        header = renderer.header(copyright_, options)
        print(header, file=f)
        log_generated("header", header)

        _tally_partial_parents(filetree, options, tracker)

        # Print files paragraphs
        for _, group in sorted(groups.items(), key=lambda i: i[1].sort_key(options)):
            if not group.copyright_block_valid():
                continue

            licenses.update(group.licenses.keys())

            if options.glob:
                file_patterns = group.files.get_patterns(tracker)
            else:
                # When called with --no-glob, only list the file names
                file_patterns = group.files.sorted_members()

            paragraph = renderer.file_block(group, options, file_patterns)

            print(file=f)
            print(paragraph, file=f)

            log_generated("group", paragraph)

        # Print license paragraphs
        for key in sorted(licenses):
            paragraph = renderer.license_block(registry.get(key), options)

            print(file=f)
            print(paragraph, file=f)

            log_generated("license block", paragraph)
