#!/usr/bin/python
import sys
sys.path.insert(0, '/usr/lib/entropy/lib')
sys.path.insert(0, '/usr/lib/entropy/server')
sys.path.insert(0, '/usr/lib/entropy/client')
sys.path.insert(0, '../lib')
sys.path.insert(0, '../server')
sys.path.insert(0, '../client')

import os
import subprocess
import errno
import codecs
import argparse

from entropy.const import etpUi, etpConst, const_convert_to_unicode, \
    const_convert_to_rawstring
from entropy.output import teal, purple, darkgreen, blue, brown, print_info, \
    red, nocolor, print_warning, print_error
from entropy.exceptions import DependenciesNotRemovable
from entropy.i18n import _
from entropy.client.interfaces import Client
from entropy.cli import print_package_info
import entropy.dep
import entropy.tools

# equo modules
from text_ui import install_packages

APP_NAME = os.path.basename(sys.argv[0])
KERNEL_BINARY_VIRTUAL = const_convert_to_unicode("virtual/linux-binary")
KERNELS_DIR = const_convert_to_rawstring("/etc/kernels")
RELEASE_LEVEL = const_convert_to_rawstring("RELEASE_LEVEL")

def _get_kernels(etp_client):
    matches, x_rc = etp_client.atom_match(KERNEL_BINARY_VIRTUAL,
        multi_match = True, multi_repo = True)
    return matches, x_rc

def _remove_tag_from_slot(slot):
    if not hasattr(entropy.dep, "remove_tag_from_slot"):
        # backward compatibility
        return slot[::-1].split(",", 1)[-1][::-1]
    return entropy.dep.remove_tag_from_slot(slot)

def _get_target_tag(etp_client, kernel_match):
    try:
        matches = etp_client.get_reverse_queue([kernel_match],
            recursive = False)
    except DependenciesNotRemovable:
        # wtf should not happen
        raise
    tags = set()
    for pkg_id, pkg_repo in matches:
        tag = etp_client.open_repository(pkg_repo).retrieveTag(pkg_id)
        if tag:
            tags.add(tag)

    if tags:
        tags = sorted(tags, reverse = True)
        return tags.pop(0)

def _setup_kernel_symlink(target_tag):
    eselect_exec = "/usr/bin/eselect"
    if os.access(eselect_exec, os.X_OK):
        subprocess.call((eselect_exec, "kernel", "set", target_tag))

def _guess_kernel_name(kernel_atom):
    """
    This method takes advantage of Entropy kernel package info files available
    at /etc/kernels/<pkg-name>-<pkg-ver>/ directory.
    This function tries to read uname -r from the RELEASE_LEVEL file.
    """
    namever = entropy.dep.remove_cat(kernel_atom)
    kernel_meta_file = os.path.join(KERNELS_DIR, namever, RELEASE_LEVEL)
    if os.path.isfile(kernel_meta_file):
        try:
            with open(kernel_meta_file, "r") as km_f:
                kernel_name = km_f.readline().strip()
                if kernel_name:
                    return kernel_name
        except (OSError, IOError):
            return None

def _guess_kernel_package_file(release_level):
    """
    This method takes advantage of Entropy kernel package info files available
    at /etc/kernels/<pkg-name>-<pkg-ver>/ directory looking for
    a RELEASE_LEVEL file whose content matches release_level (uname -r).
    """
    if not os.path.isdir(KERNELS_DIR):
        return None

    subs = []
    for curdir, _subs, files in os.walk(KERNELS_DIR):
        subs.extend(_subs)

    for sub in subs:
        sub_path = os.path.join(KERNELS_DIR, sub)
        try:
            dir_list = os.listdir(sub_path)
        except OSError as err:
            if err.errno != errno.ENOENT:
                raise
            continue
        if RELEASE_LEVEL not in dir_list:
            continue

        level_path = os.path.join(
            sub_path, RELEASE_LEVEL)
        with codecs.open(
            level_path, "r", etpConst['conf_raw_encoding']) as rel_f:
            rel_line = rel_f.readline().strip()

        if release_level == rel_line:
            return const_convert_to_unicode(level_path)

