#!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 nixbot.clients import Client, Fleet
from nixbot.command import Commands, Main, command, parse, scan, table
from nixbot.command import inits, md5sum, mods, modules, sums
from nixbot.handler import Event
from nixbot.runtime import level
from nixbot.persist import pidname, setwd


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):
        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()
    level(Main.level or "debug")
    setwd(Main.name)
    pidfile(pidname(Main.name))
    if Main.md5 and not sums(Main.md5):
        logging.error("table sum doesn't match")
    table()
    Commands.add(cmd)
    Commands.add(ver)
    inits(Main.init or "irc,rss")
    forever()


def console():
    import readline # noqa: F401
    parse(Main, " ".join(sys.argv[1:]))
    Main.init = Main.sets.get("init", Main.init)
    Main.verbose = Main.sets.get("verbose", Main.verbose)
    Main.level   = Main.sets.get("level", Main.level or "warn")
    level(Main.level)
    setwd(Main.name)
    if Main.md5 and not sums(Main.md5):
        logging.error("table sum doesn't match")
    table()
    Commands.add(cmd)
    Commands.add(ver)
    if "v" in Main.opts:
        banner()
    for _mod, thr in inits(Main.init):
        if "w" in Main.opts:
            thr.join(30.0)
    csl = Console()
    csl.start(daemon=True)
    forever()


def control():
    if len(sys.argv) == 1:
        return
    parse(Main, " ".join(sys.argv[1:]))
    level(Main.level or "warn")
    setwd(Main.name)
    if Main.md5 and not sums(Main.md5):
        logging.error("table sum doesn't match")
    table()
    Commands.add(cmd)
    Commands.add(md5)
    Commands.add(srv)
    Commands.add(tbl)
    Commands.add(ver)
    csl = CLI()
    evt = Event()
    evt.orig = repr(csl)
    evt.type = "command"
    evt.txt = Main.otxt
    command(evt)
    evt.wait()


def service():
    level(Main.level or "warn")
    setwd(Main.name)
    banner()
    privileges()
    pidfile(pidname(Main.name))
    if Main.md5 and not sums(Main.md5):
        logging.error("table sum doesn't match")
    table()
    Commands.add(cmd)
    Commands.add(ver)
    inits(Main.init or "irc,rss")
    forever()


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


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 % (Main.name.upper(), name, name, name, Main.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(str(Main.version))


def banner():
    tme = time.ctime(time.time()).replace("  ", " ")
    out(f"{Main.name.upper()} {Main.version} since {tme} ({Main.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"):
        Main.init = ",".join(modules())
    if check("v"):
        Main.opts["v"] = True
    if check("c"):
        wrap(console)
    elif check("d"):
        background()
    elif check("s"):
        wrapped(service)
    else:
        wrapped(control)


if __name__ == "__main__":
    main()
