# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# This program is free software; you can distribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program 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 General Public License for mo 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.
#
# AUTHOR
# Adrian Freihofer <adrian.freihofer (at] neratec.com>
#

import os
from wic import kickstart
from wic import msger
from wic.utils import syslinux
from wic.utils import runner
from wic.utils.oe import misc
from wic.utils.errors import ImageError
from wic.pluginbase import SourcePlugin


# pylint: disable=no-init
class RootfsPlugin(SourcePlugin):
    """
    Create root partition and install syslinux bootloader

    This plugin creates a disk image containing a bootable root partition with
    syslinux installed. The filesystem is ext2/3/4, no extra boot partition is
    required.

    Example kickstart file:
    part / --source rootfs-pcbios-ext --ondisk sda --fstype=ext4 --label rootfs --align 1024
    bootloader --source rootfs-pcbios-ext --timeout=0 --append="rootwait rootfstype=ext4"

    The first line generates a root file system including a syslinux.cfg file
    The "--source rootfs-pcbios-ext" in the second line triggers the installation
    of ldlinux.sys into the image.
    """

    name = 'rootfs-pcbios-ext'

    @staticmethod
    def _get_rootfs_dir(rootfs_dir):
        """
        Find rootfs pseudo dir

        If rootfs_dir is a directory consider it as rootfs directory.
        Otherwise ask bitbake about the IMAGE_ROOTFS directory.
        """
        if os.path.isdir(rootfs_dir):
            return rootfs_dir

        image_rootfs_dir = misc.get_bitbake_var("IMAGE_ROOTFS", rootfs_dir)
        if not os.path.isdir(image_rootfs_dir):
            msg = "No valid artifact IMAGE_ROOTFS from image named"
            msg += " %s has been found at %s, exiting.\n" % \
                (rootfs_dir, image_rootfs_dir)
            msger.error(msg)

        return image_rootfs_dir

    # pylint: disable=unused-argument
    @classmethod
    def do_configure_partition(cls, part, source_params, image_creator,
                               image_creator_workdir, oe_builddir, bootimg_dir,
                               kernel_dir, native_sysroot):
        """
        Creates syslinux config in rootfs directory

        Called before do_prepare_partition()
        """
        options = image_creator.ks.handler.bootloader.appendLine

        syslinux_conf = ""
        syslinux_conf += "PROMPT 0\n"

        timeout = kickstart.get_timeout(image_creator.ks)
        if not timeout:
            timeout = 0
        syslinux_conf += "TIMEOUT " + str(timeout) + "\n"
        syslinux_conf += "ALLOWOPTIONS 1\n"

        # Derive SERIAL... line from from kernel boot parameters
        syslinux_conf += syslinux.serial_console_form_kargs(options) + "\n"

        syslinux_conf += "DEFAULT linux\n"
        syslinux_conf += "LABEL linux\n"
        syslinux_conf += "  KERNEL /boot/bzImage\n"

        syslinux_conf += "  APPEND label=boot root=%s %s\n" % \
                             (image_creator.rootdev, options)

        syslinux_cfg = os.path.join(image_creator.rootfs_dir['ROOTFS_DIR'], "boot", "syslinux.cfg")
        msger.debug("Writing syslinux config %s" % syslinux_cfg)
        with open(syslinux_cfg, "w") as cfg:
            cfg.write(syslinux_conf)

    @classmethod
    def do_prepare_partition(cls, part, source_params, image_creator,
                             image_creator_workdir, oe_builddir, bootimg_dir,
                             kernel_dir, krootfs_dir, native_sysroot):
        """
        Creates partition out of rootfs directory

        Prepare content for a rootfs partition i.e. create a partition
        and fill it from a /rootfs dir.
        Install syslinux bootloader into root partition image file
        """
        def is_exe(exepath):
            """Verify exepath is an executable file"""
            return os.path.isfile(exepath) and os.access(exepath, os.X_OK)

        # Make sure syslinux-nomtools is available in native sysroot or fail
        native_syslinux_nomtools = os.path.join(native_sysroot, "usr/bin/syslinux-nomtools")
        if not is_exe(native_syslinux_nomtools):
            msger.info("building syslinux-native...")
            misc.exec_cmd("bitbake syslinux-native")
        if not is_exe(native_syslinux_nomtools):
            msger.error("Couldn't find syslinux-nomtools (%s), exiting\n" %
                        native_syslinux_nomtools)

        if part.rootfs is None:
            if 'ROOTFS_DIR' not in krootfs_dir:
                msger.error("Couldn't find --rootfs-dir, exiting")
            rootfs_dir = krootfs_dir['ROOTFS_DIR']
        else:
            if part.rootfs in krootfs_dir:
                rootfs_dir = krootfs_dir[part.rootfs]
            elif part.rootfs:
                rootfs_dir = part.rootfs
            else:
                msg = "Couldn't find --rootfs-dir=%s connection"
                msg += " or it is not a valid path, exiting"
                msger.error(msg % part.rootfs)

        real_rootfs_dir = cls._get_rootfs_dir(rootfs_dir)

        part.set_rootfs(real_rootfs_dir)
        part.prepare_rootfs(image_creator_workdir, oe_builddir, real_rootfs_dir, native_sysroot)

        # install syslinux into rootfs partition
        syslinux_cmd = "syslinux-nomtools -d /boot -i %s" % part.source_file
        misc.exec_native_cmd(syslinux_cmd, native_sysroot)

    @classmethod
    def do_install_disk(cls, disk, disk_name, image_creator, workdir, oe_builddir,
                        bootimg_dir, kernel_dir, native_sysroot):
        """
        Assemble partitions to disk image

        Called after all partitions have been prepared and assembled into a
        disk image. In this case, we install the MBR.
        """
        mbrfile = os.path.join(native_sysroot, "usr/share/syslinux/")
        if image_creator.ptable_format == 'msdos':
            mbrfile += "mbr.bin"
        elif image_creator.ptable_format == 'gpt':
            mbrfile += "gptmbr.bin"
        else:
            msger.error("Unsupported partition table: %s" % \
                        image_creator.ptable_format)

        if not os.path.exists(mbrfile):
            msger.error("Couldn't find %s. Has syslinux-native been baked?" % mbrfile)

        full_path = disk['disk'].device
        msger.debug("Installing MBR on disk %s as %s with size %s bytes" \
                    % (disk_name, full_path, disk['min_size']))

        ret_code = runner.show(['dd', 'if=%s' % mbrfile, 'of=%s' % full_path, 'conv=notrunc'])
        if ret_code != 0:
            raise ImageError("Unable to set MBR to %s" % full_path)