#!/usr/bin/env python3
#
# Copyright (C) 2021 Red Hat, Inc.
# SPDX-License-Identifier: LGPL-2.1-or-later

import argparse
import os
import pathlib
import pty
import shutil
import subprocess
import sys


class Parser:
    def __init__(self):
        # Options that are common to all actions that use containers
        containerparser = argparse.ArgumentParser(add_help=False)
        containerparser.add_argument(
            "target",
            help="perform action on target OS",
        )
        containerparser.add_argument(
            "--engine",
            choices=["auto", "podman", "docker"],
            default="auto",
            help="container engine to use",
        )
        containerparser.add_argument(
            "--login",
            default=os.getlogin(),  # exempt from syntax-check
            help="login to use inside the container",
        )
        containerparser.add_argument(
            "--image-prefix",
            default="registry.gitlab.com/libvirt/libvirt/ci-",
            help="use container images from non-default location",
        )
        containerparser.add_argument(
            "--image-tag",
            default=":latest",
            help="use container images with non-default tags",
        )

        # Options that are common to all actions that call the
        # project's build system
        mesonparser = argparse.ArgumentParser(add_help=False)
        mesonparser.add_argument(
            "--meson-args",
            default="",
            help="additional arguments passed to meson "
                 "(eg --meson-args='-Dopt1=enabled -Dopt2=disabled')",
        )
        mesonparser.add_argument(
            "--ninja-args",
            default="",
            help="additional arguments passed to ninja",
        )

        # Options that are common to all actions that use lcitool
        lcitoolparser = argparse.ArgumentParser(add_help=False)
        lcitoolparser.add_argument(
            "--lcitool",
            metavar="PATH",
            default="lcitool",
            help="path to lcitool binary",
        )

        # Main parser
        self.parser = argparse.ArgumentParser()
        subparsers = self.parser.add_subparsers(
            dest="action",
            metavar="ACTION",
        )
        subparsers.required = True

        # build action
        buildparser = subparsers.add_parser(
            "build",
            help="run a build in a container",
            parents=[containerparser, mesonparser],
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        buildparser.set_defaults(func=Application.action_build)

        # test action
        testparser = subparsers.add_parser(
            "test",
            help="run a build in a container (including tests)",
            parents=[containerparser, mesonparser],
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        testparser.set_defaults(func=Application.action_test)

        # shell action
        shellparser = subparsers.add_parser(
            "shell",
            help="start a shell in a container",
            parents=[containerparser],
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        shellparser.set_defaults(func=Application.action_shell)

        # list-images action
        listimagesparser = subparsers.add_parser(
            "list-images",
            help="list known container images",
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        listimagesparser.set_defaults(func=Application.action_list_images)

        # refresh action
        refreshparser = subparsers.add_parser(
            "refresh",
            help="refresh data generated with lcitool",
            parents=[lcitoolparser],
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        )
        refreshparser.add_argument(
            "--quiet",
            action="store_true",
            default=False,
            help="refresh data silently"
        )
        refreshparser.set_defaults(func=Application.action_refresh)

    def parse(self):
        return self.parser.parse_args()


class Application:
    def __init__(self):
        self.basedir = pathlib.Path(__file__).resolve().parent
        self.args = Parser().parse()

        if self.args.action == "refresh":
            if not shutil.which(self.args.lcitool):
                sys.exit("error: 'lcitool' not installed")

    def make_run(self, target):
        args = [
            "-C",
            self.basedir,
            target,
        ]

        if self.args.action in ["build", "test", "shell"]:
            args.extend([
                f"CI_ENGINE={self.args.engine}",
                f"CI_USER_LOGIN={self.args.login}",
                f"CI_IMAGE_PREFIX={self.args.image_prefix}",
                f"CI_IMAGE_TAG={self.args.image_tag}",
            ])

        if self.args.action in ["build", "test"]:
            args.extend([
                f"CI_MESON_ARGS={self.args.meson_args}",
                f"CI_NINJA_ARGS={self.args.ninja_args}",
            ])

        if pty.spawn(["make"] + args) != 0:
            sys.exit("error: 'make' failed")

    def lcitool_run(self, args):
        output = subprocess.check_output([self.args.lcitool] + args)
        return output.decode("utf-8")

    def lcitool_get_hosts(self):
        output = self.lcitool_run(["hosts"])
        return output.splitlines()

    def generate_dockerfile(self, host, cross=None):
        args = ["dockerfile", host, "libvirt"]
        outdir = self.basedir.joinpath("containers")
        outfile = f"ci-{host}.Dockerfile"

        if cross:
            args.extend(["--cross", cross])
            outfile = f"ci-{host}-cross-{cross}.Dockerfile"

        outpath = outdir.joinpath(outfile)
        if not self.args.quiet:
            print(outpath)

        output = self.lcitool_run(args)
        with open(outpath, "w") as f:
            f.write(output)

    def generate_vars(self, host):
        args = ["variables", host, "libvirt"]
        outdir = self.basedir.joinpath("cirrus")
        outfile = f"{host}.vars"

        outpath = outdir.joinpath(outfile)
        if not self.args.quiet:
            print(outpath)

        output = self.lcitool_run(args)
        with open(outpath, "w") as f:
            f.write(output)

    def refresh_containers(self):
        debian_cross = [
            "aarch64",
            "armv6l",
            "armv7l",
            "i686",
            "mips",
            "mips64el",
            "mipsel",
            "ppc64le",
            "s390x",
        ]
        fedora_cross = [
            "mingw32",
            "mingw64",
        ]

        for host in self.lcitool_get_hosts():
            if host.startswith("freebsd-") or host.startswith("macos-"):
                continue

            self.generate_dockerfile(host)

            if host == "fedora-rawhide":
                for cross in fedora_cross:
                    self.generate_dockerfile(host, cross)

            if host.startswith("debian-"):
                for cross in debian_cross:
                    if host == "debian-sid" and cross == "mips":
                        continue
                    self.generate_dockerfile(host, cross)

    def refresh_cirrus(self):
        for host in self.lcitool_get_hosts():
            if not (host.startswith("freebsd-") or host.startswith("macos-")):
                continue

            self.generate_vars(host)

    def action_build(self):
        self.make_run(f"ci-build@{self.args.target}")

    def action_test(self):
        self.make_run(f"ci-test@{self.args.target}")

    def action_shell(self):
        self.make_run(f"ci-shell@{self.args.target}")

    def action_list_images(self):
        self.make_run(f"ci-list-images")

    def action_refresh(self):
        self.refresh_containers()
        self.refresh_cirrus()

    def run(self):
        self.args.func(self)


if __name__ == "__main__":
    Application().run()