#
# Copyright (C) 2019  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 abc import ABCMeta, abstractmethod

from blivet.errors import NoDisksError
from blivet.formats.disklabel import DiskLabel
from pyanaconda.anaconda_loggers import get_module_logger

from pyanaconda.core.i18n import _
from pyanaconda.modules.storage.bootloader.execution import setup_bootloader
from pyanaconda.modules.common.constants.objects import DISK_INITIALIZATION
from pyanaconda.modules.common.constants.services import STORAGE
from pyanaconda.modules.storage.disk_initialization import DiskInitializationConfig
from pyanaconda.modules.storage.partitioning.base_partitioning import PartitioningTask

log = get_module_logger(__name__)

__all__ = ["NonInteractivePartitioningTask"]


class NonInteractivePartitioningTask(PartitioningTask, metaclass=ABCMeta):
    """A task for the non-interactive partitioning configuration."""

    def _run(self, storage):
        """Do the partitioning."""
        self._clear_partitions(storage)
        self._prepare_bootloader(storage)
        self._configure_partitioning(storage)
        self._setup_bootloader(storage)

    def _get_initialization_config(self):
        """Get the initialization config.

        FIXME: This is a temporary method.
        """
        config = DiskInitializationConfig()

        # Update the config.
        disk_init_proxy = STORAGE.get_proxy(DISK_INITIALIZATION)
        config.initialization_mode = disk_init_proxy.InitializationMode
        config.drives_to_clear = disk_init_proxy.DrivesToClear
        config.devices_to_clear = disk_init_proxy.DevicesToClear
        config.initialize_labels = disk_init_proxy.InitializeLabelsEnabled
        config.format_unrecognized = disk_init_proxy.FormatUnrecognizedEnabled
        config.clear_non_existent = False

        # Update the disk label.
        disk_label = disk_init_proxy.DefaultDiskLabel

        if disk_label and not DiskLabel.set_default_label_type(disk_label):
            log.warning("%s is not a supported disklabel type on this platform. "
                        "Using default disklabel %s instead.", disk_label,
                        DiskLabel.get_platform_label_types()[0])

        return config

    def _clear_partitions(self, storage):
        """Clear partitions.

        :param storage: an instance of Blivet
        """
        # Set up the initialization config.
        config = self._get_initialization_config()

        # Sort partitions by descending partition number to minimize confusing
        # things like multiple "destroy sda5" actions due to parted renumbering
        # partitions. This can still happen through the UI but it makes sense to
        # avoid it where possible.
        partitions = sorted(storage.partitions,
                            key=lambda p: getattr(p.parted_partition, "number", 1),
                            reverse=True)
        for part in partitions:
            log.debug("Looking at partition: %s", part.name)
            if not config.can_remove(storage, part):
                continue

            storage.recursive_remove(part)
            log.debug("Partitions: %s", [p.name for p in part.disk.children])

        # Now remove any empty extended partitions.
        storage.remove_empty_extended_partitions()

        # Ensure all disks have appropriate disk labels.
        for disk in storage.disks:
            log.debug("Looking at disk: %s", disk.name)
            if config.can_remove(storage, disk):
                log.debug("Removing %s.", disk.name)
                storage.recursive_remove(disk)

            if config.can_initialize(storage, disk):
                log.debug("Initializing %s.", disk.name)
                storage.initialize_disk(disk)

        # Check the usable disks.
        if not any(d for d in storage.disks if not d.format.hidden and not d.protected):
            raise NoDisksError(_("No usable disks."))

    def _prepare_bootloader(self, storage):
        """Prepare the bootloader.

        :param storage: an instance of Blivet
        """
        setup_bootloader(storage, dry_run=True)

    @abstractmethod
    def _configure_partitioning(self, storage):
        """Configure the partitioning.

        :param storage: an instance of Blivet
        """
        pass

    def _setup_bootloader(self, storage):
        """Set up the bootloader.

        :param storage: an instance of Blivet
        """
        setup_bootloader(storage)
