#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Name:     install.py
# Purpose:  This module contain functions to install ISO files to USB disk non destructively.
# Authors:  Sundar
# Licence:  This file is a part of multibootusb package. You can redistribute it or modify
# under the terms of GNU General Public License, v.2 or above

import lzma
import os
import platform
import shutil
import subprocess
import threading
import time
from .usb import *
from .gen import *
# from .iso import *
from . import iso
from scripts.update_cfg_file import *
from . import config
from . import persistence


def install_distro():
    """
    Install selected ISO to USB disk.
    :return:
    """
    usb_mount = config.usb_mount
    install_dir = os.path.join(config.usb_mount, "multibootusb",
                               iso_basename(config.image_path))

    if not os.path.exists(os.path.join(usb_mount, "multibootusb")):
        log("Copying multibootusb directory to " + usb_mount)
        shutil.copytree(
            resource_path(os.path.join("data", "tools", "multibootusb")),
            os.path.join(config.usb_mount, "multibootusb"))

    if not os.path.exists(install_dir):
        _iso_file_list = iso.iso_file_list(config.image_path)
        os.makedirs(install_dir)
        with open(os.path.join(install_dir, "multibootusb.cfg"), "w") as f:
            f.write(config.distro)
        with open(os.path.join(install_dir, "iso_file_list.cfg"), 'w') as f:
            for file_path in _iso_file_list:
                f.write(file_path + "\n")
    else:
        # This path is usually not taken.
        with open(os.path.join(install_dir, "multibootusb.cfg"), "r") as f:
            assert config.distro == f.read()
        with open(os.path.join(install_dir, "iso_file_list.cfg"), 'r') as f:
            _iso_file_list = [s.strip() for s in f.readlines()]

    log("Installing " + iso_name(config.image_path) + " on " + install_dir)

    # Some distros requires certain directories be at the root.
    relocator = DirectoryRelocator(install_dir, usb_mount)

    if config.distro == "opensuse":
        iso.iso_extract_file(config.image_path, install_dir, 'boot')
        config.status_text = "Copying ISO..."
        log("Copying " + config.image_path + " to " + usb_mount)
        copy_iso(config.image_path, usb_mount)
    elif config.distro == "Windows" or config.distro == 'pc-unlocker'\
            or config.distro == 'pc-tool' or config.distro == 'grub2only':
        log("Extracting iso to " + usb_mount)
        iso_extract_full(config.image_path, usb_mount)
    elif config.distro == "trinity-rescue":
        iso_extract_full(config.image_path, install_dir)
        relocator.move(('trk3',))
    elif config.distro == "ipfire":
        iso.iso_extract_file(config.image_path, usb_mount,
                             ['*.tlz', 'distro.img'])
        iso.iso_extract_file(config.image_path, install_dir, 'boot')
    elif config.distro == "zenwalk":
        config.status_text = "Copying ISO..."
        iso.iso_extract_file(config.image_path, install_dir, "kernel")
        copy_iso(config.image_path, install_dir)
    elif config.distro in ["salix-live", 'wifislax']:
        # iso.iso_extract_file(config.image_path, install_dir, "boot")
        iso.iso_extract_file(config.image_path, install_dir,
                             ['*syslinux', '*isolinux', '*system_tools', '*menus', '*vmlinuz', '*initrd*',
                              'EFI'])
        iso.iso_extract_file(config.image_path, usb_mount,
                             ['*modules', '*packages', '*optional',
                              '*liveboot'])
        #iso.iso_extract_full(config.image_path, usb_mount)
        config.status_text = "Copying ISO..."
        copy_iso(config.image_path, install_dir)
    elif config.distro == "rising-av":
        iso.iso_extract_file(config.image_path, install_dir, '*boot')
        iso.iso_extract_file(config.image_path, usb_mount, '*rising')
    elif config.distro == 'sgrubd2':
        copy_iso(config.image_path, install_dir)
    elif config.distro == 'alt-linux':
        iso.iso_extract_file(config.image_path, install_dir, '-xr!*rescue')
        iso.iso_extract_file(config.image_path, config.usb_mount, 'rescue')
    elif config.distro == "generic":
        #with open(os.path.join(install_dir, "generic.cfg"), "w") as f:
        #    f.write(os.path.join(isolinux_bin_dir(config.image_path), "generic") + ".bs")
        iso_extract_full(config.image_path, usb_mount)
    elif config.distro == 'grub4dos':
        iso_extract_full(config.image_path, usb_mount)
    elif config.distro == 'ReactOS':
        iso_extract_full(config.image_path, usb_mount)
    elif config.distro == 'grub4dos_iso' or config.distro == 'raw_iso' or config.distro == 'memdisk_iso' or \
                    config.distro == 'memdisk_img':
        copy_iso(config.image_path, install_dir)
    elif config.distro == 'Avira-RS':
        iso_extract_full(config.image_path, install_dir)
        # we want following directories on root of the USB drive. Ensure the previous directories are removed before moving.
        relocator.move(('antivir', 'avupdate', 'system'))
    elif config.distro == 'alpine':
        iso_extract_full(config.image_path, install_dir)
        relocator.move(('apks',))
    elif config.distro == 'insert':
        iso_extract_full(config.image_path, install_dir)
        relocator.move(('INSERT',))
    elif config.distro == 'centos-install' and \
      any(f=='.treeinfo' for f in _iso_file_list):
        # DVD installer
        iso.iso_extract_file(config.image_path, install_dir, '-xr-!Packages')
        log("Copying the source iso file as is.")
        copy_iso(config.image_path, install_dir)
    else:
        iso.iso_extract_full(config.image_path, install_dir)


    if config.persistence != 0:
        log('Creating persistence...')
        config.status_text = 'Creating persistence...'
        persistence.create_persistence()

    install_patch()


