#!/usr/bin/env python
""" Launches or updates cloudformation stacks based on the given config.

The script is smart enough to figure out if anything (the template, or
parameters) has changed for a given stack. If not, it will skip that stack.

Can also pull parameters from other stack's outputs.
"""

import argparse
import logging
from collections import Mapping
import copy

import yaml
from stacker.builder import Builder

logger = logging.getLogger()

DEBUG_FORMAT = ('[%(asctime)s] %(levelname)s %(name)s:%(lineno)d'
                '(%(funcName)s) - %(message)s')
INFO_FORMAT = ('[%(asctime)s] %(message)s')

ISO_8601 = '%Y-%m-%dT%H:%M:%S'


def get_stack_definitions(config, stack_list):
    """ Extract stack definitions from the config.

    If no stack_list given, return stack config as is.
    """
    if not stack_list:
        return config['stacks']
    return [s for s in config['stacks'] if s['name'] in stack_list]


class KeyValueAction(argparse.Action):
    def __init__(self, option_strings, dest, default=None, nargs=None,
                 **kwargs):
        if nargs:
            raise ValueError("nargs not allowed")
        default = default or {}
        super(KeyValueAction, self).__init__(option_strings, dest, nargs,
                                             default=default, **kwargs)

    def __call__(self, parser, namespace, values, option_string=None):
        if not isinstance(values, Mapping):
            raise ValueError("type must be 'key_value'")
        if not getattr(namespace, self.dest):
            setattr(namespace, self.dest, {})
        getattr(namespace, self.dest).update(values)


def key_value_arg(string):
    try:
        k, v = string.split("=", 1)
    except ValueError:
        raise argparse.ArgumentTypeError(
            "%s does not match KEY=VALUE format." % string)
    return {k: v}


def parse_args():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('-r', '--region', default='us-east-1',
                        help="The AWS region to launch in. Default: "
                             "%(default)s")
    parser.add_argument('-m', '--max-zones', type=int,
                        help="Gives you the ability to limit the # of zones "
                             "that resources will be launched in. If not "
                             "given, then resources will be launched in all "
                             "available availability zones.")
    parser.add_argument('-v', '--verbose', action='count', default=0,
                        help='Increase output verbosity. May be specified up '
                             'to twice.')
    parser.add_argument('-d', '--domain',
                        help="The domain to run in. Gets converted into the "
                             "BaseDomain Parameter for use in stack "
                             "templates.")
    parser.add_argument("-p", "--parameter", dest="parameters",
                        metavar="PARAMETER=VALUE", type=key_value_arg,
                        action=KeyValueAction, default={},
                        help="Adds parameters from the command line "
                             "that can be used inside any of the stacks "
                             "being built. Can be specified more than once.")
    parser.add_argument("--stacks", action="append",
                        metavar="STACKNAME", type=str,
                        help="Only work on the stacks given. Can be "
                             "specified more than once. If not specified "
                             "then stacker will work on all stacks in the "
                             "config file.")
    parser.add_argument('config',
                        help="The config file where stack configuration is "
                             "located. Must be in yaml format.")

    return parser.parse_args()


def setup_logging(verbosity):
    log_level = logging.INFO
    log_format = INFO_FORMAT
    if verbosity > 0:
        log_level = logging.DEBUG
        log_format = DEBUG_FORMAT
    if verbosity < 2:
        logging.getLogger('boto').setLevel(logging.CRITICAL)

    return logging.basicConfig(format=log_format, datefmt=ISO_8601,
                               level=log_level)

if __name__ == '__main__':
    args = parse_args()
    setup_logging(args.verbose)
    parameters = copy.deepcopy(args.parameters)
    parameters['BaseDomain'] = args.domain

    with open(args.config) as fd:
        config = yaml.load(fd)

    builder = Builder(args.region, mappings=config['mappings'],
                      parameters=parameters)

    stack_definitions = get_stack_definitions(config, args.stacks)
    stack_names = [s['name'] for s in stack_definitions]
    logger.info("Working on stacks: %s", ', '.join(stack_names))
    builder.build(stack_definitions)
