#!/usr/bin/env python3
# This file is placed in the Public Domain.
#
# pylint: disable=C,R,W0105,W0201,W0212,W0613,E0402,E0611
# ruff: noqa: E402


"""OBJR - objects runtime

    objr <cmd> [key=val] [key==val]
    objr [-a] [-c] [-d] [-h] [-v]

COMMANDS

    cmd    list available commands
    mod    list available modules

USAGE

    $ objr [cmnd] mod=module1,module2,module3
    $ objr -c mod=module1,module2,module3

OPTIONS

    -a     load all modules
    -c     start console
    -d     start daemon
    -h     display help
    -v     use verbose
"""


import getpass
import os
import pwd
import readline # pylint: disable=W0611
import sys
import termios
import time


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


from objx import Default, Workdir, cdir, skel
from objr import Client, Command, Event, cmnd, errors, parse_cmd, spl


import objd


Cfg             = Default()
Cfg.mod         = "cmd,mod"
Cfg.name        = sys.argv[0].split(os.sep)[-1]
Cfg.wd          = os.path.expanduser(f"~/.{Cfg.name}")
Cfg.pidfile     = os.path.join(Cfg.wd, f"{Cfg.name}.pid")
Workdir.workdir = Cfg.wd


dte    = time.ctime(time.time()).replace("  ", " ")


class Console(Client):

    def announce(self, txt):
        pass

    def callback(self, evt):
        Client.callback(self, evt)
        evt.wait()

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

    def say(self, channel, txt):
        txt = txt.encode('utf-8', 'replace').decode()
        print(txt)


"utilities"


def daemon(pidfile, 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("/")
    if os.path.exists(pidfile):
        os.unlink(pidfile)
    cdir(os.path.dirname(pidfile))
    with open(pidfile, "w", encoding="utf-8") as fds:
        fds.write(str(os.getpid()))


def debug(txt):
    print(txt)


def init(pkg, modstr, disable="", wait=False):
    mds = []
    for modname in spl(modstr):
        if modname in spl(disable):
            continue
        module = getattr(pkg, modname, None)
        if not module:
            continue
        if "init" in dir(module):
            module.init()
            mds.append(module)
    return mds


def privileges(username):
    pwnam = pwd.getpwnam(username)
    os.setgid(pwnam.pw_gid)
    os.setuid(pwnam.pw_uid)


def wrap(func):
    old2 = None
    try:
        old2 = termios.tcgetattr(sys.stdin.fileno())
    except termios.error:
        pass
    try:
        func()
    except (KeyboardInterrupt, EOFError):
        print("")
    finally:
        if old2:
            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old2)


"runtime"


def main():
    skel()
    parse_cmd(Cfg, " ".join(sys.argv[1:]))
    if 'a' in Cfg.opts:
        Cfg.mod = ",".join(objd.__dir__())
    if "v" in Cfg.opts:
        debug(f"{Cfg.name.upper()} {Cfg.opts.upper()} started {dte}")
    if "h" in Cfg.opts:
        print(__doc__)
        return
    if "c" in Cfg.opts:
        init(objd, Cfg.mod, Cfg.sets.dis, True)
        csl = Console()
        csl.start()
        while 1:
            time.sleep(1.0)
        return
    if 'x' in Cfg.opts:
        return cmnd(Cfg.otxt, print)
    Cfg.mod = ",".join(objd.__dir__())
    Cfg.user = getpass.getuser()
    daemon(Cfg.pidfile, "v" in Cfg.opts)
    privileges(Cfg.user)
    init(objd, Cfg.mod, Cfg.sets.dis, True)
    while 1:
        time.sleep(1.0)
    return


def wrapped():
    wrap(main)
    errors()


if __name__ == "__main__":
    wrapped()
