# Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later
import logging
import os
import glob
import pmb.helpers.mount
import pmb.install.losetup
import pmb.helpers.cli
import pmb.config


def previous_install(args, path):
    """
    Search the sdcard for possible existence of a previous installation of
    pmOS. We temporarily mount the possible pmOS_boot partition as
    /dev/sdcardp1 inside the native chroot to check the label from there.
    :param path: path to sdcard device (e.g. /dev/mmcblk0)
    """
    label = ""
    for blockdevice_outside in [f"{path}1", f"{path}p1"]:
        if not os.path.exists(blockdevice_outside):
            continue
        blockdevice_inside = "/dev/sdcardp1"
        pmb.helpers.mount.bind_file(args, blockdevice_outside,
                                    args.work + '/chroot_native' +
                                    blockdevice_inside)
        try:
            label = pmb.chroot.root(args, ["blkid", "-s", "LABEL",
                                           "-o", "value",
                                           blockdevice_inside],
                                    output_return=True)
        except RuntimeError:
            logging.info("WARNING: Could not get block device label,"
                         " assume no previous installation on that partition")

        pmb.helpers.run.root(args, ["umount", args.work + "/chroot_native" +
                                    blockdevice_inside])
    return "pmOS_boot" in label


def mount_sdcard(args, path):
    """
    :param path: path to sdcard device (e.g. /dev/mmcblk0)
    """
    # Sanity checks
    if args.deviceinfo["external_storage"] != "true":
        raise RuntimeError("According to the deviceinfo, this device does"
                           " not support a sdcard installation.")
    if not os.path.exists(path):
        raise RuntimeError(f"The sdcard device does not exist: {path}")
    for path_mount in glob.glob(f"{path}*"):
        if pmb.helpers.mount.ismount(path_mount):
            raise RuntimeError(f"{path_mount} is mounted! Will not attempt to"
                               " format this!")
    logging.info(f"(native) mount /dev/install (host: {path})")
    pmb.helpers.mount.bind_file(args, path,
                                args.work + "/chroot_native/dev/install")
    if previous_install(args, path):
        if not pmb.helpers.cli.confirm(args, "WARNING: This device has a"
                                       " previous installation of pmOS."
                                       " CONTINUE?"):
            raise RuntimeError("Aborted.")
    else:
        if not pmb.helpers.cli.confirm(args, f"EVERYTHING ON {path} WILL BE"
                                       " ERASED! CONTINUE?"):
            raise RuntimeError("Aborted.")


def create_and_mount_image(args, size_boot, size_root, size_reserve,
                           split=False):
    """
    Create a new image file, and mount it as /dev/install.

    :param size_boot: size of the boot partition in MiB
    :param size_root: size of the root partition in MiB
    :param size_reserve: empty partition between root and boot in MiB (pma#463)
    :param split: create separate images for boot and root partitions
    """

    # Short variables for paths
    chroot = args.work + "/chroot_native"
    img_path_prefix = "/home/pmos/rootfs/" + args.device
    img_path_full = img_path_prefix + ".img"
    img_path_boot = img_path_prefix + "-boot.img"
    img_path_root = img_path_prefix + "-root.img"

    # Umount and delete existing images
    for img_path in [img_path_full, img_path_boot, img_path_root]:
        outside = chroot + img_path
        if os.path.exists(outside):
            pmb.helpers.mount.umount_all(args, chroot + "/mnt")
            pmb.install.losetup.umount(args, img_path)
            pmb.chroot.root(args, ["rm", img_path])

    # Make sure there is enough free space
    size_mb = round(size_boot + size_reserve + size_root)
    disk_data = os.statvfs(args.work)
    free = round((disk_data.f_bsize * disk_data.f_bavail) / (1024**2))
    if size_mb > free:
        raise RuntimeError("Not enough free space to create rootfs image! "
                           f"(free: {free}M, required: {size_mb}M)")

    # Create empty image files
    pmb.chroot.user(args, ["mkdir", "-p", "/home/pmos/rootfs"])
    size_mb_full = str(size_mb) + "M"
    size_mb_boot = str(round(size_boot)) + "M"
    size_mb_root = str(round(size_root)) + "M"
    images = {img_path_full: size_mb_full}
    if split:
        images = {img_path_boot: size_mb_boot,
                  img_path_root: size_mb_root}
    for img_path, size_mb in images.items():
        logging.info(f"(native) create {os.path.basename(img_path)} "
                     f"({size_mb})")
        pmb.chroot.root(args, ["truncate", "-s", size_mb, img_path])

    # Mount to /dev/install
    mount_image_paths = {img_path_full: "/dev/install"}
    if split:
        mount_image_paths = {img_path_boot: "/dev/installp1",
                             img_path_root: "/dev/installp2"}

    for img_path, mount_point in mount_image_paths.items():
        logging.info("(native) mount " + mount_point +
                     " (" + os.path.basename(img_path) + ")")
        pmb.install.losetup.mount(args, img_path)
        device = pmb.install.losetup.device_by_back_file(args, img_path)
        pmb.helpers.mount.bind_file(args, device,
                                    args.work + "/chroot_native" + mount_point)


def create(args, size_boot, size_root, size_reserve, split, sdcard):
    """
    Create /dev/install (the "install blockdevice").

    :param size_boot: size of the boot partition in MiB
    :param size_root: size of the root partition in MiB
    :param size_reserve: empty partition between root and boot in MiB (pma#463)
    :param split: create separate images for boot and root partitions
    :param sdcard: path to sdcard device (e.g. /dev/mmcblk0) or None
    """
    pmb.helpers.mount.umount_all(
        args, args.work + "/chroot_native/dev/install")
    if sdcard:
        mount_sdcard(args, sdcard)
    else:
        create_and_mount_image(args, size_boot, size_root, size_reserve,
                               split)
