#
# DBus interface for the bootloader module.
#
# Copyright (C) 2018 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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 this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
from dasbus.server.interface import dbus_interface
from dasbus.server.property import emits_properties_changed
from dasbus.typing import *  # pylint: disable=wildcard-import
from pyanaconda.modules.common.base import KickstartModuleInterfaceTemplate
from pyanaconda.modules.common.constants.objects import BOOTLOADER
from pyanaconda.modules.common.containers import TaskContainer
from pyanaconda.modules.storage.constants import BootloaderMode, ZIPLSecureBoot


@dbus_interface(BOOTLOADER.interface_name)
class BootloaderInterface(KickstartModuleInterfaceTemplate):
    """DBus interface for the bootloader module."""

    def connect_signals(self):
        """Connect the signals."""
        super().connect_signals()
        self.watch_property("BootloaderMode", self.implementation.bootloader_mode_changed)
        self.watch_property("PreferredLocation", self.implementation.preferred_location_changed)
        self.watch_property("Drive", self.implementation.drive_changed)
        self.watch_property("DriveOrder", self.implementation.drive_order_changed)
        self.watch_property("KeepMBR", self.implementation.keep_mbr_changed)
        self.watch_property("KeepBootOrder", self.implementation.keep_boot_order_changed)
        self.watch_property("ExtraArguments", self.implementation.extra_arguments_changed)
        self.watch_property("Timeout", self.implementation.timeout_changed)
        self.watch_property("ZIPLSecureBoot", self.implementation.zipl_secure_boot_changed)
        self.watch_property("IsPasswordSet", self.implementation.password_is_set_changed)

    def GetDefaultType(self) -> Str:
        """Get the default type of the boot loader.

        FIXME: This is a temporary workaround for UI.

        :return: a name of a boot loader type
        """
        return self.implementation.get_default_type().value

    @property
    def BootloaderMode(self) -> Int:
        """The mode of the bootloader."""
        return self.implementation.bootloader_mode.value

    @BootloaderMode.setter
    @emits_properties_changed
    def BootloaderMode(self, mode: Int):
        """Set the type of the bootloader.

        Allowed values:
            0  Disabled.
            1  Enabled.
            2  Skipped.

        The skipped mode prevents extra installation steps that makes
        the target machine bootable, e.g. write to MBR on x86 BIOS
        systems. However, the corresponding RPM packages are still
        installed. The disabled mode also prevents the installation
        of RPM packages.

        :param mode: a number of the mode
        """
        self.implementation.set_bootloader_mode(BootloaderMode(mode))

    @property
    def PreferredLocation(self) -> Str:
        """Where the boot record is written."""
        return self.implementation.preferred_location

    @PreferredLocation.setter
    @emits_properties_changed
    def PreferredLocation(self, location: Str):
        """Specify where the boot record is written.

        Supported values:
            DEFAULT    The default location.
            MBR        The master boot record.
            PARTITION  Install the boot loader on the first sector of
                       the partition containing the kernel.

        :param location: a string with a location
        """
        self.implementation.set_preferred_location(location)

    @property
    def Drive(self) -> Str:
        """The drive where the bootloader should be written."""
        return self.implementation.drive

    @Drive.setter
    @emits_properties_changed
    def Drive(self, drive: Str):
        """Set the drive where the bootloader should be written.

        Specifies which drive the bootloader should be written to and
        thus, which drive the computer will boot from.

        :param drive: a name of the drive
        """
        self.implementation.set_drive(drive)

    @property
    def DriveOrder(self) -> List[Str]:
        """Potentially partial order for disks."""
        return self.implementation.drive_order

    @DriveOrder.setter
    @emits_properties_changed
    def DriveOrder(self, drives: List[Str]):
        """Set the potentially partial order for disks.

        :param drives: a list of names of drives
        """
        self.implementation.set_drive_order(drives)

    @property
    def KeepMBR(self) -> Bool:
        """Don't update the MBR."""
        return self.implementation.keep_mbr

    @KeepMBR.setter
    @emits_properties_changed
    def KeepMBR(self, value: Bool):
        """Set if the MBR can be updated.

        If you want to keep the MBR, then the bootloader will be installed
        but the MBR will not be updated. Therefore, when the system reboots,
        a previously installed OS will be booted.

        :param value: True if the MBR cannot be updated, otherwise False
        """
        self.implementation.set_keep_mbr(value)

    @property
    def KeepBootOrder(self) -> Bool:
        """Don't change the existing boot order."""
        return self.implementation.keep_boot_order

    @KeepBootOrder.setter
    @emits_properties_changed
    def KeepBootOrder(self, value: Bool):
        """Set if the the boot order can be changed.

        Boot the drives in their existing order, to override the default
        of booting into the newly installed drive on Power Systems servers
        and EFI systems. This is useful for systems that, for example,
        should network boot first before falling back to a local boot.

        :param value: True to use the existing order, otherwise False
        """
        self.implementation.set_keep_boot_order(value)

    @property
    def ExtraArguments(self) -> List[Str]:
        """List of extra bootloader arguments."""
        return self.implementation.extra_arguments

    @emits_properties_changed
    def SetExtraArguments(self, args: List[Str]):
        """Set the extra bootloader arguments.

        Specifies kernel parameters. The default set of bootloader
        arguments is “rhgb quiet”. You will get this set of arguments
        regardless of what extra parameters you set.

        :param args: list of arguments
        """
        self.implementation.set_extra_arguments(args)

    @ExtraArguments.setter
    @emits_properties_changed
    def ExtraArguments(self, args: List[Str]):
        """Set the extra bootloader arguments.

        Specifies kernel parameters. The default set of bootloader
        arguments is “rhgb quiet”. You will get this set of arguments
        regardless of what extra parameters you set.

        :param args: list of arguments
        """
        self.implementation.set_extra_arguments(args)

    @property
    def Timeout(self) -> Int:
        """The bootloader timeout."""
        return self.implementation.timeout

    @Timeout.setter
    @emits_properties_changed
    def Timeout(self, timeout: Int):
        """Set the bootloader timeout.

        Specify the number of seconds before the bootloader times out
        and boots the default option.

        :param timeout: a number of seconds
        """
        self.implementation.set_timeout(timeout)

    @property
    def ZIPLSecureBoot(self) -> Str:
        """The ZIPL Secure Boot for s390x."""
        return self.implementation.zipl_secure_boot.value

    @ZIPLSecureBoot.setter
    @emits_properties_changed
    def ZIPLSecureBoot(self, value: Str):
        """Set up the ZIPL Secure Boot for s390x.

        Supported values:
            0     Disable Secure Boot.
            1     Enable Secure Boot (or fail if unsupported).
            auto  Enable Secure Boot if supported.

        Firmware will verify the integrity of the Linux kernel during
        boot if the Secure Boot is enabled and configured on the machine.

        Note: Secure Boot is not supported on IBM z14 and earlier models,
        therefore choose to disable it if you intend to boot the installed
        system on such models.

        :param value: a string
        """
        self.implementation.set_zipl_secure_boot(ZIPLSecureBoot(value))

    @property
    def Password(self) -> Str:
        """The GRUB boot loader password.

        If using GRUB, set the GRUB boot loader password. This should
        be used to restrict access to the GRUB shell, where arbitrary
        kernel options can be passed.
        """
        return self.implementation.password

    @emits_properties_changed
    def SetEncryptedPassword(self, password: Str):
        """Set the GRUB boot loader password.

        :param password: a string with the encrypted password
        """
        self.implementation.set_password(password, encrypted=True)

    @property
    def IsPasswordSet(self) -> Bool:
        """Is the GRUB boot loader password set?"""
        return self.implementation.password_is_set

    @property
    def IsPasswordEncrypted(self) -> Bool:
        """Is the GRUB boot loader password encrypted?"""
        return self.implementation.password_is_encrypted

    def IsEFI(self) -> Bool:
        """Is the bootloader based on EFI?

        :return: True or False
        """
        return self.implementation.is_efi()

    def GetArguments(self) -> List[Str]:
        """Get the bootloader arguments.

        Get kernel parameters that are currently set up for the bootloader.
        The list is complete and final after the bootloader installation.

        :return: list of arguments
        """
        return self.implementation.get_arguments()

    def DetectWindows(self) -> Bool:
        """Are Windows OS installed on the system?

        Guess by searching for bootable partitions of other operating
        systems whether there are Windows OS installed on the system.

        :return: True or False
        """
        return self.implementation.detect_windows()

    def InstallBootloaderWithTasks(self, payload_type: Str, kernel_versions: List[Str]) \
            -> List[ObjPath]:
        """Install the bootloader with a list of tasks.

        FIXME: This is just a temporary method.

        :param payload_type: a string with the payload type
        :param kernel_versions: a list of kernel versions
        :return: a list of paths to DBus tasks
        """
        tasks = self.implementation.install_bootloader_with_tasks(
            payload_type,
            kernel_versions
        )
        return TaskContainer.to_object_path_list(tasks)

    def GenerateInitramfsWithTasks(self, payload_type: Str, kernel_versions: List[Str]) \
            -> List[ObjPath]:
        """Generate initramfs with a list of tasks.

        FIXME: This is just a temporary method.

        :param payload_type: a string with the payload type
        :param kernel_versions: a list of kernel versions
        :return: a list of paths to DBus tasks
        """
        tasks = self.implementation.generate_initramfs_with_tasks(
            payload_type,
            kernel_versions
        )
        return TaskContainer.to_object_path_list(tasks)
