#!/usr/bin/env python3
#
# Copyright (C) 2018, 2019 Collabora Limited
# Author: Guillaume Tucker <guillaume.tucker@collabora.com>
#
# This module is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library 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 Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this library; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import sys

from kernelci.cli import Args, Command, parse_args
import kernelci.build
import kernelci.config.build


class cmd_validate(Command):
    help = "Validate the YAML configuration"
    opt_args = [Args.verbose]

    def __call__(self, configs, args):
        # ToDo: Use jsonschema
        return True


class cmd_list_configs(Command):
    help = "List the build configurations"

    def __call__(self, configs, args):
        for conf_name in list(configs['build_configs'].keys()):
            print(conf_name)
        return True


class cmd_check_new_commit(Command):
    help = "Check if a new commit is available on a branch"
    args = [Args.build_config, Args.storage]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        update = kernelci.build.check_new_commit(conf, args.storage)
        if update is False or update is True:
            return update
        print(update)
        return True


class cmd_update_last_commit(Command):
    help = "Update the last commit file on the remote storage server"
    args = [Args.build_config, Args.api, Args.db_token, Args.commit]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        kernelci.build.set_last_commit(
            conf, args.api, args.db_token, args.commit)
        return True


class cmd_tree_branch(Command):
    help = "Print the name of the tree and branch for a given build config"
    args = [Args.build_config]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        print(conf.tree.name)
        print(conf.tree.url)
        print(conf.branch)
        return True


class cmd_update_mirror(Command):
    help = "Update the local kernel git mirror"
    args = [Args.build_config, Args.mirror]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        kernelci.build.update_mirror(conf, args.mirror)
        return True


class cmd_update_repo(Command):
    help = "Update the local kernel repository checkout"
    args = [Args.build_config, Args.kdir]
    opt_args = [Args.mirror]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        kernelci.build.update_repo(conf, args.kdir, args.mirror)
        return True


class cmd_describe(Command):
    help = "Print the git commit, describe and verbose describe from kdir"
    args = [Args.build_config, Args.kdir]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        commit = kernelci.build.head_commit(args.kdir)
        describe = kernelci.build.git_describe(conf.tree.name, args.kdir)
        verbose = kernelci.build.git_describe_verbose(args.kdir)
        print(commit)
        print(describe)
        print(verbose or describe)
        return True


class cmd_generate_fragments(Command):
    help = "Generate the config fragment files in kdir"
    args = [Args.build_config, Args.kdir]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        kernelci.build.generate_fragments(conf, args.kdir)
        return True


class cmd_generate_defconfig_fragments(Command):
    help = "Generate the config fragment files for a given defconfig"
    args = [Args.defconfig, Args.kdir]

    def __call__(self, configs, args):
        fragments = configs['fragments']
        split = args.defconfig.split('+')
        defconfig = split.pop(0)
        for part in split:
            frag = fragments.get(part)
            if frag and frag.configs:
                kernelci.build.generate_config_fragment(frag, args.kdir)
        return True


class cmd_expand_fragments(Command):
    help = "Expand a defconfig string with full fragment paths"
    args = [Args.defconfig]

    def __call__(self, configs, args):
        frags = configs['fragments']
        split = args.defconfig.split('+')
        expanded = [split.pop(0)]
        for part in split:
            frag = frags.get(part)
            if frag:
                expanded.append(frag.path)
            else:
                expanded.append(part)
        print('+'.join(expanded))
        return True


class cmd_push_tarball(Command):
    help = "Create and up a source tarball to the remote storage server"
    args = [Args.build_config, Args.kdir, Args.storage,
            Args.api, Args.db_token]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        func_args = (conf, args.kdir, args.storage, args.api, args.db_token)
        if not all(func_args):
            print("Invalid arguments")
            return False
        tarball_url = kernelci.build.push_tarball(*func_args)
        if not tarball_url:
            return False
        print(tarball_url)
        return True


class cmd_list_variants(Command):
    help = "Print the list of build variants for a given build configuration"
    args = [Args.build_config]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        for variant in conf.variants:
            print(variant.name)
        return True


class cmd_arch_list(Command):
    help = "Print the list of CPU architecture names for a given build variant"
    args = [Args.build_config, Args.variant]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        variant = conf.get_variant(args.variant)
        for arch in variant.arch_list:
            print(arch)
        return True


def _show_build_env(build_env, arch=None):
    print(build_env.name)
    print(build_env.cc)
    print(build_env.cc_version)
    if arch:
        print(build_env.get_arch_name(arch) or '')
        print(build_env.get_cross_compile(arch) or '')
        print(build_env.get_cross_compile_compat(arch) or '')


