# SPDX-License-Identifier: GPL-3.0-or-later
"""
Base worker class to run various commands that build the image.
"""

import logging
import os

from . import internal, library

# initramfs-tools is a dependency for the kernel-image package. However, when
# kernel is not installed, as in case of Raspberry Pi image, explicit
# dependency is needed.
BASE_PACKAGES = [
    'initramfs-tools',
]

logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


class ImageBuilder:  # pylint: disable=too-many-instance-attributes
    """Base for all image builders."""
    architecture: str | None = None
    machine: str = 'all'
    include_non_free_firmware: bool = False
    include_contrib: bool = False

    builder_backend: str = 'internal'
    partition_table_type: str = 'msdos'
    root_filesystem_type: str = 'btrfs'
    boot_filesystem_type: str | None = None
    boot_size: str | None = None
    efi_filesystem_type: str | None = None
    efi_size: str | None = None
    firmware_filesystem_type: str | None = None
    firmware_size: str | None = None
    kernel_flavor: str = 'default'
    debootstrap_variant: str | None = None

    extra_storage_size: str = '1000M'

    @classmethod
    def get_target_name(cls):
        """Return the command line name of target for this builder."""
        return None

    @classmethod
    def get_builder_class(cls, target):
        """Return an builder class given target name."""
        from . import builders  # noqa: F401, pylint: disable=unused-variable

        for subclass in cls.get_subclasses():
            if subclass.get_target_name() == target:
                return subclass

        raise ValueError('No such target')

    @classmethod
    def get_subclasses(cls):
        """Iterate through the subclasses of this class."""
        for subclass in cls.__subclasses__():
            yield subclass
            yield from subclass.get_subclasses()

    def __init__(self, arguments):
        """Initialize object."""
        self.arguments = arguments
        self.packages = BASE_PACKAGES
        self.ram_directory = None

        self.builder_backends = {}
        self.builder_backends['internal'] = internal.InternalBuilderBackend(
            self)

        self.image_file = os.path.join(self.arguments.build_dir,
                                       self._get_image_base_name() + '.img')

    @property
    def image_size(self):
        """Return the size of the image to create based on CLI arguments."""
        return self.arguments.image_size

    @property
    def release_components(self):
        """Return the Debian release components to use for the build."""
        components = ['main']
        if self.include_non_free_firmware:
            components.append('non-free-firmware')

        if self.include_contrib:
            components.append('contrib')

        if self.arguments.release_component:
            for component in self.arguments.release_component:
                if component not in components:
                    components.append(component)

        return components

    def build(self):
        """Run the image building process."""
        archive_file = self.image_file + '.xz'
        self.make_image()
        self.compress(archive_file, self.image_file)

        self.sign(archive_file)

    def _get_builder_backend(self):
        """Returns the builder backend."""
        builder = self.builder_backend
        return self.builder_backends[builder]

    def make_image(self):
        """Call a builder backend to create basic image."""
        self._get_builder_backend().make_image()

    def _get_image_base_name(self):
        """Return the base file name of the final image."""
        build_stamp = self.arguments.build_stamp
        build_stamp = build_stamp + '_' if build_stamp else ''
        return 'freedombox-{distribution}_{build_stamp}{machine}' \
            '-{architecture}'.format(
                distribution=self.arguments.distribution,
                build_stamp=build_stamp, machine=self.machine,
                architecture=self.architecture)

    def compress(self, archive_file, image_file):
        """Compress the generated image."""
        if not self.arguments.skip_compression:
            library.compress(archive_file, image_file)
        else:
            logger.info('Skipping image compression')

    def sign(self, archive):
        """Sign the final output image."""
        if not self.arguments.sign:
            return

        library.sign(archive)

    @staticmethod
    def _replace_extension(file_name, new_extension):
        """Replace a file's extension with a new extention."""
        return file_name.rsplit('.', maxsplit=1)[0] + new_extension
