# python-eduvpn-client - The GNU/Linux eduVPN client and Python API
#
# Copyright: 2017, The Commons Conservancy eduVPN Programme
# SPDX-License-Identifier: GPL-3.0+
import logging
import threading
import uuid
import sys
import os
from os import path
from future.standard_library import install_aliases
install_aliases()
from functools import lru_cache
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gtk, GdkPixbuf, GLib
from eduvpn.config import icon_size
from eduvpn.metadata import Metadata
from eduvpn.exceptions import EduvpnException
from dbus.exceptions import NameExistsException
from typing import Any, Optional, Tuple

logger = logging.getLogger(__name__)


def make_unique_id():  # type: () -> str
    return str(uuid.uuid4())


# ui thread
def error_helper(parent, msg_big, msg_small):  # type: (Gtk.GObject, str, str) -> None
    """
    Shows a GTK error message dialog.

    args:
        parent (GObject): A GTK Window
        msg_big (str): the big string
        msg_small (str): the small string
    """
    logger.error(u"{}: {}".format(msg_big, msg_small))
    error_dialog = Gtk.MessageDialog(parent, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, str(msg_big))
    error_dialog.format_secondary_text(str(msg_small))
    error_dialog.run()
    error_dialog.hide()


def thread_helper(func):  # type: (Any) -> threading.Thread
    """
    Runs a function in a thread

    args:
        func (lambda): a function to run in the background
    """
    thread = threading.Thread(target=func)
    thread.daemon = True
    thread.start()
    return thread


def pil2pixbuf(img):  # type: (Any) -> GdkPixbuf.Pixbuf
    """
    Convert a pillow (pil) object to a pixbuf

    args:
        img: (pil.Image): A pillow image

    returns:
        GtkPixbuf: a GTK Pixbuf
    """
    width, height = img.size
    if img.mode != "RGB":  # gtk only supports RGB
        img = img.convert(mode='RGB')
    bytes = GLib.Bytes(img.tobytes())
    pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(bytes, GdkPixbuf.Colorspace.RGB, False, 8, width, height, width * 3)
    return pixbuf


def bytes2pixbuf(data,
                 width=icon_size['width'],
                 height=icon_size['height'],
                 display_name=None):  # type: (bytes, int, int, Optional[str]) -> GdkPixbuf.Pixbuf
    """
    converts raw bytes into a GTK PixBug

    args:
        data (bytes): raw bytes
        width (int): width of image
        height (int): height of images

    returns:
        GtkPixbuf: a GTK Pixbuf

    """
    loader = GdkPixbuf.PixbufLoader()
    loader.set_size(width, height)
    try:
        loader.write(data)
        loader.close()
    except (GLib.Error, TypeError) as e:
        logger.error(u"can't process icon for {}: {}".format(display_name, str(e)))
    else:
        return loader.get_pixbuf()


@lru_cache(maxsize=1)
def get_prefix():  # type: () -> str
    """
    Returns the Python prefix where eduVPN is installed

    returns:
        str: path to Python installation prefix
    """
    target = 'share/eduvpn/builder/window.ui'
    local = path.dirname(path.dirname(path.abspath(__file__)))
    options = [local, path.expanduser('~/.local'), '/usr/local', sys.prefix]
    for option in options:
        logger.debug(u"looking for '{}' in '{}'".format(target, option))
        if path.isfile(path.join(option, target)):
            return option
    raise Exception("Can't find eduVPN installation")


@lru_cache(maxsize=1)
def have_dbus():
    # type: () -> bool
    try:
        import dbus
        dbus = dbus.SystemBus(private=True)
    except Exception as e:
        logger.error(u"WARNING: dbus daemons not running, eduVPN client functionality limited")
        return False
    else:
        dbus.close()
        return True


@lru_cache(maxsize=1)
def have_dbus_notification_service():
    # type: () -> bool
    try:
        import dbus
        dbus = dbus.SessionBus(private=True)
        proxy = dbus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
    except NameExistsException as e:
        logger.error(u"WARNING: dbus notification service not available, eduVPN client functionality limited")
        dbus.close()
        return False
    except Exception as e:
        logger.error(u"WARNING: dbus daemon not running, eduVPN client functionality limited")
        return False
    else:
        dbus.close()
        return True


@lru_cache(maxsize=1)
def get_pixbuf(logo=None):
    # type: (Optional[str]) -> Tuple[GdkPixbuf.Pixbuf, GdkPixbuf.Pixbuf]
    if not logo:
        logo = path.join(get_prefix(), 'share/eduvpn/eduvpn.png')

    small = GdkPixbuf.Pixbuf.new_from_file_at_scale(logo, icon_size['width'], icon_size['height'], True)
    big = GdkPixbuf.Pixbuf.new_from_file_at_scale(logo, icon_size['width'] * 2, icon_size['height'] * 2, True)
    return small, big


def metadata_of_selected(builder):
    # type: (Gtk.builder) -> Any
    selection = builder.get_object('provider-selection')
    model, treeiter = selection.get_selected()
    if treeiter is None:
        return
    else:
        uuid_, _, _, _ = model[treeiter]
        return Metadata.from_uuid(uuid_)


def detect_distro(release_file='/etc/os-release'):
    # type: (str) -> Tuple[str, str]
    params = {}
    if not os.access(release_file, os.R_OK):
        raise EduvpnException("Can't detect distribution version, '/etc/os-release' doesn't exist.")

    with open(release_file, 'r') as f:
        for line in f.readlines():
            splitted = line.strip().split('=')
            if len(splitted) == 2:
                key, value = splitted
                params[key] = value.strip('""')

    if 'ID' not in params and 'VERSION_ID' not in params:
        raise EduvpnException("Can't detect distribution version, '/etc/os-release' doesn't "
                              "contain ID and VERSION_ID fields")

    return params['ID'], params['VERSION_ID']


def are_we_running_ubuntu1804():
    # type: () -> bool
    try:
        distro, version = detect_distro()
    except EduvpnException as e:
        logger.error(u"can't determine distribution and version: {}".format(e))
        return False
    else:
        if distro == 'ubuntu' and version == '18.04':
            logger.critical("You are running Ubuntu 18.04, which leaks DNS information")
            return True
        else:
            return False