#!/usr/bin/env python3

import argparse
import signal
import subprocess
import sys
from gi.repository import Gio, GLib

PP_NAME = "org.freedesktop.UPower.PowerProfiles"
PP_PATH = "/org/freedesktop/UPower/PowerProfiles"
PP_IFACE = "org.freedesktop.UPower.PowerProfiles"
PROPERTIES_IFACE = "org.freedesktop.DBus.Properties"


def get_proxy():
    try:
        bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
        proxy = Gio.DBusProxy.new_sync(
            bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PROPERTIES_IFACE, None
        )
    except:
        raise
    return proxy


def command(func):
    def wrapper(*args, **kwargs):
        try:
            func(*args, **kwargs)
        except GLib.Error as error:
            sys.stderr.write(
                f"Failed to communicate with power-profiles-daemon: {error}\n"
            )
            sys.exit(1)
        except ValueError as error:
            sys.stderr.write(f"Error: {error}\n")
            sys.exit(1)

    return wrapper


@command
def _version(args):  # pylint: disable=unused-argument
    proxy = get_proxy()
    ver = proxy.Get("(ss)", PP_IFACE, "Version")
    print(ver)


@command
def _get(args):  # pylint: disable=unused-argument
    proxy = get_proxy()
    profile = proxy.Get("(ss)", PP_IFACE, "ActiveProfile")
    print(profile)


@command
def _set(args):
    try:
        proxy = get_proxy()
        proxy.Set(
            "(ssv)", PP_IFACE, "ActiveProfile", GLib.Variant.new_string(args.profile[0])
        )
    except:
        raise


def get_profiles_property(prop):
    try:
        proxy = get_proxy()
    except:
        raise

    profiles = None
    try:
        profiles = proxy.Get("(ss)", PP_IFACE, prop)
    except:
        raise
    return profiles


@command
def _list(args):  # pylint: disable=unused-argument
    try:
        profiles = get_profiles_property("Profiles")
        reason = get_proxy().Get("(ss)", PP_IFACE, "PerformanceDegraded")
        degraded = reason != ""
        active = get_proxy().Get("(ss)", PP_IFACE, "ActiveProfile")
    except:
        raise

    index = 0
    for profile in reversed(profiles):
        if index > 0:
            print("")
        marker = "*" if profile["Profile"] == active else " "
        print(f'{marker} {profile["Profile"]}:')
        for driver in ["CpuDriver", "PlatformDriver"]:
            if driver not in profile:
                continue
            value = profile[driver]
            print(f"    {driver}:\t{value}")
        if profile["Profile"] == "performance":
            print("    Degraded:  ", f"yes ({reason})" if degraded else "no")
        index += 1


@command
def _list_holds(args):  # pylint: disable=unused-argument
    try:
        holds = get_profiles_property("ActiveProfileHolds")
    except:
        raise

    index = 0
    for hold in holds:
        if index > 0:
            print("")
        print("Hold:")
        print("  Profile:        ", hold["Profile"])
        print("  Application ID: ", hold["ApplicationId"])
        print("  Reason:         ", hold["Reason"])
        index += 1


@command
def _launch(args):
    reason = args.reason
    profile = args.profile
    appid = args.appid
    if not args.arguments:
        raise ValueError("No command to launch")
    if not args.appid:
        appid = args.arguments[0]
    if not profile:
        profile = "performance"
    if not reason:
        reason = f"Running {args.appid}"
    ret = 0
    try:
        bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
        proxy = Gio.DBusProxy.new_sync(
            bus, Gio.DBusProxyFlags.NONE, None, PP_NAME, PP_PATH, PP_IFACE, None
        )
    except:
        raise

    cookie = proxy.HoldProfile("(sss)", profile, reason, appid)

    # Kill child when we go away
    def receive_signal(_signum, _stack):
        launched_app.terminate()

    signal.signal(signal.SIGTERM, receive_signal)

    # print (f'Got {cookie} for {profile} hold')
    with subprocess.Popen(args.arguments) as launched_app:
        try:
            launched_app.wait()
            ret = launched_app.returncode
        except KeyboardInterrupt:
            ret = launched_app.returncode
    proxy.ReleaseProfile("(u)", cookie)

    return ret


def main():
    parser = argparse.ArgumentParser(
        epilog="Use “powerprofilesctl COMMAND --help” to get detailed help for individual commands",
    )
    subparsers = parser.add_subparsers(help="Individual command help", dest="command")
    parser_list = subparsers.add_parser("list", help="List available power profiles")
    parser_list.set_defaults(func=_list)
    parser_list_holds = subparsers.add_parser(
        "list-holds", help="List current power profile holds"
    )
    parser_list_holds.set_defaults(func=_list_holds)
    parser_get = subparsers.add_parser(
        "get", help="Print the currently active power profile"
    )
    parser_get.set_defaults(func=_get)
    parser_set = subparsers.add_parser(
        "set", help="Set the currently active power profile"
    )
    parser_set.add_argument(
        "profile",
        nargs=1,
        help="Profile to use for set command",
    )
    parser_set.set_defaults(func=_set)
    parser_launch = subparsers.add_parser(
        "launch",
        help="Launch a command while holding a power profile",
        description="Launch the command while holding a power profile,"
        "either performance, or power-saver. By default, the profile hold "
        "is for the performance profile, but it might not be available on "
        "all systems. See the list command for a list of available profiles.",
    )
    parser_launch.add_argument(
        "arguments",
        nargs="*",
        help="Command to launch",
    )
    parser_launch.add_argument(
        "--profile", "-p", required=False, help="Profile to use for launch command"
    )
    parser_launch.add_argument(
        "--reason", "-r", required=False, help="Reason to use for launch command"
    )
    parser_launch.add_argument(
        "--appid", "-i", required=False, help="AppId to use for launch command"
    )
    parser_launch.set_defaults(func=_launch)
    parser_version = subparsers.add_parser(
        "version", help="Print version information and exit"
    )
    parser_version.set_defaults(func=_version)

    args = parser.parse_args()
    # default behavior is to run list if no command is given
    if not args.command:
        args.func = _list
    args.func(args)


if __name__ == "__main__":
    main()
