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

"""Command line option handling."""

from __future__ import annotations

import argparse
import collections.abc as abc
import logging
import os
import pathlib
import typing as t

from ._regex import rex
from .core_types import Options
from .log import logger


class Defaults:
    exclude_file_regex: str = r"""
    # Ignore general backup files
    ~$|
    # Ignore emacs recovery files
    \.\#.*$|
    # Ignore vi swap files
    \..*\.swp$|
    # Ignore baz-style junk files or directories
    ^,,|
    # File-names that should be ignored (never directories)
    ^(?:DEADJOE|\.cvsignore|\.arch-inventory|\.bzrignore|\.gitignore)$|
    # File or directory names that should be ignored
    ^(?:CVS|RCS|\.deps|\{arch\}|\.arch-ids|\.svn|\.hg|_darcs|\.git|
    \.shelf|_MTN|\.bzr(?:\.backup|tags)?)$
    """

    exclude_directory_regex: str = r"""
    # Ignore baz-style junk files or directories
    ^,,|
    # File or directory names that should be ignored
    ^(?:CVS|RCS|\.deps|\{arch\}|\.arch-ids|\.svn|\.hg|_darcs|\.git|
    \.pc|\.shelf|_MTN|\.bzr(?:\.backup|tags)?)$|
    # Python compiled files
    ^__pycache__$
    """

    # Exclude debian/copyright and debian/changelog by default
    exclude_special_regex: str = r"""
    ^debian/(copyright|changelog)$
    """

    # Empty exclude regexp, to be overridden by users
    exclude_fullname_regex: str = r"""
    ^$
    """

    # Top directory for processing files
    root: str = "."

    # Files/dirs to parse
    files: t.ClassVar[list[str]] = []

    # Mode to work on:
    # partial: only process the files/dirs specified and generate the output
    #          for this subset
    # full: (default) parse the files/dirs specified, and generate the
    #       copyright information for the whole source tree
    mode: str = "full"

    # copyright 1.0 file, relative to the package dir
    copyright_file: str = "debian/copyright"

    # group by license or by copyright
    group_by: str = "license"

    # Split groups on sub dirs containing licenses
    split_on_license: bool = True

    # Split the debian/* file paragraph
    split_debian: bool = True

    # Treat all files as text
    text: bool = False

    # Try to use glob patterns in the File: output
    glob: bool = True

    # Progress bar
    progress: bool = True

    # Number of parallel jobs (0 for auto)
    jobs: int = 0

    # output filename, empty for stdout
    output: str = ""

    # output SPDX licence identifiers instead of DEP-5 names
    spdx: bool = False

    # Expand archive files (tar, zip, …) as virtual file-systems
    expand_archives: bool = True
    max_archive_depth: int = 1
    max_archive_member_size: int = 10 * 1024 * 1024


def _add_boolean_argument(
    parser: argparse.ArgumentParser,
    name: str,
    *,
    default: bool,
    dest: str | None = None,
) -> None:
    """Helper to add options --name --no-name and set the default"""
    if dest is None:
        dest = name.replace("-", "_")
    _ = parser.add_argument(f"--{name}", action="store_true", dest=dest)
    _ = parser.add_argument(f"--no-{name}", action="store_false", dest=dest)
    parser.set_defaults(**{dest: default})


