#!/usr/bin/env python3
#
#  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; either version 2 of the License, or
#  (at your option) any later version.
#
#  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.
#

import argparse
import logging
import os
import pwd
import sys
import multiprocessing
from multiprocessing import reduction

manager = None
logger = None


def open_uhid_process(queue_in, conn_out):
    while True:
        try:
            pid = queue_in.get()
        except KeyboardInterrupt:
            return 0
        else:
            fd = os.open('/dev/uhid', os.O_RDWR)
            reduction.send_handle(conn_out, fd, pid)


def maybe_start_tuhi(queue):
    sys.path

    try:
        should_start, verbose = queue.get()
    except KeyboardInterrupt:
        return 0

    if not should_start:
        return

    sys.path.append(os.getcwd())

    import tuhi.base
    if verbose:
        tuhi.base.logger.setLevel(logging.DEBUG)
    t = tuhi.base.Tuhi()
    while True:
        try:
            t.run()
        except KeyboardInterrupt:
            pass


def start_tuhi_server(args):
    queue = multiprocessing.Queue()

    tuhi_process = multiprocessing.Process(target=maybe_start_tuhi, args=(queue,))
    tuhi_process.daemon = True
    tuhi_process.start()

    sys.path.append(os.path.join(os.getcwd(), 'tools'))

    # import after spawning the process, or the 2 processes will fight for GLib
    import kete
    from gi.repository import Gio, GLib

    global logger
    logger = logging.getLogger('tuhi-live')
    logger.addHandler(kete.logger_handler)
    logger.setLevel(logging.INFO)

    logger.debug('connecting to the bus')

    # connect to the session
    try:
        connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
    except GLib.Error as e:
        if (e.domain == 'g-io-error-quark' and
                e.code == Gio.IOErrorEnum.DBUS_ERROR):
            raise kete.DBusError(e.message)
        else:
            raise e

    logger.debug('looking for tuhi on the bus')
    # attempt to connect to tuhi
    try:
        proxy = Gio.DBusProxy.new_sync(connection,
                                       Gio.DBusProxyFlags.NONE, None,
                                       kete.TUHI_DBUS_NAME,
                                       kete.ROOT_PATH,
                                       kete.ORG_FREEDESKTOP_TUHI1_MANAGER,
                                       None)
    except GLib.Error as e:
        if (e.domain == 'g-io-error-quark' and
                e.code == Gio.IOErrorEnum.DBUS_ERROR):
            raise kete.DBusError(e.message)
        else:
            raise e

    started = proxy.get_name_owner() is not None

    if not started:
        print(f'No-one is handling {kete.TUHI_DBUS_NAME}, attempting to start a daemon')

    queue.put((not started, args.verbose))


def run_live(request_fd_queue, conn_fd):
    import kete
    from gi.repository import Gio, GLib

    def on_name_appeared(connection, name, client):
        global manager
        logger.info('Connected to the Tuhi daemon')
        manager = kete.TuhiKeteManager()

        for device in manager.devices:
            logger.info(f'starting live on {device}, please press button on the device')
            request_fd_queue.put(os.getpid())
            fd = reduction.recv_handle(conn_fd)
            device.start_live(fd)

    Gio.bus_watch_name(Gio.BusType.SESSION,
                       kete.TUHI_DBUS_NAME,
                       Gio.BusNameWatcherFlags.NONE,
                       on_name_appeared,
                       None)

    mainloop = GLib.MainLoop()

    connected_devices = 0

    def on_disconnect(dev, pspec):
        mainloop.quit()

    wait_for_disconnect = False

    try:
        mainloop.run()
    except KeyboardInterrupt:
        pass
    finally:
        for device in manager.devices:
            if device.live:
                connected_devices += 1

        for device in manager.devices:
            if device.live and device.connected:
                logger.info(f'stopping live on {device}')
                device.connect('notify::connected', on_disconnect)
                device.stop_live()
                wait_for_disconnect = True

    # we re-run the mainloop to terminate the connections
    if wait_for_disconnect:
        try:
            mainloop.run()
        except KeyboardInterrupt:
            pass


def drop_privileges():
    sys.stderr.write('dropping privileges\n')

    os.setgroups([])
    gid = int(os.getenv('SUDO_GID'))
    uid = int(os.getenv('SUDO_UID'))
    pwname = os.getenv('SUDO_USER')
    os.setresgid(gid, gid, gid)
    os.initgroups(pwname, gid)
    os.setresuid(uid, uid, uid)

    pw = pwd.getpwuid(uid)

    # we completely clear the environment and start a new and controlled one
    os.environ.clear()
    os.environ['XDG_RUNTIME_DIR'] = f'/run/user/{uid}'
    os.environ['HOME'] = pw.pw_dir


def parse(args):
    desc = 'tool to start the live mode on all devices tuhi knows about'
    parser = argparse.ArgumentParser(description=desc)
    parser.add_argument('-v', '--verbose',
                        help='Show some debugging informations',
                        action='store_true',
                        default=False)

    return parser.parse_args(args[1:])


def main(args=sys.argv):
    if not os.geteuid() == 0:
        sys.exit('Script must be run as root')

    args = parse(args)

    request_fd_queue = multiprocessing.Queue()
    conn_in, conn_out = multiprocessing.Pipe()

    fd_process = multiprocessing.Process(target=open_uhid_process, args=(request_fd_queue, conn_out))
    fd_process.daemon = True
    fd_process.start()

    drop_privileges()

    start_tuhi_server(args)
    run_live(request_fd_queue, conn_in)


if __name__ == '__main__':
    main(sys.argv)