#!/usr/bin/env python
import sys
import logging

from twisted.internet import reactor
from twisted.python import log
from twisted.internet.task import deferLater


reactor.return_code = 127

cmd = None
if len(sys.argv) > 1:
    cmd = sys.argv[1]

from optparse import OptionParser, OptionGroup
parser = OptionParser(usage='%prog [options] {status|info|dump|start|stop|ignore|unignore|lock|unlock} [uri ...]')

# global stuff
parser.add_option('-n', '--dryrun', action='store_true', dest='dryrun', default=False, help='does nothing')
parser.add_option('-v', '', action='store_true', dest='verbose', default=False, help='verbose output')

# status stuff
if cmd in ['status']:
    parser.add_option('', '--use-cache-only', action='store_true', default=False, dest='use_cache_only', help='dont query hosts, but use cached data only')

# parallel actions stuff
if cmd in ['start', 'stop', 'ignore', 'unignore', 'lock', 'unlock', 'update', 'updateartefact']:
    parser.add_option('-p', '--parallel', action='store', dest='parallel', default='1', help='parallel execution, where and how')

# message for lock, ignore
if cmd in ['ignore', 'lock']:
    group = OptionGroup(parser, '(MANDATORY) reason for command')
    group.add_option('-m', '--message', dest='message')
    group.add_option('', '--force', action='store_true', dest='force', default=False, help='force the command')
    parser.add_option_group(group)

# dump stuff
if cmd in ['dump']:
    group = OptionGroup(parser, 'dumping information')
    group.add_option('', '--attribute', action='store', dest='attribute', help='queries given attribute only')
    group.add_option('', '--show-pending-updates', action='store_const', dest='filter', const='pending-updates', help='show all pending updates for target')
    group.add_option('', '--show-current-artefacts', action='store_const', dest='filter', const='current-artefacts', help='show all artefacts on target')
    parser.add_option_group(group)

# info stuff
if cmd in ['info']:
    parser.add_option('', '--full', action='store_true', dest='full', default=False, help='shows all artefacts')

if not cmd:
    parser.print_help()
    sys.exit(1)


opts, args = parser.parse_args()

args = args[1:]


observer = log.PythonLoggingObserver()
observer.start()

import yadtshell
logger = logging.getLogger('yadtshell')



if opts.verbose:
    yadtshell.settings.ch.setLevel(logging.DEBUG)

if opts.dryrun:
    yadtshell.settings.ybc = yadtshell.settings.DummyBroadcaster()

component_names = args
if component_names:
    components = yadtshell.util.restore_current_state()
    component_names = yadtshell.helper.expand_hosts(component_names)
    component_names = yadtshell.helper.glob_hosts(components, component_names)

def create_simple_plan(cmd, component_names):
    action_set = set()
    for component_name in component_names:
        component = components.get(component_name, None)
        if not component:
            component = components[yadtshell.uri.change_version(component_name, 'current')]
        if not component:
            logger.warning('could not resolve uri %s' % component_name)
            continue
        action_set.add(yadtshell.actions.Action(cmd, component.uri, kwargs=vars(opts)))
    plan = yadtshell.actions.ActionPlan(cmd, action_set)
    yadtshell.util.dump_action_plan(cmd, plan)
    return plan

def createDeferredFromPlan(plan):
    plan = yadtshell.metalogic.apply_instructions(plan, opts.parallel)
    yadtshell.util.dump_action_plan(cmd, plan)
    am = yadtshell.ActionManager()
    return am.action(flavor=cmd, **vars(opts))



deferred = None
if cmd == 'status':
    deferred = yadtshell.status(hosts=args, **vars(opts))
elif cmd == 'info':
    yadtshell.info(**vars(opts))
    sys.exit(0)
elif cmd == 'dump':
    yadtshell.dump(args, **vars(opts))
    sys.exit(0)
elif cmd == 'update':
    #deferred = yadtshell.status(use_cache_only=True)
    deferred = yadtshell.status()
    deferred.addCallback(
        yadtshell.update.compare_versions, 
        args,
        **vars(opts)
    )
    am = yadtshell.ActionManager()
    deferred.addCallback(am.action, **vars(opts))
elif cmd in ['ignore', 'unignore', 'lock', 'unlock', 'updateartefact']:
    plan = create_simple_plan(cmd, component_names)
    deferred = createDeferredFromPlan(plan)
else:
    try:
        plan = yadtshell.metalogic.metalogic(cmd, component_names)
        deferred = createDeferredFromPlan(plan)
    except Exception, e:
        logger.critical('an error occured while trying to "%s %s"' % (cmd, ', '.join(args)))
        logger.debug(e)
        # TODO event failed needed here
        sys.exit(1)


def publish_result():
    if reactor.return_code == 0:
        state = 'finished'
    else:
        state = 'failed'
    yadtshell.settings.ybc.publish_cmd(cmd=cmd, state=state)
    logger.debug('waiting for outstanding events to be delivered')
    return deferLater(reactor, 0, observer.stop)

def publish_start():
    yadtshell.settings.ybc.publish_cmd(cmd=cmd, state='started')


try:
    deferred.addErrback(yadtshell.twisted.report_error, logger.critical)
    deferred.addBoth(yadtshell.twisted.stop_and_return)

    if not reactor.running:
        yadtshell.settings.ybc.addOnSessionOpenHandler(publish_start)
        reactor.addSystemEventTrigger('before', 'shutdown', publish_result)
        reactor.run()

except yadtshell.actions.ActionException, e:
    reactor.return_code = e.exitcode
    msg = e.message
    if e.rootcause:
        msg += ': ' + str(e.rootcause)
    logger.critical(msg)
except BaseException, e:
    reactor.return_code = 2
    logger.critical(str(e))

logger.info('exit code: %i' % reactor.return_code)
sys.exit(reactor.return_code)

