# -*- coding: utf-8 -*-
'''
VirtualBox Guest Additions installer
'''
from __future__ import absolute_import

# Import python libs
import contextlib
import functools
import glob
import logging
import os
import re
import tempfile


log = logging.getLogger(__name__)
__virtualname__ = 'vbox_guest'
_additions_dir_prefix = 'VBoxGuestAdditions'
_shared_folders_group = 'vboxsf'


def __virtual__():
    '''
    Set the vbox_guest module if the OS Linux
    '''
    if __grains__.get('kernel', '') not in ('Linux', ):
        return False
    return __virtualname__


def additions_mount():
    '''
    Mount VirtualBox Guest Additions CD to the temp directory.

    To connect VirtualBox Guest Additions via VirtualBox graphical interface
    press 'Host+D' ('Host' is usually 'Right Ctrl').

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.additions_mount

    :return: True or OSError exception
    '''
    mount_point = tempfile.mkdtemp()
    ret = __salt__['mount.mount'](mount_point, '/dev/cdrom')
    if ret is True:
        return mount_point
    else:
        raise OSError(ret)


def additions_umount(mount_point):
    '''
    Unmount VirtualBox Guest Additions CD from the temp directory.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.additions_umount

    :param mount_point: directory VirtualBox Guest Additions is mounted to
    :return: True or an string with error
    '''
    ret = __salt__['mount.umount'](mount_point)
    if ret:
        os.rmdir(mount_point)
    return ret


@contextlib.contextmanager
def _additions_mounted():
    mount_point = additions_mount()
    yield mount_point
    additions_umount(mount_point)