# removing and installing proprietary drivers might reset the selected
# OpenGL implementation

def _get_opengl_impl():
    eselect_exec = "/usr/bin/eselect"
    sts = 1
    out = "xorg-x11"
    if os.access(eselect_exec, os.X_OK) and not etpUi['pretend']:
        sts, xout = entropy.tools.getstatusoutput("%s opengl show" % (
            eselect_exec,))
        if sts == 0:
            out = xout
    return out

def _set_opengl_impl(opengl):
    eselect_exec = "/usr/bin/eselect"
    if os.access(eselect_exec, os.X_OK):
        args = (eselect_exec, "opengl", "set", opengl)
        subprocess.call(args)

def _show_kernel_warnings(kernel_atom):
    print_info("%s %s" % (purple(kernel_atom), teal(_("has been installed."))))
    print_warning("%s: %s" % (red(_("Attention")),
        brown(_("some external drivers cannot work across multiple kernels."))))
    print_warning(darkgreen(_("Please reboot your computer now !")))

def _switch_kernel(nsargs):

    from_running = nsargs.from_running
    kernel_package = nsargs.kernel

    etp_client = None
    acquired = False
    try:
        etp_client = Client()
        acquired = entropy.tools.acquire_entropy_locks(etp_client)
        if not acquired:
            print_error(brown(_("Another Entropy is currently running.")))
            return 1

        pkg_id, pkg_repo = etp_client.atom_match(kernel_package)
        if pkg_id == -1:
            print_error("%s: %s" % (brown(_("Package does not exist")),
                teal(kernel_package),))
            return 1
        kernel_matches, rc = _get_kernels(etp_client)

        kernel_match = (pkg_id, pkg_repo)
        if kernel_match not in kernel_matches:
            print_error("%s: %s" % (brown(_("Not a kernel")),
                teal(kernel_package),))
            return 1

        kernel_atom = etp_client.open_repository(pkg_repo).retrieveAtom(pkg_id)
        # this can be None !
        target_tag = _get_target_tag(etp_client, kernel_match)

        inst_repo = etp_client.installed_repository()
        # try to look for the currently running kernel first if
        # --from-running is specified (use uname -r)
        latest_kernel = -1
        k_rc = 1
        if from_running:
            try:
                uname_r = os.uname()[2]
            except OSError:
                uname_r = None
            except IndexError:
                uname_r = None

            pkg_file = None
            if uname_r is not None:
                pkg_file = _guess_kernel_package_file(uname_r)
            if pkg_file is not None:
                _pkg_ids = list(inst_repo.searchBelongs(pkg_file))
                # if more than one, get the latest
                _pkg_ids.sort(reverse=True)
                if _pkg_ids:
                    latest_kernel = _pkg_ids[0]
            if latest_kernel == -1:
                print_error(
                    brown(_("Cannot find your currently running kernel.")))
                print_error(
                    brown(_("Try without --from-running.")))
                return 1

        if latest_kernel == -1:
            latest_kernel, k_rc = inst_repo.atomMatch(
                KERNEL_BINARY_VIRTUAL)
        installed_revdeps = []
        if (latest_kernel != -1) and target_tag:
            installed_revdeps = etp_client.get_removal_queue(
                [latest_kernel], recursive = False)

        # only pull in packages that are installed at this time.
        def _installed_pkgs_translator(inst_pkg_id):
            if inst_pkg_id == latest_kernel:
                # will be added later
                return None
            key, slot = inst_repo.retrieveKeySlot(inst_pkg_id)
            target_slot = _remove_tag_from_slot(slot) + "," + target_tag

            pkg_id, pkg_repo = etp_client.atom_match(key,
                match_slot = target_slot)
            if pkg_id == -1:
                return None
            return pkg_id, pkg_repo

        matches = map(_installed_pkgs_translator, installed_revdeps)
        matches = [x for x in matches if x is not None]
        matches.append(kernel_match)

        opengl = _get_opengl_impl()
        rc, other = install_packages(
            etp_client,
            atomsdata = matches,
            check_critical_updates = False
        )
        if (rc == 0) and (not etpUi['pretend']):
            _set_opengl_impl(opengl)
            if target_tag:
                # if target_tag is None, we are unable to set the symlink
                _setup_kernel_symlink(target_tag)
            else:
                # try to guess, sigh, for now
                guessed_kernel_name = _guess_kernel_name(kernel_atom)
                if guessed_kernel_name:
                    _setup_kernel_symlink(guessed_kernel_name)
            _show_kernel_warnings(kernel_atom)
        return rc

    finally:
        if acquired and (etp_client is not None):
            entropy.tools.release_entropy_locks(etp_client)
        if etp_client is not None:
            etp_client.shutdown()

