#!/usr/bin/python3

import sys
import os
from threading import Thread
from subprocess import call, Popen, PIPE

log_path = os.getenv("HOME") + "/.var/log/mycroft/"

flags = {
    "h": "help",
    "--help": "help",
    "r": "restart",
    "--restart": "restart",
    "--foreground": "foreground",
}

services = {
    "bus": "mycroft.messagebus.service",
    "skills": "mycroft.skills",
    "audio": "mycroft.audio",
    "voice": "mycroft.client.speech",
}

def help(_type="top", code=0):
    """
    Function to display help messages and exit the program.

    @param _type The type of the help message.
    @param code The return code to exit with.
    """
    helps = {
        "top":
"""Welcome to the Mycroft command line utility!
usage: mycroft [-h] [COMMAND]

Commands:
    start   Mycroft command/service launcher
    stop    Mycroft service stopper

Optional arguments:
    -h, --help  Show this help message""",
        "start":
"""Mycroft command/service launcher
usage: mycroft start [SERVICE]...

Services:
    all     Runs core services: bus, audio, skills, voice
    debug   Runs core services, then starts the CLI
    audio   The audio playback service
    bus     The message bus service
    skills  The skill service
    voice   Voice capture service

Optional arguments:
    -h, --help      Show this help message
    -r, --restart   Restart the service
    --foreground    Keeps the process running in the terminal""",
        "stop":
"""Mycroft service stopper
usage: mycroft stop [SERVICE]...

Services:
    all     Stops all services: bus, audio, skills, voice
    audio   The audio playback service
    bus     The message bus service
    skills  The skill service
    voice   Voice capture service

Optional arguments:
    -h, --help      Show this help message"""
    }
    print(helps[_type])
    sys.exit(code)

def parse_flags(args):
    """
    Simplistic function to parse the arguments given to the executable, remove
    all options ("-" and "--") comparing them to a "flags" list and determining
    which are activated in order to change the behaviour of the program.

    This function will also remove all the flags from the args list.

    Every option on the form -o=var or --option=var will have var used as a
    value to the flag and stored as a value to the flag which will be stored as
    the key. A flag without value will have None instead. So a typical example
    will be:

    {
        "name_of_activated_flag": None,
        "flag_with_value": "var",
    }

    If an option is not in the "flags" list, the program will exit with a
    message and the code 2.

    @param args The arguments of the executable.
    @return activated All the trigered flags and their values.
    @return args The arguments with all options removed.
    """
    activated = {}
    args_orig = args.copy()
    for arg in args_orig:
        if arg[0] == "-":
            if arg[1] == "-":
                try:
                    if "=" in arg:
                        activated[flags[arg.split("=")[0]]] = arg.split("=")[1]
                    else:
                        activated[flags[arg]] = None
                except KeyError:
                    print("Argument {} doesn't exist.".format(arg))
                    sys.exit(2)
            else:
                v = None
                f = arg
                if "=" in arg:
                    v = arg.split("=")[1]
                    f = arg.split("=")[0]
                for a in f[1:]:
                    try:
                        activated[flags[a]] = v
                    except KeyError:
                        print("Argument {} doesn't exist.".format(arg))
                        sys.exit(2)
            args.remove(arg)
        else:
            continue
    return activated, args[1:]

args = sys.argv

opt_args, args = parse_flags(args)

if len(args) == 0:
    c = 1
    if "help" in opt_args:
        c = 0
    help(code=c)

########## START ##########
def start(args):
    """
    The start subcommand.
    """
    def launch(name, restart=False, foreground=False):
        """
        Function to launch a service.
        """
        if restart:
            stop(["", name])
        if foreground:
            t = Thread(target=call, args=[["python3", "-m", services[name]]])
            t.start()
            return t
        else:
            print(log_path)
            call(["mkdir", "-p", log_path])
            p = Popen(["python3", "-m", services[name]],
                      stdout=open(log_path + name + ".log", "a"),
                      stderr=open(log_path + name + ".log", "a"))
            return p
    if "all" in args:
        args = [""] + list(services.keys())
    if "foreground" in opt_args:
        processes = []
    for serv in args[1:]:
        print("Start service {}...".format(serv))
        r = launch(serv, "restart" in opt_args, "foreground" in opt_args)
        if not "foreground" in opt_args:
            print("Service {} started with pid {}.".format(serv, r.pid))
        else:
            processes.append((serv, r))
    if "foreground" in opt_args:
        try:
            while True:
                for p in processes:
                    if not p[1].is_alive():
                        print("Service {} failed.".format(p[0]))
                        raise Exception
        except:
            for p in processes:
                p.kill()
            sys.exit(3)

########## STOP  ##########
def stop(args):
    """
    The stop subcommand.
    """
    if "all" in args:
        args = [""] + list(services.keys())
    for serv in args[1:]:
        try:
            cmd = 'pgrep -fo "python3 (.*)-m ' + services[serv] + '"'
            p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True)
            out, err = p.communicate()
            if out:
                p = Popen("kill -9 {}".format(int(out.decode("utf-8"))),
                          shell=True)
                r = p.wait()
                if not r:
                    print("Service {} killed successfully.".format(serv))
                else:
                    print("Unable to kill service {}, try manually. Pid {}."
                          .format(serv, int(out.decode("utf-8"))))
                    sys.exit(4)
            else:
                print("Service {} is not running.".format(serv))
        except KeyError:
            print("Service {} doesn't exist.".format(serv))
            sys.exit(5)

if args[0] == "start":
    start(args)

if args[0] == "stop":
    stop(args)
