#!/usr/bin/env python
#
#   YADT - an Augmented Deployment Tool
#   Copyright (C) 2010-2013  Immobilien Scout GmbH
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program 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 General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

import sys
import logging

from twisted.internet import reactor
from twisted.python import log
from twisted.internet.task import deferLater
from yadtshell.commandline import (EXIT_CODE_MISSING_COMMAND,
                                   ensure_command_has_required_arguments,
                                   normalize_options,
                                   validate_command_line_options)
from optparse import OptionParser, OptionGroup


reactor.return_code = 127

parser = OptionParser(usage='%prog [options] {status|info|dump|start|stop|ignore|unignore|lock|unlock} [uri ...] [command options]')
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')

if len(sys.argv) < 2:
    parser.print_help()
    sys.exit(EXIT_CODE_MISSING_COMMAND)

cmd = sys.argv[1]

# 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')


opts, args = parser.parse_args()

opts = normalize_options(opts)

args = args[1:]
component_names = args

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

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

ensure_command_has_required_arguments(cmd, component_names, parser.print_help)
validate_command_line_options(cmd, opts, parser.print_help)

yadtshell.settings.load_settings()

logging.getLogger("twisted").setLevel(logging.WARN)
logging.getLogger("broadcaster").setLevel(logging.WARN)

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

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

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()
    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, 2, 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))

if reactor.return_code == 0:
    print yadtshell.settings.term.render('${GREEN}${BOLD}%s SUCCESSFUL.${NORMAL}' % cmd.upper())
else:
    print yadtshell.settings.term.render('${RED}${BOLD}%s FAILED.${NORMAL}' % cmd.upper())
    logger.critical('exit code: %i' % reactor.return_code)
    logger.info('For details see: "{0}"'.format(yadtshell.settings.log_file))

sys.exit(reactor.return_code)