def copy_iso(src, dst):
    """
    A simple wrapper for copying larger files. This is necessary as
    shutil copy files is much slower under Windows platform
    :param src: Path to source file
    :param dst: Destination directory
    :return:
    """
    if platform.system() == "Windows":
        # Note that xcopy asks if the target is a file or a directory when
        # source filename (or dest filename) contains space(s) and the target
        # does not exist.
        assert os.path.exists(dst)
        subprocess.call(['xcopy', '/Y', src, dst], shell=True)
    elif platform.system() == "Linux":
        shutil.copy(src, dst)


def install_progress():
    """
    Function to calculate progress percentage of install.
    :return:
    """
    from . import progressbar
    try:
        usb_details = details(config.usb_disk)
    except PartitionNotMounted as e:
        log(str(e))
        return

    config.usb_mount = usb_details['mount_point']
    usb_size_used = usb_details['size_used']
    thrd = threading.Thread(target=install_distro, name="install_progress")
    # thrd.daemon()
    # install_size = usb_size_used / 1024
#     install_size = iso_size(config.image_path) / 1024
    final_size = (usb_size_used + iso_size(config.image_path)) + config.persistence
    thrd.start()
    pbar = progressbar.ProgressBar(maxval=100).start()  # bar = progressbar.ProgressBar(redirect_stdout=True)
    while thrd.is_alive():
        current_size = shutil.disk_usage(usb_details['mount_point'])[1]
        percentage = int((current_size / final_size) * 100)
        if percentage > 100:
            percentage = 100
        config.percentage = percentage
        pbar.update(percentage)
        time.sleep(0.1)


def replace_syslinux_modules(syslinux_version, under_this_dir):
    # Replace modules files extracted from iso with corresponding
    # version provided by multibootusb.
    modules_src_dir = os.path.join(
        multibootusb_host_dir(), "syslinux", "modules", syslinux_version)

    for dirpath, dirnames, filenames in os.walk(under_this_dir):
        for fname in filenames:
            if not fname.lower().endswith('.c32'):
                continue
            dst_path = os.path.join(under_this_dir, dirpath, fname)
            src_path = os.path.join(modules_src_dir, fname)
            if not os.path.exists(src_path):
                log("Suitable replacement of '%s' is not bundled. "
                    "Trying to unlzma." % fname)
                try:
                    with lzma.open(dst_path) as f:
                        expanded = f.read()
                except lzma.LZMAError:
                    continue
                except (OSError, IOError) as e:
                    log("%s while accessing %s." % (e, dst_path))
                    continue
                with open(dst_path, 'wb') as f:
                    f.write(expanded)
                log("Successfully decompressed %s." % fname)
                continue
            try:
                os.remove(dst_path)
                shutil.copy(src_path, dst_path)
                log("Replaced %s module" % fname)
            except (OSError, IOError) as err:
                log(err)
                log("Could not update " + fname)

def install_patch():
    """
    Function to certain distros which uses makeboot.sh script for making bootable usb disk.
    This is required to make sure that same version (32/64 bit) of modules present is the isolinux directory
    :return:
    """
    isobin_path = isolinux_bin_path(config.image_path)
    if not isobin_path:
        return

    iso_cfg_ext_dir = os.path.join(multibootusb_host_dir(),
                                   "iso_cfg_ext_dir")
    isolinux_path = os.path.join(iso_cfg_ext_dir, isobin_path)
#   iso_linux_bin_dir = isolinux_bin_dir(config.image_path)
    distro_install_dir = os.path.join(
        config.usb_mount, "multibootusb", iso_basename(config.image_path))
    config.syslinux_version = isolinux_version(isolinux_path)

    if config.distro in ['slitaz', 'ubunu']:
        replace_syslinux_modules(config.syslinux_version, distro_install_dir)
    elif config.distro == 'gentoo':
        replace_syslinux_modules(config.syslinux_version, distro_install_dir)
    elif config.distro == 'debian':
        iso_file_list = iso.iso_file_list(config.image_path)
        if not any(s.strip().lower().endswith("makeboot.sh")
                   for s in iso_file_list):
            log('Patch not required...')
            return

        isolinux_bin_dir_ = os.path.join(
            distro_install_dir, isolinux_bin_dir(config.image_path))
        replace_syslinux_modules(config.syslinux_version, isolinux_bin_dir_)


class DirectoryRelocator:
    def __init__(self, src_dir, dst_dir):
        self.src_dir = src_dir
        self.dst_dir = dst_dir

    def move(self, dirs):
        for dir_name in dirs:
            log('Relocating %s from %s to %s' %
                (dir_name, self.src_dir, self.dst_dir))
            src = os.path.join(self.src_dir, dir_name)
            dst = os.path.join(self.dst_dir, dir_name)
            if os.path.exists(dst):
                shutil.rmtree(dst)
            shutil.move(src, dst)

if __name__ == '__main__':
    config.image_path = '../../../DISTROS/2016/slitaz-4.0.iso'
    install_distro()
