#!/usr/bin/env python3
# This file is placed in the Public Domain.
#
# pylint: disable=C0115,C0116,E0402,W0212,R1710,W0611,E0611,R0903


"runtime"


import os
import readline
import sys
import termios
import time
import threading
import traceback
import _thread


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


from sbn.methods import parse, spl
from sbn.reactor import Cfg, Event, Client, command
from sbn.storage import Storage
from sbn.threads import Thread, launch


from sbn import modules


Cfg.mod = "err,mdl,mod,req,sts,thr,bsc"
Cfg.name = __file__.split(os.sep)[-1]


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


class CLI(Client):

    def raw(self, txt):
        cprint(txt)


class Console(Client):

    prompting = threading.Event()

    def announce(self, txt):
        pass

    def handle(self, evt):
        command(evt)
        evt.wait()

    def prompt(self):
        Console.prompting.set()
        inp = input("> ")
        Console.prompting.clear()
        return inp

    def poll(self):
        try:
            return self.event(self.prompt())
        except EOFError:
            _thread.interrupt_main()

    def raw(self, txt):
        if Console.prompting.is_set():
            txt = "\n" + txt
        print(txt)
        Console.prompting.clear()
        sys.stdout.flush()


def daemon():
    pid = os.fork()
    if pid != 0:
        os._exit(0)
    os.setsid()
    os.umask(0)
    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())


def lsmod(pkg):
    return ",".join([x[:-3] for x in os.listdir(pkg.__path__[0])
                     if not x.startswith("__")])


def scan(pkg, modstr="", initer=False, wait=False) -> []:
    inited = []
    scanned = []
    threads = []
    if modstr == "":
        modstr = lsmod(pkg)
    for modname in spl(modstr):
        module = getattr(pkg, modname, None)
        if not module:
            continue
        scanned.append(modname)
        Client.scan(module)
        Storage.scan(module)
        if initer:
            try:
                module.init
            except AttributeError:
                continue
            inited.append(modname)
            threads.append(launch(module.init, name=f"init {modname}"))
    if wait:
        for thread in threads:
            thread.join()
    return inited


def wrap(func) -> None:
    if "d" in Cfg.opts:
        Client.debug("terminal disabled")
        return
    old = termios.tcgetattr(sys.stdin.fileno())
    try:
        func()
    except (EOFError, KeyboardInterrupt):
        print("")
        sys.stdout.flush()
    finally:
        termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old)
    for exc in Client.errors + Thread.errors:
        traceback.print_exception(
                                  type(exc),
                                  exc,
                                  exc.__traceback__
                                 )

def main():
    Client.output = cprint
    parse(Cfg, " ".join(sys.argv[1:]))
    if "a" in Cfg.opts:
        Cfg.mod = lsmod(modules)
    if "v" in Cfg.opts:
        tme = time.ctime(time.time()).replace("  ", " ")
        Client.debug(f"{Cfg.name.upper()} started {tme} {Cfg.mod.upper()} {Cfg.opts.upper()}")
    if "d" in Cfg.opts:
        Client.output = None
        daemon()
        scan(modules, Cfg.mod, True)
        while 1:
            time.sleep(1.0)
        return
    csl = Console()
    if "c" in Cfg.opts:
        scan(modules, Cfg.mod, True)
        csl.start()
        csl.wait()
    else:
        scan(modules, Cfg.mod)
        cli = CLI()
        evt = Event()
        evt.orig = object.__repr__(cli)
        evt.txt = Cfg.otxt
        command(evt)
        evt.wait()


def wrapped():
    wrap(main)


if __name__ == "__main__":
    wrapped()