def _list_kernels(nsargs):
    etp_client = None
    acquired = False
    try:
        etp_client = Client()
        acquired = entropy.tools.acquire_entropy_locks(etp_client)
        if not acquired:
            print_error(brown(_("Another Entropy is currently running.")))
            return 1
        matches, rc = _get_kernels(etp_client)
        if (rc != 0) or (not matches):
            print_warning(_("No kernel packages found"))
            return 1

        key_sorter = lambda x: \
            etp_client.open_repository(x[1]).retrieveAtom(x[0])
        for pkg_id, pkg_repo in sorted(matches, key = key_sorter):
            repo = etp_client.open_repository(pkg_repo)
            print_package_info(pkg_id, etp_client, repo,
                show_repo_if_quiet = True, extended = etpUi['verbose'],
                quiet = etpUi['quiet'])

        return 0
    finally:
        if acquired and (etp_client is not None):
            entropy.tools.release_entropy_locks(etp_client)
        if etp_client is not None:
            etp_client.shutdown()

if __name__ == "__main__":

    parser = argparse.ArgumentParser(
        description=_("Sabayon Kernel Switcher"),
        formatter_class=argparse.RawDescriptionHelpFormatter,
        prog=APP_NAME)

    def _add_standard_args(_parser, restricted):
        _parser.add_argument("--quiet", "-q", action="store_true",
                            default=False,
                            help=_("quiet mode"))
        if not restricted:
            _parser.add_argument("--ask", "-a", action="store_true",
                                 default=False,
                                 help=_("ask confirmation"))
            _parser.add_argument("--pretend", "-p", action="store_true",
                                 default=False,
                                 help=_("just show what would be done"))

    subparsers = parser.add_subparsers(
        title="kernel-switcher",
        description=_("available commands"))

    switch_parser = subparsers.add_parser(
        "switch", help=_("install a new or just another kernel"))
    switch_parser.set_defaults(func=_switch_kernel)
    switch_parser.add_argument(
        "--from-running", action="store_true",
        default=False,
        help=_("use 'uname -r' to determine the running kernel"))
    switch_parser.add_argument(
        "kernel", metavar="<kernel>",
        help=_("the new kernel package dependency name"))
    _add_standard_args(switch_parser, False)

    list_parser = subparsers.add_parser(
        "list", help=_("list kernels"))
    list_parser.set_defaults(func=_list_kernels)
    _add_standard_args(list_parser, True)

    help_parser = subparsers.add_parser(
        "help", help=_("this help"))
    def _print_help(*args):
        parser.print_help()
    help_parser.set_defaults(func=_print_help)

    try:
        nsargs = parser.parse_args(sys.argv[1:])
    except IOError as err:
        parser.print_help()
        raise SystemExit(1)

    etpUi['ask'] = getattr(nsargs, "ask", etpUi['ask'])
    etpUi['quiet'] = getattr(nsargs, "quiet", etpUi['quiet'])
    etpUi['pretend'] = getattr(nsargs, "pretend", etpUi['pretend'])

    try:
        rc = nsargs.func(nsargs)
    except KeyboardInterrupt:
        rc = 1
    raise SystemExit(rc)
