#!/usr/bin/env python3
# Copyright (C) 2025 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Helper tool to check various metadata (e.g. licensing) in source files."""

import argparse
from pathlib import Path
import re
import sys

import util


_FILE_HEADER_RE = re.compile(
    r"""# Copyright \(C\) 20[0-9]{2} The Android Open Source Project
#
# Licensed under the Apache License, Version 2\.0 \(the "License"\);
# you may not use this file except in compliance with the License\.
# You may obtain a copy of the License at
#
#      http://www\.apache\.org/licenses/LICENSE-2\.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.
# See the License for the specific language governing permissions and
# limitations under the License\.
"""
)


def check_license(path: Path, lines: list[str]) -> bool:
    """Check license header."""
    # Enforce licensing on configs & scripts.
    if not (
        path.suffix in (".bash", ".cfg", ".ini", ".py", ".toml")
        or lines[0] in ("#!/bin/bash", "#!/bin/sh", "#!/usr/bin/env python3")
    ):
        return True

    # Extract the file header.
    header_lines = []
    for line in lines:
        if line.startswith("#"):
            header_lines.append(line)
        else:
            break
    if not header_lines:
        print(
            f"error: {path.relative_to(util.TOPDIR)}: "
            "missing file header (copyright+licensing)",
            file=sys.stderr,
        )
        return False

    # Skip the shebang.
    if header_lines[0].startswith("#!"):
        header_lines.pop(0)

    # If this file is imported into the tree, then leave it be.
    if header_lines[0] == "# DO NOT EDIT THIS FILE":
        return True

    header = "".join(f"{x}\n" for x in header_lines)
    if not _FILE_HEADER_RE.match(header):
        print(
            f"error: {path.relative_to(util.TOPDIR)}: "
            "file header incorrectly formatted",
            file=sys.stderr,
        )
        print(
            "".join(f"> {x}\n" for x in header_lines), end="", file=sys.stderr
        )
        return False

    return True


def check_path(opts: argparse.Namespace, path: Path) -> bool:
    """Check a single path."""
    try:
        data = path.read_text(encoding="utf-8")
    except FileNotFoundError:
        return True
    lines = data.splitlines()
    # NB: Use list comprehension and not a generator so we run all the checks.
    return all(
        [
            check_license(path, lines),
        ]
    )


def check_paths(opts: argparse.Namespace, paths: list[Path]) -> bool:
    """Check all the paths."""
    # NB: Use list comprehension and not a generator so we check all paths.
    return all([check_path(opts, x) for x in paths])  # noqa: C419


def find_files(opts: argparse.Namespace) -> list[Path]:
    """Find all the files in the source tree."""
    result = util.run(
        opts,
        ["git", "ls-tree", "-r", "-z", "--name-only", "HEAD"],
        cwd=util.TOPDIR,
        capture_output=True,
        encoding="utf-8",
    )
    return [util.TOPDIR / x for x in result.stdout.split("\0")[:-1]]


def get_parser() -> argparse.ArgumentParser:
    """Get a CLI parser."""
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "-n",
        "--dry-run",
        dest="dryrun",
        action="store_true",
        help="show everything that would be done",
    )
    parser.add_argument(
        "paths",
        nargs="*",
        help="the paths to scan",
    )
    return parser


def main(argv: list[str]) -> int:
    """The main func!"""
    parser = get_parser()
    opts = parser.parse_args(argv)

    paths = opts.paths
    if not opts.paths:
        paths = find_files(opts)

    return 0 if check_paths(opts, paths) else 1


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