class cmd_get_build_env(Command):
    help = "Print the build environment parameters for a given build variant"
    args = [Args.build_config, Args.variant]
    opt_args = [Args.arch]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        variant = conf.get_variant(args.variant)
        _show_build_env(variant.build_environment, args.arch)
        return True


class cmd_get_reference(Command):
    help = "Print reference tree and branch for bisections"
    args = [Args.tree_name, Args.branch]

    def __call__(self, configs, args):
        for conf in configs['build_configs'].values():
            if conf.tree.name == args.tree_name and conf.branch == args.branch:
                if conf.reference:
                    print(conf.reference.tree.url)
                    print(conf.reference.tree.name)
                    print(conf.reference.branch)
                    return True
        return False


class cmd_show_build_env(Command):
    help = "Show parameters of a given build environment"
    args = [Args.build_env]
    opt_args = [Args.arch]

    def __call__(self, configs, args):
        build_env = configs['build_environments'][args.build_env]
        _show_build_env(build_env, args.arch)
        return True


class cmd_list_kernel_configs(Command):
    help = "List the kernel configs to build for a given build configuration"
    args = [Args.build_config, Args.kdir]
    opt_args = [Args.variant, Args.arch]

    def __call__(self, configs, args):
        conf = configs['build_configs'][args.build_config]
        configs = kernelci.build.list_kernel_configs(
            conf, args.kdir, args.variant, args.arch)
        for item in configs:
            print(' '.join(item))
        return True


class cmd_build_kernel(Command):
    help = "Build a kernel"
    args = [Args.build_env, Args.arch, Args.kdir]
    opt_args = [Args.defconfig, Args.j, Args.verbose, Args.output,
                Args.mod_path]

    def __call__(self, configs, args):
        build_env = configs['build_environments'][args.build_env]
        return kernelci.build.build_kernel(
            build_env, args.kdir, args.arch, args.defconfig, args.j,
            args.verbose, args.output, args.mod_path)


class cmd_install_kernel(Command):
    help = "Install the kernel binaries and bmeta.json locally"
    args = [Args.kdir]
    opt_args = [Args.build_config, Args.tree_name, Args.tree_url, Args.branch,
                Args.commit, Args.describe, Args.describe_verbose,
                Args.output, Args.publish_path, Args.install_path,
                Args.mod_path]

    def __call__(self, configs, args):
        if args.build_config:
            conf = configs['build_configs'][args.build_config]
            tree_name = conf.tree.name
            tree_url = conf.tree.url
            branch = conf.branch
        else:
            tree_name = args.tree_name
            tree_url = args.tree_url
            branch = args.branch
        if not all((tree_name, tree_url, branch)):
            print("""\
Invalid arguments, either of these 2 sets are possible:
   --tree-name, --tree-url and --branch
or
   --config (in which case the tree and branch are read from the YAML config)\
""")
            return False
        return kernelci.build.install_kernel(
            args.kdir, tree_name, tree_url, branch, args.commit, args.describe,
            args.describe_verbose, args.output, args.publish_path,
            args.install_path, args.mod_path)


class cmd_push_kernel(Command):
    help = "Push the kernel binaries"
    args = [Args.kdir, Args.api, Args.db_token]
    opt_args = [Args.install_path]

    def __call__(self, configs, args):
        return kernelci.build.push_kernel(args.kdir, args.api, args.db_token,
                                          args.install_path)


class cmd_publish_kernel(Command):
    help = "Publish the kernel meta-data"
    args = [Args.kdir]
    opt_args = [Args.api, Args.db_token, Args.json_path, Args.install_path]

    def __call__(self, configs, args):
        if not ((args.api and args.db_token) or args.json_path):
            print("""\
Invalid arguments, please provide at least one of these sets of options:
    --db-token, --api to publish to the backend server
    --json-path to save the data in a local JSON file\
""")
            return False
        return kernelci.build.publish_kernel(
            args.kdir, args.install_path, args.api, args.db_token,
            args.json_path)


class cmd_pull_tarball(Command):
    help = "Downloads and untars kernel sources"
    args = [Args.kdir, Args.url]
    opt_args = [Args.filename, Args.retries, Args.delete]

    def __call__(self, configs, args):
        return kernelci.build.pull_tarball(args.kdir, args.url,
                                           args.filename, args.retries,
                                           args.delete)


if __name__ == '__main__':
    args = parse_args("kci_build", "build-configs.yaml", globals())
    configs = kernelci.config.build.from_yaml(args.yaml_configs)
    status = args.func(configs, args)
    sys.exit(0 if status is True else 1)
