# Copyright (C) 2009 Canonical
#
# Authors:
#  Michael Vogt
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import aptsources.distro
from datetime import datetime
import distro_info
from functools import wraps
import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Handy", "1")
from gi.repository import Gio, Gtk, GLib, Handy
import json
import os
import subprocess

import logging
LOG=logging.getLogger(__name__)

import time

UA_STATUS_JSON = "/var/lib/ubuntu-advantage/status.json"

def setup_ui(self, path, domain):
    # setup ui
    self.builder = Gtk.Builder()
    self.builder.set_translation_domain(domain)
    self.builder.add_from_file(path)
    self.builder.connect_signals(self)
    for o in self.builder.get_objects():
        if issubclass(type(o), Gtk.Buildable):
            name = Gtk.Buildable.get_name(o)
            setattr(self, name, o)
        else:
            logging.debug("can not get name for object '%s'" % o)

def has_gnome_online_accounts():
    try:
        d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop')
        return d != None
    except Exception:
        return False

def is_current_distro_lts():
    distro = aptsources.distro.get_distro()
    di = distro_info.TrisquelDistroInfo()
    return di.is_lts(distro.codename)

def is_current_distro_supported():
    distro = aptsources.distro.get_distro()
    di = distro_info.TrisquelDistroInfo()
    return distro.codename in di.supported(datetime.now().date())

def current_distro():
    distro = aptsources.distro.get_distro()
    di = distro_info.TrisquelDistroInfo()
    releases = di.get_all(result="object")
    for release in releases:
        if release.series == distro.codename:
            return release


def get_ua_status():
    """Return a dict of all UA status information or empty dict on error."""
    # status.json will exist on any attached system. It will also be created
    # by the systemd timer ua-timer which will update UA_STATUS_JSON every 12
    # hours to reflect current status of UA subscription services.
    # Invoking `pro status` with subp will result in a network call to
    # contracts.canonical.com which could raise Timeouts on network limited
    # machines. So, prefer the status.json file when possible.
    status_json = ""
    if os.path.exists(UA_STATUS_JSON):
        with open(UA_STATUS_JSON) as stream:
            status_json = stream.read()
    else:
        try:
            # Success writes UA_STATUS_JSON
            result = subprocess.run(
                ['pro', 'status', '--format=json'], stdout=subprocess.PIPE
            )
        except Exception as e:
            print("Failed to run `pro status`:\n%s" % e)
            return {}
        if result.returncode != 0:
            print(
                "Trisquel Pro client returned code %d" % result.returncode
            )
            return {}
        status_json = result.stdout
    if not status_json:
        print(
            "Warning: no Trisquel Pro Client status found."
            " Is ubuntu-pro-client installed?"
        )
        return {}
    try:
        status = json.loads(status_json)
    except json.JSONDecodeError as e:
        print("Failed to parse ubuntu advantage client JSON:\n%s" % e)
        return {}
    if status.get("_schema_version", "0.1") != "0.1":
        print(
            "Pro status schema version change: %s" % status["_schema_version"]
        )
    return status


def get_ua_service_status(service_name='esm-infra', status=None):
    """Get service availability and status for a specific UA service.

    Return a tuple (available, service_status).
      :boolean available: set True when either:
        - attached contract is entitled to the service
        - unattached machine reports service "availability" as "yes"
      :str service_status: will be one of the following:
        - "disabled" when the service is available and applicable but not
          active
        - "enabled" when the service is available and active
        - "n/a" when the service is not applicable to the environment or not
          entitled for the attached contract
    """
    if not status:
        status = get_ua_status()
    # Assume unattached on empty status dict
    available = False
    service_status = "n/a"
    for service in status.get("services", []):
        if service.get("name") != service_name:
            continue
        if "available" in service:
            available = bool("yes" == service["available"])
        if "status" in service:
            service_status = service["status"]  # enabled, disabled or n/a
    return (available, service_status)


def retry(exceptions, tries=10, delay=0.1, backoff=2):
    """
    Retry calling the decorated function using an exponential backoff.

    Args:
        exceptions: The exception to check. may be a tuple of
            exceptions to check.
        tries: Number of times to try (not retry) before giving up.
        delay: Initial delay between retries in seconds.
        backoff: Backoff multiplier (e.g. value of 2 will double the delay
            each retry).
    """
    def deco_retry(f):

        @wraps(f)
        def f_retry(*args, **kwargs):
            mtries, mdelay = tries, delay
            while mtries > 1:
                try:
                    return f(*args, **kwargs)
                except exceptions as e:
                    msg = '{}, Retrying in {} seconds...'.format(e, mdelay)
                    logging.warning(msg)
                    time.sleep(mdelay)
                    mtries -= 1
                    mdelay *= backoff
            return f(*args, **kwargs)

        return f_retry  # true decorator

    return deco_retry

def is_dark_theme(widget):
    env_gtk_theme = GLib.getenv("GTK_THEME")
    if env_gtk_theme != None:
        return GLib.str_has_suffix(env_gtk_theme, "dark")
    return Handy.StyleManager.get_default().get_dark()
