#!/usr/bin/env python3
# This file is placed in the Public Domain.


import inspect
import json
import os
import pathlib
import sys
import time


sys.path.insert(0, os.getcwd())


from tob.clients import Client
from tob.command import Commands, Config, command, parse, scanner, table
from tob.handler import Event
from tob.logging import level
from tob.package import Mods, getmod, inits, modules, sums
from tob.persist import Workdir, moddir, pidname, skel
from tob.utility import md5sum, spl


import tobot.modules as MODS


CHECKSUM = "74c8e52fc1ef4d1f5dc90eae4f2200c4"


Config.name = "tob"
Config.version = 1


class CLI(Client):

    def __init__(self):
        Client.__init__(self)
        self.register("command", command)

    def raw(self, txt):
        print(txt.encode('utf-8', 'replace').decode("utf-8"))


class Console(CLI):

    def callback(self, event):
        if not event.txt:
            return
        super().callback(event)
        event.wait()

    def poll(self):
        evt = Event()
        evt.txt = input("> ")
        evt.type = "command"
        return evt


def boot(doparse=True):
    Workdir.wdr = os.path.expanduser(f"~/.{Config.name}")
    Mods.add("modules", os.path.dirname(inspect.getfile(MODS)))
    if doparse:
        parse(Config, " ".join(sys.argv[1:]))
        Config.level = Config.sets.level or Config.level
    if "v" in Config.opts:
        banner()
    level(Config.level)
    if "e" in Config.opts:
        sys.path.insert(0, os.getcwd())
        Mods.dirs["mods"] = "examples"
    else:
        Mods.dirs["mods"] = moddir()
    if "a" in Config.opts:
        Config.sets.init = ",".join(modules())
    Commands.add(cmd)
    Commands.add(ver)
    skel()
    table()
    sums(CHECKSUM)


"scripts"


def background():
    daemon("-v" in sys.argv)
    privileges()
    Config.opts += "m"
    boot(False)
    pidfile(pidname(Config.name))
    inits(spl(Config.default))
    forever()


def console():
    import readline # noqa: F401
    boot()
    for _mod, thr in inits(spl(Config.sets.init)):
        if "w" in Config.opts:
            thr.join(30.0)
    csl = Console()
    csl.start()
    forever()


def control():
    if len(sys.argv) == 1:
        return
    boot()
    Commands.add(md5)
    Commands.add(srv)
    Commands.add(tbl)
    csl = CLI()
    evt = Event()
    evt.orig = repr(csl)
    evt.type = "command"
    evt.txt = " ".join(sys.argv[1:])
    evt.cmd  = evt.txt.split()[0]
    command(evt)
    evt.wait()


def service():
    privileges()
    Config.opts += "m"
    boot(False)
    pidfile(pidname(Config.name))
    banner()
    inits(spl(Config.default))
    forever()


"utility"


def banner():
    tme = time.ctime(time.time()).replace("  ", " ")
    print("%s %s since %s (%s)" % (Config.name.upper(), Config.version, tme, Config.level.upper()))


def check(txt):
    args = sys.argv[1:]
    for arg in args:
        if not arg.startswith("-"):
            continue
        for char in txt:
            if char in arg:
                return True
    return False


def daemon(verbose=False):
    pid = os.fork()
    if pid != 0:
        os._exit(0)
    os.setsid()
    pid2 = os.fork()
    if pid2 != 0:
        os._exit(0)
    if not verbose:
        with open('/dev/null', 'r', encoding="utf-8") as sis:
            os.dup2(sis.fileno(), sys.stdin.fileno())
        with open('/dev/null', 'a+', encoding="utf-8") as sos:
            os.dup2(sos.fileno(), sys.stdout.fileno())
        with open('/dev/null', 'a+', encoding="utf-8") as ses:
            os.dup2(ses.fileno(), sys.stderr.fileno())
    os.umask(0)
    os.chdir("/")
    os.nice(10)


def forever():
    while True:
        try:
            time.sleep(0.1)
        except (KeyboardInterrupt, EOFError):
            break


def pidfile(filename):
    if os.path.exists(filename):
        os.unlink(filename)
    path2 = pathlib.Path(filename)
    path2.parent.mkdir(parents=True, exist_ok=True)
    with open(filename, "w", encoding="utf-8") as fds:
        fds.write(str(os.getpid()))


def privileges():
    import getpass
    import pwd
    pwnam2 = pwd.getpwnam(getpass.getuser())
    os.setgid(pwnam2.pw_gid)
    os.setuid(pwnam2.pw_uid)


"commands"


def cmd(event):
    event.reply(",".join(sorted(Commands.names or Commands.cmds)))


def md5(event):
    tbl = getmod("tbl")
    if tbl:
        event.reply(md5sum(tbl.__file__))
    else:
        event.reply("table is not there.")


def srv(event):
    import getpass
    name = getpass.getuser()
    event.reply(TXT % (Config.name.upper(), name, name, name, Config.name))


def tbl(event):
    Commands.names = {}
    scanner()
    event.reply("# This file is placed in the Public Domain.")
    event.reply("")
    event.reply("")
    event.reply('"lookup tables"')
    event.reply("")
    event.reply("")
    event.reply(f"NAMES = {json.dumps(Commands.names, indent=4, sort_keys=True)}")
    event.reply("")
    event.reply("")
    event.reply("MD5 = {")
    for module in scanner():
        event.reply(f'    "{module.__name__.split(".")[-1]}": "{md5sum(module.__file__)}",')
    event.reply("}")


def ver(event):
    event.reply(f"{Config.name.upper()} {Config.version}")


TXT = """[Unit]
Description=%s
After=network-online.target

[Service]
Type=simple
User=%s
Group=%s
ExecStart=/home/%s/.local/bin/%s -s

[Install]
WantedBy=multi-user.target"""


"runtime"


def wrapped(func):
    try:
        func()
    except (KeyboardInterrupt, EOFError):
        print("")


def wrap(func):
    import termios
    old = None
    try:
        old = termios.tcgetattr(sys.stdin.fileno())
    except termios.error:
        pass
    try:
        wrapped(func)
    finally:
        if old:
            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old)


def main():
    if check("c"):
        wrap(console)
    elif check("d"):
        background()
    elif check("s"):
        wrapped(service)
    else:
        wrapped(control)


"trampoline"


if __name__ == "__main__":
    main()
