#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2021 sezanzeb <proxima@sezanzeb.de>
#
# This file is part of key-mapper.
#
# key-mapper 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 3 of the License, or
# (at your option) any later version.
#
# key-mapper 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 key-mapper.  If not, see <https://www.gnu.org/licenses/>.


"""Control the dbus service from the command line."""


import os
import grp
import sys
import argparse
import logging

from keymapper.logger import logger, update_verbosity, log_info
from keymapper.config import config

# import keymapper modules as late as possible to make sure the correct
# log level is applied before anything is logged


AUTOLOAD = 'autoload'
START = 'start'
STOP = 'stop'
STOP_ALL = 'stop-all'
HELLO = 'hello'

# internal stuff that the gui uses
START_DAEMON = 'start-daemon'
HELPER = 'helper'


def run(cmd):
    """Run and log a command."""
    logger.info('Running `%s`...', cmd)
    code = os.system(cmd)
    if code != 0:
        logger.error('Failed. exit code %d', code)


def group_exists(name):
    """Check if a group with that name exists."""
    try:
        grp.getgrnam(name)
        return True
    except KeyError:
        return False


COMMANDS = [AUTOLOAD, START, STOP, HELLO, STOP_ALL]

INTERNALS = [START_DAEMON, HELPER]


def utils(options):
    """Listing names, tasks that don't require a running daemon."""
    if options.list_devices:
        logger.setLevel(logging.ERROR)
        from keymapper.groups import groups
        for group in groups:
            print(group.key)
        sys.exit(0)

    if options.key_names:
        from keymapper.state import system_mapping
        print('\n'.join(system_mapping.list_names()))
        sys.exit(0)


def communicate(options, daemon):
    """Commands that require a running daemon"""
    # import stuff late to make sure the correct log level is applied
    # before anything is logged
    from keymapper.groups import groups
    from keymapper.paths import USER

    def require_group():
        if options.device is None:
            logger.error('--device missing')
            sys.exit(1)

        if options.device.startswith('/dev'):
            group = groups.find(path=options.device)
        else:
            group = groups.find(key=options.device)

        if group is None:
            logger.error('unknown device "%s"', options.device)
            sys.exit(1)

        return group

    if daemon is None:
        sys.exit(0)

    if options.config_dir is not None:
        path = os.path.abspath(os.path.expanduser(os.path.join(
            options.config_dir,
            'config.json'
        )))
        if not os.path.exists(path):
            logger.error('"%s" does not exist', path)
            sys.exit(1)

        logger.info('Using config from "%s" instead', path)
        config.load_config(path)

    if USER != 'root':
        # Might be triggered by udev, so skip the root user.
        # This will also refresh the config of the daemon if the user changed
        # it in the meantime.
        # config_dir is either the cli arg or the default path in home
        config_dir = os.path.dirname(config.path)
        daemon.set_config_dir(config_dir)

    if options.command == AUTOLOAD:
        # if device was specified, autoload for that one. if None autoload
        # for all devices.
        if options.device is None:
            daemon.autoload()
        else:
            group = require_group()
            daemon.autoload_single(group.key)

    if options.command == START:
        group = require_group()

        logger.info(
            'Starting injection: "%s", "%s"',
            options.device, options.preset
        )

        daemon.start_injecting(group.key, options.preset)

    if options.command == STOP:
        group = require_group()
        daemon.stop_injecting(group.key)

    if options.command == STOP_ALL:
        daemon.stop_all()

    if options.command == HELLO:
        response = daemon.hello('hello')
        logger.info('Daemon answered with "%s"', response)


def internals(options):
    """Methods that are needed to get the gui to work and that require root.

    key-mapper-control should be started with sudo or pkexec for this.
    """
    debug = ' -d' if options.debug else ''

    if options.command == HELPER:
        cmd = f'key-mapper-helper{debug}'
    elif options.command == START_DAEMON:
        cmd = f'key-mapper-service --hide-info{debug}'
    else:
        return

    # daemonize
    cmd = f'{cmd} &'
    os.system(cmd)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--command', action='store', dest='command', help=(
            'Communicate with the daemon. Available commands are start, '
            'stop, autoload, hello or stop-all'
        ), default=None, metavar='NAME'
    )
    parser.add_argument(
        '--config-dir', action='store', dest='config_dir',
        help=(
            'path to the config directory containing config.json, '
            'xmodmap.json and the presets folder. '
            'defaults to ~/.config/key-mapper/'
        ),
        default=None, metavar='PATH',
    )
    parser.add_argument(
        '--preset', action='store', dest='preset',
        help='The filename of the preset without the .json extension.',
        default=None, metavar='NAME',
    )
    parser.add_argument(
        '--device', action='store', dest='device',
        help='One of the device keys from --list-devices',
        default=None, metavar='NAME'
    )
    parser.add_argument(
        '--list-devices', action='store_true', dest='list_devices',
        help='List available device keys and exit',
        default=False
    )
    parser.add_argument(
        '-n', '--symbol-names', action='store_true', dest='key_names',
        help='Print all available names for the mapping',
        default=False
    )
    parser.add_argument(
        '-d', '--debug', action='store_true', dest='debug',
        help='Displays additional debug information',
        default=False
    )
    parser.add_argument(
        '-v', '--version', action='store_true', dest='version',
        help='Print the version and exit', default=False
    )

    options = parser.parse_args(sys.argv[1:])

    if options.version:
        log_info()
        sys.exit(0)

    update_verbosity(options.debug)

    logger.debug('Call for "%s"', sys.argv)

    if options.command is not None:
        if options.command in INTERNALS:
            options.debug = True
            internals(options)
        elif options.command in COMMANDS:
            from keymapper.daemon import Daemon
            daemon = Daemon.connect(fallback=False)
            communicate(options, daemon)
        else:
            logger.error('Unknown command "%s"', options.command)
    else:
        utils(options)
