#!/usr/bin/python3
# Copyright (c) Turnkey GNU/Linux <admin@turnkeylinux.org>
#
# this file is part of tkldev-detective.
#
# tkldev-detective is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# tkldev-detective is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# tkldev-detective. If not, see <https://www.gnu.org/licenses/>.
from argparse import ArgumentParser
from os.path import relpath, abspath
from typing import Generator
import logging
import sys

from libtkldet import locator, modman, colors
from libtkldet.report import Report, filter_all_reports
import libtkldet
import libtkldet.error
from libtkldet.error import ApplianceNotFoundError
import libtkldet.classifier
import libtkldet.linter

logger = logging.getLogger('tkldev-detective')

def perform_lint(
    root_path: str, dump_tags: bool, skip_lint: bool, ignore_non_appliance: bool
) -> Generator[Report, None, None]:
    libtkldet.initialize(root_path, ignore_non_appliance)
    try:
        root = locator.get_appliance_root(root_path)
    except ApplianceNotFoundError:
        if not ignore_non_appliance:
            raise
        else:
            root = root_path

    for path in locator.locator(root_path, ignore_non_appliance):
        item = libtkldet.classifier.FileItem(
            value=path,
            _tags={},
            relpath=relpath(path, start=root),
            abspath=abspath(path),
        )
        ignore = False
        for classifier in all_classifiers:
            classifier.classify(item)
            if item.has_tag_type('ignore'):
                logger.info('item "%s" skipped (tagged with %s)',
                            item.abspath,
                            ', '.join(map(repr, item.tags_with_type('ignore'))))
                ignore = True
                break
        if ignore:
            continue

        if dump_tags:
            item.pretty_print()
        if not skip_lint:
            for linter in all_linters:
                gen = linter.do_check(item)
                if gen:
                    yield from gen


if __name__ == "__main__":
    parser = ArgumentParser()
    parser.add_argument("--color", choices=["always", "never", "auto"], default="auto")
    parser.add_argument("--log-level", choices=["debug", "info", "warn",
                                                "error"], default="warn")
    subparsers = parser.add_subparsers(dest="action")

    list_parser = subparsers.add_parser("list")
    list_parser.add_argument("list_item", choices=["linters", "classifiers", "all"])
    lint_parser = subparsers.add_parser("lint")
    lint_parser.add_argument(
        "-t",
        "--dump-tags",
        action="store_true",
        help="show tags for each file before linting",
    )
    lint_parser.add_argument(
        "-s",
        "--skip-lint",
        action="store_true",
        help="don't actually perform lint, only classification",
    )
    lint_parser.add_argument(
        "-i",
        "--ignore-non-appliance",
        action="store_true",
        help="if no appliance found, just try to lint target anyway",
    )
    lint_parser.add_argument(
        "target",
        help="appliance name, path to appliance or path to file inside appliance",
    )

    args = parser.parse_args()

    log_level = logging.WARNING
    if args.log_level == "debug":
        log_level = logging.DEBUG
    elif args.log_level == "info":
        log_level = logging.INFO
    elif args.log_level == "warn":
        log_level = logging.WARNING
    elif args.log_level == "error":
        log_level = logging.ERROR

    logging.basicConfig(level=log_level)

    if args.color == "auto":
        colors.set_colors_enabled(sys.stdout.isatty())
    else:
        colors.set_colors_enabled(args.color == "always")

    modman.load_modules()

    all_classifiers = libtkldet.classifier.get_weighted_classifiers()
    all_linters = libtkldet.linter.get_weighted_linters()

    linters_by_name = {
        linter.__class__.__name__: linter
        for linter in all_linters
    }
    classifiers_by_name = {
        classifier.__class__.__name__: classifier
        for classifier in all_classifiers
    }

    if args.action == "list":
        if args.list_item in ("linters", "all"):
            for item in all_linters:
                print("linter", item.__class__.__name__)
        if args.list_item in ("classifiers", "all"):
            for item in all_classifiers:
                print("classifier", item.__class__.__name__)

    elif args.action == "lint":
        try:
            for report in filter_all_reports(
                perform_lint(
                    args.target,
                    args.dump_tags,
                    args.skip_lint,
                    args.ignore_non_appliance,
                )
            ):
                print("\n|   ".join(report.format().split("\n")))
                print()
        except libtkldet.error.PlanNotFoundError as e:
            print(
                colors.RED
                + "error: "
                + colors.RESET
                + "unable to find required plan: "
                + e.args[0]
            )
            sys.exit(1)
        except libtkldet.error.TKLDevDetectiveError as e:
            print(colors.RED + "error: " + colors.RESET + e.args[0])
            sys.exit(1)