def _return_mount_error(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except OSError as e:
            return str(e)
    return wrapper


def _additions_install_program_path(mount_point):
    return os.path.join(mount_point, {
        'Linux': 'VBoxLinuxAdditions.run',
        'Solaris': 'VBoxSolarisAdditions.pkg',
        'Windows': 'VBoxWindowsAdditions.exe'
    }[__grains__.get('kernel', '')])


def _additions_install_opensuse(**kwargs):
    kernel_type = re.sub(
        r'^(\d|\.|-)*', '', __grains__.get('kernelrelease', ''))
    kernel_devel = 'kernel-{0}-devel'.format(kernel_type)
    ret = __salt__['state.single']('pkg.installed', 'devel packages',
                                   pkgs=['make', 'gcc', kernel_devel])
    return ret


def _additions_install_ubuntu(**kwargs):
    ret = __salt__['state.single']('pkg.installed', 'devel packages',
                                   pkgs=['dkms', ])
    return ret


def _additions_install_fedora(**kwargs):
    ret = __salt__['state.single']('pkg.installed', 'devel packages',
                                   pkgs=['dkms', 'gcc'])
    return ret


def _additions_install_linux(mount_point, **kwargs):
    reboot = kwargs.pop('reboot', False)
    restart_x11 = kwargs.pop('restart_x11', False)
    upgrade_os = kwargs.pop('upgrade_os', False)
    if upgrade_os:
        __salt__['pkg.upgrade']()
    # dangerous: do not call variable `os` as it will hide os module
    guest_os = __grains__.get('os', '')
    if guest_os == 'openSUSE':
        _additions_install_opensuse(**kwargs)
    elif guest_os == 'ubuntu':
        _additions_install_ubuntu(**kwargs)
    elif guest_os == 'fedora':
        _additions_install_fedora(**kwargs)
    else:
        log.warning("{0} is not fully supported yet.".format(guest_os))
    installer_path = _additions_install_program_path(mount_point)
    installer_ret = __salt__['cmd.run_all'](installer_path)
    if installer_ret['retcode'] in (0, 1):
        if reboot:
            __salt__['system.reboot']()
        elif restart_x11:
            raise NotImplementedError("Restarting x11 is not supported yet.")
        else:
            # VirtualBox script enables module itself, need to restart OS
            # anyway, probably don't need that.
            # for service in ('vboxadd', 'vboxadd-service', 'vboxadd-x11'):
            #     __salt__['service.start'](service)
            pass
        return additions_version()
    elif installer_ret['retcode'] in (127, '127'):
        return ("'{0}' not found on CD. Make sure that VirtualBox Guest "
                "Additions CD is attached to the CD IDE Controller.".format(
                    os.path.basename(installer_path)))
    else:
        return installer_ret['stderr']


@_return_mount_error
def additions_install(**kwargs):
    '''
    Install VirtualBox Guest Additions. Uses the CD, connected by VirtualBox.

    To connect VirtualBox Guest Additions via VirtualBox graphical interface
    press 'Host+D' ('Host' is usually 'Right Ctrl').

    See https://www.virtualbox.org/manual/ch04.html#idp52733088 for more details.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.additions_install
        salt '*' vbox_guest.additions_install reboot=True
        salt '*' vbox_guest.additions_install upgrade_os=True

    :param reboot: reboot computer to complete installation
    :type reboot: bool
    :param upgrade_os: upgrade OS (to ensure the latests version of kernel and developer tools are installed)
    :type upgrade_os: bool
    :return: version of VirtualBox Guest Additions or string with error
    '''
    with _additions_mounted() as mount_point:
        kernel = __grains__.get('kernel', '')
        if kernel == 'Linux':
            return _additions_install_linux(mount_point, **kwargs)


def _additions_dir():
    root = '/opt'
    dirs = glob.glob(os.path.join(root, _additions_dir_prefix) + '*')
    if dirs:
        return dirs[0]
    else:
        raise EnvironmentError('No VirtualBox Guest Additions dirs found!')


def _additions_remove_linux_run(cmd):
    uninstaller_ret = __salt__['cmd.run_all'](cmd)
    return uninstaller_ret['retcode'] in (0, )


def _additions_remove_linux(**kwargs):
    try:
        return _additions_remove_linux_run(
                os.path.join(_additions_dir(), 'uninstall.sh'))
    except EnvironmentError:
        return False


def _additions_remove_linux_use_cd(mount_point, **kwargs):
    force = kwargs.pop('force', False)
    args = ''
    if force:
        args += '--force'
    return _additions_remove_linux_run('{program} uninstall {args}'.format(
        program=_additions_install_program_path(mount_point), args=args))


@_return_mount_error
def _additions_remove_use_cd(**kwargs):
    '''
    Remove VirtualBox Guest Additions.

    It uses the CD, connected by VirtualBox.
    '''

    with _additions_mounted() as mount_point:
        kernel = __grains__.get('kernel', '')
        if kernel == 'Linux':
            return _additions_remove_linux_use_cd(mount_point, **kwargs)


def additions_remove(**kwargs):
    '''
    Remove VirtualBox Guest Additions.

    Firstly it tries to uninstall itself by executing
    '/opt/VBoxGuestAdditions-VERSION/uninstall.run uninstall'.
    It uses the CD, connected by VirtualBox if it failes.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.additions_remove
        salt '*' vbox_guest.additions_remove force=True

    :param force: force VirtualBox Guest Additions removing
    :type force: bool
    :return: True if VirtualBox Guest Additions were removed successfully else False

    '''
    kernel = __grains__.get('kernel', '')
    if kernel == 'Linux':
        ret = _additions_remove_linux()
    if not ret:
        ret = _additions_remove_use_cd(**kwargs)
    return ret


def additions_version():
    '''
    Check VirtualBox Guest Additions version.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.additions_version

    :return: version of VirtualBox Guest Additions or False if they are not installed
    '''
    try:
        d = _additions_dir()
    except EnvironmentError:
        return False
    if d and len(os.listdir(d)) > 0:
        return re.sub(r'^{0}-'.format(_additions_dir_prefix), '',
                os.path.basename(d))
    return False


def grant_access_to_shared_folders_to(name, users=None):
    '''
    Grant access to auto-mounted shared folders to the users.

    User is specified by it's name. To grant access for several users use argument `users`.
    Access will be denied to the users not listed in `users` argument.

    See https://www.virtualbox.org/manual/ch04.html#sf_mount_auto for more details.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.grant_access_to_shared_folders_to fred
        salt '*' vbox_guest.grant_access_to_shared_folders_to users ['fred', 'roman']

    :param name: name of the user to grant access to auto-mounted shared folders to
    :type name: str
    :param users: list of names of users to grant access to auto-mounted shared folders to (if specified, `name` will not be taken into account)
    :type users: list of str
    :return: list of users who have access to auto-mounted shared folders
    '''
    if users is None:
        users = [name]
    if __salt__['group.members'](_shared_folders_group, ','.join(users)):
        return users
    else:
        if not __salt__['group.info'](_shared_folders_group):
            if not additions_version:
                return ("VirtualBox Guest Additions are not installed. Ιnstall "
                        "them firstly. You can do it with the help of command "
                        "vbox_guest.additions_install.")
            else:
                return (
                    "VirtualBox Guest Additions seems to be installed, but "
                    "group '{0}' not found. Check your installation and fix "
                    "it. You can uninstall VirtualBox Guest Additions with "
                    "the help of command :py:func:`vbox_guest.additions_remove "
                    "<salt.modules.vbox_guest.additions_remove> (it has "
                    "`force` argument to fix complex situations; use "
                    "it with care) and then install it again. You can do "
                    "it with the help of :py:func:`vbox_guest.additions_install "
                    "<salt.modules.vbox_guest.additions_install>`."
                    "".format(_shared_folders_group))
        else:
            return ("Cannot replace members of the '{0}' group."
                    "".format(_shared_folders_group))


def list_shared_folders_users():
    '''
    List users who have access to auto-mounted shared folders.

    See https://www.virtualbox.org/manual/ch04.html#sf_mount_auto for more details.

    CLI Example:

    .. code-block:: bash

        salt '*' vbox_guest.list_shared_folders_users

    :return: list of users who have access to auto-mounted shared folders
    '''
    try:
        return __salt__['group.info'](_shared_folders_group)['members']
    except KeyError:
        return []