def process_options(args: abc.Sequence[str] | None = None) -> Options:
    def _filename(arg: str) -> str:
        if not pathlib.Path(arg).exists():
            msg = f"{arg} does not exist"
            raise argparse.ArgumentTypeError(msg)
        return arg

    logger_format = "[%(levelname)s] %(message)s"

    parser: argparse.ArgumentParser = argparse.ArgumentParser(
        description="License checker and copyright dep5 helper",
        fromfile_prefix_chars="@",
    )
    _ = parser.add_argument("-X", "--exclude", default=Defaults.exclude_fullname_regex)

    _ = parser.add_argument(
        "--mode",
        choices=["full", "partial"],
        default=Defaults.mode,
    )

    _ = parser.add_argument("--copyright-file", default=Defaults.copyright_file)

    _ = parser.add_argument("--debug", action="store_true")
    _ = parser.add_argument("-v", "--verbose", action="store_true")
    _ = parser.add_argument("-q", "--quiet", action="store_true")

    _ = parser.add_argument("-a", "--text", action="store_true")

    _ = parser.add_argument(
        "--group-by",
        choices=["license", "copyright"],
        default=Defaults.group_by,
    )
    _add_boolean_argument(parser, "split-on-license", default=Defaults.split_on_license)
    _add_boolean_argument(parser, "split-debian", default=Defaults.split_debian)

    _add_boolean_argument(parser, "glob", default=Defaults.glob)

    _add_boolean_argument(parser, "progress", default=Defaults.progress)
    _add_boolean_argument(parser, "spdx", default=Defaults.spdx)
    _add_boolean_argument(parser, "expand-archives", default=Defaults.expand_archives)
    _ = parser.add_argument(
        "--max-archive-depth",
        type=int,
        default=Defaults.max_archive_depth,
        help="Max depth of nested archive expansion (default: 1)",
    )
    _ = parser.add_argument(
        "--max-archive-member-size",
        type=int,
        default=Defaults.max_archive_member_size,
        help="Skip archive members larger than this many bytes (default: 10 MB)",
    )
    _ = parser.add_argument(
        "--jobs",
        "-j",
        type=int,
        default=Defaults.jobs,
        help="Number of jobs to run in parallel (0 for auto)",
    )

    _ = parser.add_argument("-o", "--output", default=Defaults.output)

    _ = parser.add_argument("--root", default=Defaults.root, type=_filename)

    _ = parser.add_argument("files", nargs="*", default=Defaults.files, type=_filename)

    options = parser.parse_args(args)

    if options.debug:
        logger_level = logging.DEBUG
    elif options.verbose:
        logger_level = logging.INFO
    elif options.quiet:
        logger_level = logging.ERROR
    else:
        logger_level = logging.WARNING

    options.exclude_fullname_re = rex.compile(
        options.exclude,
        rex.IGNORECASE | rex.VERBOSE,
    )
    options.exclude_special_re = rex.compile(
        Defaults.exclude_special_regex,
        rex.IGNORECASE | rex.VERBOSE,
    )
    options.exclude_file_re = rex.compile(Defaults.exclude_file_regex, rex.VERBOSE)
    options.exclude_directory_re = rex.compile(
        Defaults.exclude_directory_regex,
        rex.VERBOSE,
    )

    # - can also be used to specify the stdout
    if options.output == "-":
        options.output = ""

    # Force an absolute path for options.root, and sanitize the user option
    # (see #855778, for problem caused by a trailing /)
    options.root = str(pathlib.Path(options.root).absolute())
    # Transform positional arguments into paths that are relative to the root
    # path
    options.files = [os.path.relpath(i, options.root) for i in options.files]

    logging.basicConfig(format=logger_format)
    logger.setLevel(logger_level)

    return Options(
        copyright_file=options.copyright_file,
        debug=options.debug,
        exclude_directory_re=options.exclude_directory_re,
        exclude_file_re=options.exclude_file_re,
        exclude_fullname_re=options.exclude_fullname_re,
        exclude_special_re=options.exclude_special_re,
        files=options.files,
        glob=options.glob,
        group_by=options.group_by,
        jobs=options.jobs,
        mode=options.mode,
        output=options.output,
        progress=options.progress,
        root=options.root,
        split_debian=options.split_debian,
        split_on_license=options.split_on_license,
        spdx=options.spdx,
        text=options.text,
        expand_archives=options.expand_archives,
        max_archive_depth=options.max_archive_depth,
        max_archive_member_size=options.max_archive_member_size,
    )
