#!python
# This file is placed in the Public Domain.


"main"


import json
import logging
import os
import os.path
import pathlib
import signal
import sys
import time


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


from nixt.clients import NAME, Client, Fleet
from nixt.command import Commands, command, parse, scan, table
from nixt.command import Mods, inits, md5sum, mods, modules, sums
from nixt.handler import Event
from nixt.persist import pidname, setwd, skel
from nixt.runtime import level


CHECKSUM = "d80a32045e2abadddcbc62054c74bb69"


Mods.ignore = "rst,udp,web,wsd"


class Config:

    debug    = False
    gets     = {}
    init     = ""
    level    = "warn"
    md5      = True
    opts     = {}
    otxt     = ""
    sets     = {}
    verbose  = False
    version  = 410


class CLI(Client):

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

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


class Console(CLI):

    def announce(self, txt):
        pass

    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 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 handler(signum, frame):
    print("handler called!")


signal.signal(signal.SIGHUP, handler)


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)


def background():
    daemon("-v" in sys.argv)
    privileges()
    boot(False)
    pidfile(pidname(NAME))
    inits(Config.init or "irc,mdl,rss")
    forever()


def console():
    import readline # noqa: F401
    boot()
    for _mod, thr in inits(Config.init):
        if "w" in Config.opts:
            thr.join(30.0)
    csl = Console()
    csl.start(daemon=True)
    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 = Config.otxt
    command(evt)
    evt.wait()


def service():
    privileges()
    boot(False)
    pidfile(pidname(NAME))
    inits(Config.init or "irc,mdl,rss")
    forever()


def boot(doparse=True):
    if doparse:
        parse(Config, " ".join(sys.argv[1:]))
    Config.init = Config.sets.get("init", Config.init)
    Config.verbose = Config.sets.get("verbose", Config.verbose)
    Config.level   = Config.sets.get("level", Config.level or "warn")
    level(Config.level)
    setwd(NAME)
    if Config.md5 and not sums(CHECKSUM):
        logging.error("table sum doesn't match")
    table()
    Commands.add(cmd)
    Commands.add(ver)
    if "v" in Config.opts:
        banner()


def cmd(event):
    cmds = list(Commands.cmds.keys()) + list(Commands.names.keys())
    event.reply(",".join(sorted(cmds)))


def md5(event):
    tbl = mods("tbl")[0]
    event.reply(md5sum(tbl.__file__))


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"""


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


def tbl(event):
    if not check("f"):
        Commands.names = {}
    for module in mods():
        scan(module)
    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 mods():
        event.reply(f'    "{module.__name__.split(".")[-1]}": "{md5sum(module.__file__)}",')
    event.reply("}")


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


def banner():
    tme = time.ctime(time.time()).replace("  ", " ")
    out(f"{NAME.upper()} {Config.version} since {tme} ({Config.level.upper()})")
    out(f"loaded {",".join(modules())}")


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 forever():
    while True:
        try:
            time.sleep(0.1)
        except (KeyboardInterrupt, EOFError):
            break


def out(txt):
    print(txt)
    sys.stdout.flush()


def wrapped(func):
    try:
        func()
    except (KeyboardInterrupt, EOFError):
        out("")
    Fleet.shutdown()


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("a"):
        Config.init = ",".join(modules())
    if check("v"):
        Config.opts["v"] = True
    if check("c"):
        wrap(console)
    elif check("d"):
        background()
    elif check("s"):
        wrapped(service)
    else:
        wrapped(control)


if __name__ == "__main__":
    main()
