#!/usr/bin/env python
'''
This file is part of the Python EJTP library.

The Python EJTP library is free software: you can redistribute it 
and/or modify it under the terms of the GNU Lesser Public License as
published by the Free Software Foundation, either version 3 of the 
License, or (at your option) any later version.

the Python EJTP library is distributed in the hope that it will be 
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser Public License for more details.

You should have received a copy of the GNU Lesser Public License
along with the Python EJTP library.  If not, see 
<http://www.gnu.org/licenses/>.
'''

__doc__ = '''ejtpd

This utility runs as a daemon, allowing a priveleged EJTP client to add
and remove client services from the process.

Usage:
    ejtpd start [options]
    ejtpd stop
    ejtpd client launch [options] <module> <class> [<args>] [<kwargs>]
    ejtpd client destroy [options] <victim>
    ejtpd -h | --help
    ejtpd --version

Options:
    -f <sourcefile>    Specify the source JSON file for all configuration parameters
    -i <interface>     Specify client interface
    -c <crypto>        Literal encryptor cache
    -F <filter>        Filter regex (for ejtpd DaemonClient)
    -r <remote>        Remote interface
    -h --help          Show this help message
'''

import json
from ejtp.vendor.docopt import docopt

from ejtp.logging import verbose
from ejtp import logging
logger_parameters = list(logging.loudlogger)
logger_parameters[0] = 'ejtpd'
logger = logging.makeLogger(*logger_parameters)

DEFAULT_INTERFACE  = ('udp4', ('localhost', 9630), 'EJTPD-DAEMON')
DEFAULT_CONTROLLER = ('udp4', ('localhost', 9631), 'EJTPD-CONTROL')
DEFAULT_TIMEOUT    = 5
DEFAULT_ENCRYPTOR_CACHE = {
    '["udp4",["localhost",9630],"EJTPD-DAEMON"]':  ['rotate', 60],
    '["udp4",["localhost",9631],"EJTPD-CONTROL"]': ['rotate', 95],
}

# Parsing and configuration utility functions ---------------------------------

def conf_load(filename):
    fp = None
    try:
        fp = open(filename, 'r')
    except:
        logger.warning("Couldn't load conf from path '%s'", filename)
    if fp:
        try:
            result = json.load(fp)
            logger.info("Successfully loaded conf data from '%s'", filename)
            return result
        except:
            logger.warning("Failed to parse JSON from conf file '%s'", filename)
    return {}

def conf_load_from_args(args):
    return conf_load(
         getarg(args, '-f', {}, '', '/etc/ejtpd/daemon.conf', False)
    )

def getarg(arguments, index, conf, conf_id, default, is_json=True):
    if index in arguments:
        value = arguments[index]
        if value != None:
            if is_json:
                try:
                    return json.loads(value)
                except:
                    logging.error("Couldn't parse JSON for argument %r", index)
            else:
                return value
    if conf_id in conf:
        return conf[conf_id]
    return default

def compile_enc_cache(args, conf):
    cache = DEFAULT_ENCRYPTOR_CACHE.copy()
    if "encryptor_cache" in conf and type(conf['encryptor_cache']) == dict:
        cache.update(conf['encryptor_cache'])
    arg_index = '-e'
    if arg_index in args:
        try:
            arg_cache = json.loads(args[arg_index])
            cache.update(arg_cache)
        except:
            logging.error("Couldn't parse JSON for argument %r", arg_index)
    return cache

# Daemon utility functions ----------------------------------------------------

def daemon_start(interface, controller, filtertext='.*', ecache=None, make_jack=True):
    from ejtp.router import Router
    from ejtp.applications.daemon import DaemonClient
    import os
    r = Router()
    c = DaemonClient(r, interface, controller, filtertext, ecache, make_jack)
    return (r, c, os.getpid())

def controller_do(interface, target, on_create, on_response, ecache=None, make_jack=True):
    from ejtp.router import Router
    from ejtp.applications.daemon import ControllerClient
    r = Router()
    c = ControllerClient(r, interface, target, ecache, make_jack)
    c.response_callback = on_response
    on_create(c)

def controller_init(modname, classname, *args, **kwargs):
    def on_create(client):
        print "on_create"
        client.client_init(modname, classname, *args, **kwargs)
        print "on_create finished"
    return on_create

def controller_destroy(interface):
    def on_create(client):
        print "on_create"
        client.client_destroy(interface)
    return on_create

# CLI actions -----------------------------------------------------------------

def cli_start(args):
    conf       = conf_load_from_args(args)
    enc_cache  = compile_enc_cache(args, conf)
    interface  = getarg(args, '-i', conf, 'dc_interface', DEFAULT_INTERFACE)
    controller = getarg(args, '-r', conf, 'controller',   DEFAULT_CONTROLLER)

    router, dc, pid = daemon_start(interface, controller, ecache=enc_cache)
    logger.info("Starting EJTP daemon with PID %d listening on interface %r...", pid, interface)
    logger.info("Daemon listening for commands from controller interface %r", controller)
    try:
        import time
        while True:
            time.sleep(3)
    except (KeyboardInterrupt, SystemExit):
            logger.info("Shutting down EJTP daemon...")

def cli_client(args):
    conf       = conf_load_from_args(args)
    enc_cache  = compile_enc_cache(args, conf)
    interface  = getarg(args, '-i', conf, 'controller',   DEFAULT_CONTROLLER)
    target     = getarg(args, '-r', conf, 'dc_interface', DEFAULT_INTERFACE)
    timeout    = getarg(args, '-t', conf, 'timeout',      DEFAULT_TIMEOUT)

    on_create = None
    if args['launch']:
        modname      = args['<module>']
        classname    = args['<class>']
        class_args   = eval(args['<args>'] or '[]')
        class_kwargs = eval(args['<kwargs>'] or '{}')
        on_create = controller_init(modname, classname, *class_args, **class_kwargs)
    elif args['destroy']:
        on_create = controller_destroy(eval(args['<victim>']))

    response = []
    def on_response(success, data):
        print "on_response"
        if success:
            print "Action completed successfully."
        else:
            print "Action failed."
        print json.dumps(data, indent=4)
        response.append([success, data])

    controller_do(interface, target, on_create, on_response, enc_cache)
    try:
        import time
        waited = 0
        timeslice = 0.2
        while waited <= timeout and not response:
            time.sleep(timeslice)
            if waited:
                print "... waiting for remote response..."
            waited += timeslice
    except (KeyboardInterrupt, SystemExit):
            logger.info("Tearing down controller...")
    if not response or not response[0]:
        quit(1)
    else:
        quit(0)

if __name__ == '__main__':
    arguments = docopt(__doc__, version='ejtpd 0.9.3')
    if arguments['start']:
        cli_start(arguments)
    elif arguments['client']:
        cli_client(arguments)
    elif arguments['stop']:
        print "Because the developer has not decided how to store PIDs in a"
        print "situation where you may have any number of daemons on a single"
        print "machine, there's no way to look up that data and use it to kill"
        print "the appropriate processes."
        print "\nIn the meantime, just use 'killall ejtpd' or a similar command."
    else:
        print(arguments)
