#!/usr/bin/env python
# -*- coding: utf-8 -*-

from optparse import OptionParser
from sjrpc.core import RpcError
from ccnode.ccnode import CCNode
import ConfigParser
import sys, os, atexit
import logging
import logging.handlers
import signal
import threading
from daemon import DaemonContext
from time import sleep
from math import exp
from ccnode import __version__

MAX_AUTH_TIMEOUT = 10
DEFAULT_CONFIG_FILE = '/etc/cc-node.conf'
DEFAULT_PID_FILE = '/var/run/cc-node.pid'
DEFAULT_CONFIGURATION = {
    'address': '',
    'login': '',
    'password': '',
    'port': '1984',
    'verbosity': '0',
    'detect_hypervisor': 'yes',
    #'ssl_cert': '',
    'command_execution' : 'yes',
    'force_xen' : 'no',
}

def run_node(options):
    '''
    '''
    # setup logging facility:
    level = logging.ERROR
    verb = int(options['verbosity'])
    if verb:
        if verb == 1:
            level = logging.WARNING
        elif verb == 2:
            level = logging.INFO
        else:
            level = logging.DEBUG

    logger = logging.getLogger()
    logger.setLevel(level)
    if options['stdout']:
        handler = logging.StreamHandler()
    else:
        facility = logging.handlers.SysLogHandler.LOG_DAEMON
        handler = logging.handlers.SysLogHandler(address='/dev/log',
                                                            facility=facility)
    handler.setFormatter(logging.Formatter('cc-node: %(levelname)s %(message)s')
                                                                            )
    logger.handlers = []
    logger.addHandler(handler)
    
    # setup SIGINT/SIGTERM handler
    def shutdown_handler(signum, frame):
        '''
        Handler called when SIGINT/SIGTERM emited
        '''
        logging.error('Letal signal received, node shutdown in progress')
        try:
            if node:
                logging.debug('Closing server connection')
                node.manager.shutdown()
            pass
        except:
            pass
        finally:
            os._exit(1)
    
    # register SIGINT and SIGTERM handler
    signal.signal(signal.SIGINT, shutdown_handler)
    signal.signal(signal.SIGTERM, shutdown_handler)
    
    # re-authentication thread
    def authentication(node):
        timeout = 1
        while node:
            if node.get_manager().is_running():
                logging.info('Sending authentication request')
                if node.authentify(options['login'], options['password']):
                    logging.error('Authentication suscessfull')
                    return
                else:
                    logging.error('Authentication failure')
                    timeout += 0.1
                    if timeout >= MAX_AUTH_TIMEOUT:
                        timeout = MAX_AUTH_TIMEOUT
                    sleep(exp(timeout))
    
    # start node
    none = None
    auth_thread = None
    try:
        logging.error('Initializing node client')
        try:
            node = CCNode(options['address'], int(options['port']),
                          options['detect_hypervisor'] == 'yes',
                          options['command_execution'] == 'yes',
                          force_xen=(options['force_xen'] == 'yes'))
        except Exception as err:
            logging.error('Client initialization failure: `%s`:`%s`', repr(err),
                                                                            err)
            raise err
        
        # start main loop and auth thread
        logging.info('Starting authentication thread')
        auth_thread = threading.Thread(target=authentication, args=(node,),
                                                                    name='Auth')
        auth_thread.daemon = True
        auth_thread.start()
        logging.info('Starting main loop')
        node.run()
    
    except Exception as err:
        logging.error('run_node: `%s` -> `%s`', repr(err), err)
        if auth_thread:
            del auth_thread
            auth_thread = None
        if node:
            node.manager.shutdown()
            node = None
    finally:
        return

if __name__ == '__main__':
    
    op = OptionParser(version='%%prog %s' % __version__)
    op.add_option('-c', '--config', default=DEFAULT_CONFIG_FILE,
                  help='configuration file (default: %default)')
    op.add_option('-d', '--daemonize', default=False, action='store_true',
                  help='run as daemon and write pid file')
    op.add_option('-p', '--pidfile', default=DEFAULT_PID_FILE,
                  help='pid file (default: %default)')
    op.add_option('-s', '--stdout', action='store_true', default=False,
                  help='log on standard output instead of syslog')
    
    cliopts, args = op.parse_args()
    
    # read the config file
    config = ConfigParser.SafeConfigParser()
    config.read(cliopts.config)
    try:
        options = dict(config.items('node'))
    except ConfigParser.NoSectionError:
        sys.stderr.write('Configuration error: `node` section must exist '
                         'in `%s`\n' % cliopts.config)
        sys.exit(1)
    
    # applying default config file options
    for opt, default in DEFAULT_CONFIGURATION.iteritems():
        if opt not in options or not options[opt]:
            if default is None:
                sys.stderr.write('Configuration error: you must specify `%s` '
                                 'option in `%s` !\n' % (opt, cliopts.config))
                sys.exit(1)
            else:
                options[opt] = default
    
    # Merge command options and .conf file options
    for opt in ('daemonize', 'pidfile', 'stdout'):
        if getattr(cliopts, opt) is not None:
            options[opt] = getattr(cliopts, opt)
    
    # Create option set for the daemonization process
    daemon_opts = {}
    daemonize = options['daemonize']
    if isinstance(daemonize, str):
        daemonize = daemonize in ('yes', 'true')
    daemon_opts['detach_process'] = daemonize
    if not daemonize:
        daemon_opts['stderr'] = sys.stderr
        daemon_opts['stdout'] = sys.stderr
    if options['pidfile']:
        #daemon_opts['pidfile'] = lockfile.FileLock(options['pidfile'])
        pidfile = open(cliopts.pidfile, 'w')
        daemon_opts['files_preserve'] = [pidfile]
    
    with DaemonContext(**daemon_opts):
        # write pid in pidfile
        if pidfile is not None:
            pidfile.write('%s' % os.getpid())
            pidfile.flush()
        # register pidfile cleaning
        @atexit.register
        def clean_pidfile():
            pidfile.seek(0)
            pidfile.truncate()
            pidfile.flush()
        # run node
        while True:
           run_node(options)
           logging.critical('Critical error, restarting node !')
           sleep(2)
