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


"main program"


import argparse
import logging
import os
import sys
import time


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


from bigtalk.command import Cfg, Commands
from bigtalk.clients import Console
from bigtalk.message import Message
from bigtalk.package import Mods
from bigtalk.utility import Utils
from bigtalk.persist import Workdir
from bigtalk.utility import Log


from bigtalk import modules as MODS


"config"


Cfg.commands = True
Cfg.ignore   = "rst,udp,web"
Cfg.level    = "info"
Cfg.modules  = ""
Cfg.name     = Utils.pkgname(Commands)
Cfg.poll     = 300
Cfg.port     = 6667
Cfg.room     = f"#{Cfg.name}"
Cfg.server   = "localhost"
Cfg.txt      = " ".join(sys.argv[1:])
Cfg.version  = 150
Cfg.wdr      = os.path.expanduser(f"~/.{Cfg.name}")


"clients"


class Line(Console):

    def __init__(self):
        super().__init__()
        self.register("command", Commands.command)

    def raw(self, text):
        "write to console."
        print(text.encode('utf-8', 'replace').decode("utf-8"))


class CSL(Line):

    def callback(self, event):
        "wait for callback result."
        if not event.text:
            event.ready()
            return
        super().callback(event)
        event.wait()

    def poll(self):
        "poll for an event."
        evt = Message()
        evt.text = input("> ")
        evt.kind = "command"
        return evt


"scripts"


class Scripts:

    @staticmethod
    def background(args):
        "background script."
        Runtime.daemon()
        Runtime.privileges()
        Runtime.boot(args)
        Workdir.pidfile(Cfg.name)
        Mods.scanner(Mods.list(), Cfg.ignore)
        Commands.add(Cmd.cmd, Cmd.mod, Cmd.ver)
        Mods.inits(Cfg.default or "irc,mdl,rss,wsd", Cfg.ignore)
        Runtime.forever()

    @staticmethod
    def console(args):
        "console script."
        import readline
        readline.redisplay()
        Runtime.boot(args)
        Mods.scanner(Mods.list(), Cfg.ignore)
        Commands.add(Cmd.cmd, Cmd.mod, Cmd.ver)
        Commands.cmd(Cfg.txt)
        Mods.inits(Cfg.modules, Cfg.ignore, Cfg.wait)
        csl = CSL()
        csl.start()
        Runtime.forever()

    @staticmethod
    def control(args):
        "cli script."
        if len(sys.argv) == 1:
            return
        Runtime.boot(args)
        Mods.scanner(Mods.list(), Cfg.ignore)
        Commands.add(Cmd.cmd, Cmd.mod, Cmd.srv, Cmd.ver)
        evt = Commands.cmd(Cfg.txt)
        for line in evt.result.values():
            print(line)

    @staticmethod
    def service(args):
        "service script."
        Runtime.privileges()
        Runtime.banner()
        Runtime.boot(args)
        Workdir.pidfile(Cfg.name)
        Mods.scanner(Mods.list())
        Commands.add(Cmd.cmd, Cmd.mod, Cmd.ver)
        Mods.inits(Cfg.default or "irc,mdl,rss,wsd", Cfg.ignore)
        Runtime.forever()


"commands"


class Cmd:

    @staticmethod
    def cmd(event):
        "list available commands."
        event.reply(",".join(sorted(Commands.names or Commands.cmds)))

    @staticmethod
    def mod(event):
        "list available commands."
        mods = Mods.list(Cfg.ignore)
        if not mods:
            event.reply("no modules available")
            return
        event.reply(mods)

    @staticmethod
    def srv(event):
        "generate systemd service file."
        import getpass
        name = getpass.getuser()
        event.reply(SYSTEMD % (Cfg.name.upper(), name, name, name, Cfg.name))

    @staticmethod
    def ver(event):
        "show verson."
        event.reply(f"{Cfg.name.upper()} {Cfg.version}")


"runtime"


class Runtime:
    
    @staticmethod
    def banner():
        "hello."
        tme = time.ctime(time.time()).replace("  ", " ")
        print("%s %s since %s (%s)" % (
            Cfg.name.upper(),
            Cfg.version,
            tme,
            Cfg.level.upper(),
        ))
        sys.stdout.flush()

    @staticmethod
    def boot(args):
        "in the beginning."
        Cfg.all = args.all
        Cfg.level = args.level
        Cfg.mods = args.mods
        Cfg.port = args.port
        Cfg.room = args.room
        Cfg.server = args.connect
        Cfg.verbose = args.verbose
        Cfg.wait = args.wait
        Cfg.wdr = args.wdr or os.path.expanduser(f"~/.{Utils.pkgname(Cfg)}")
        Log.level(Cfg.level)
        Workdir.setwd(Cfg.wdr)
        if Cfg.wdr:
            Mods.init("modules", os.path.join(Workdir.workdir(), "mods"))
        if MODS:
            Mods.init(MODS.__name__, MODS.__path__[0])
        if Cfg.verbose:
            Runtime.banner()
        if Cfg.all:
            Cfg.modules = Mods.list(Cfg.ignore)

    @staticmethod
    def daemon(verbose=False, nochdir=False):
        "run in the background."
        pid = os.fork()
        if pid != 0:
            os._exit(0)
        os.setsid()
        pid2 = os.fork()
        if pid2 != 0:
            os._exit(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())
        os.umask(0)
        os.chdir("/")
        os.nice(10)

    @staticmethod
    def forever():
        "run forever until ctrl-c."
        while True:
            try:
                time.sleep(0.1)
            except (KeyboardInterrupt, EOFError):
                break

    @staticmethod
    def getargs():
        "parse commandline arguments."
        parser = argparse.ArgumentParser(description="Big Talk")
        parser.add_argument("-a", "--all", action="store_true", help="load all modules")
        parser.add_argument("-c", "--console", action="store_true", help="start console")
        parser.add_argument("-d", "--daemon", action="store_true", help="start background daemon")
        parser.add_argument("-l", "--level", default=Cfg.level, help='set loglevel')
        parser.add_argument("-m", "--mods", default="", help='modules to load')
        parser.add_argument("-p", "--port", default=Cfg.port, help='set port to connect to')
        parser.add_argument("-r", "--room", help='set room/channel to joinl')
        parser.add_argument("-s", "--service", action="store_true", help="start service")
        parser.add_argument("-v", "--verbose", action='store_true',help='enable verbose')
        parser.add_argument("-w", "--wait", action='store_true',help='wait for services to start')
        parser.add_argument("-z", "--connect", default=Cfg.server, help="server to connect to")
        parser.add_argument("--wdr", help='set working directory')
        return parser.parse_known_args()

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

    @staticmethod
    def wrap(func, *args):
        "restore console."
        import termios
        old = None
        try:
            old = termios.tcgetattr(sys.stdin.fileno())
        except termios.error:
            pass
        try:
            func(*args)
        except (KeyboardInterrupt, EOFError):
            pass
        except Exception as ex:
            logging.exception(ex)
        if old:
            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old)


"data"


SYSTEMD = """[Unit]
Description=%s
After=multi-user.target

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

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


"runtime"


def main():
    "main"
    args, rest = Runtime.getargs()
    if args.daemon:
        Scripts.background(args)
    elif args.console:
        Runtime.wrap(Scripts.console, args)
    elif args.service:
        Runtime.wrap(Scripts.service, args)
    else:
        Runtime.wrap(Scripts.control, args)
    Mods.shutdown()


if __name__ == "__main__":
    main()
