#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
This is the administration program for heat-api-cloudwatch.
It is simply a command-line interface for adding, modifying, and retrieving
information about the cloudwatch alarms and metrics belonging to a user.
It is a convenience application that talks to the heat Cloudwatch API server.
"""

import gettext
import optparse
import os
import os.path
import sys
import time
import logging

# If ../heat/__init__.py exists, add ../ to Python search path, so that
# it will override what happens to be installed in /usr/(local/)lib/python...
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
                                   os.pardir,
                                   os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
    sys.path.insert(0, possible_topdir)

scriptname = os.path.basename(sys.argv[0])

gettext.install('heat', unicode=1)

from heat.cfn_client import boto_client_cloudwatch as heat_client
from heat.version import version_info as version
from heat.common import config
from heat.common import exception
from heat.cfn_client import utils

DEFAULT_PORT=8003

@utils.catch_error('alarm-describe')
def alarm_describe(options, arguments):
    '''
    Describe detail for specified alarm, or all alarms
    if no AlarmName is specified
    '''
    parameters={}
    try:
        parameters['AlarmName'] = arguments.pop(0)
    except IndexError:
        logging.info("No AlarmName passed, getting results for ALL alarms")

    c = heat_client.get_client(options.port)
    result = c.describe_alarm(**parameters)
    print c.format_metric_alarm(result)

@utils.catch_error('alarm-set-state')
def alarm_set_state(options, arguments):
    '''
    Temporarily set state for specified alarm
    '''
    usage = ('''Usage:
%s alarm-set-state AlarmName StateValue [StateReason]''' %
        (scriptname))

    parameters={}
    try:
        parameters['AlarmName'] = arguments.pop(0)
        parameters['StateValue'] = arguments.pop(0)
    except IndexError:
        logging.error("Must specify AlarmName and StateValue")
        print usage
        print "StateValue must be one of %s, %s or %s" % (
                      heat_client.BotoCWClient.ALARM_STATES)
        return utils.FAILURE
    try:
        parameters['StateReason'] = arguments.pop(0)
    except IndexError:
        parameters['StateReason'] = ""

    # We don't handle attaching data to state via this cli tool (yet)
    parameters['StateReasonData'] = None

    c = heat_client.get_client(options.port)
    result = c.set_alarm_state(**parameters)
    print result


@utils.catch_error('metric-list')
def metric_list(options, arguments):
    '''
    List all metric data for a given metric (or all metrics if none specified)
    '''
    parameters={}
    try:
        parameters['MetricName'] = arguments.pop(0)
    except IndexError:
        logging.info("No MetricName passed, getting results for ALL alarms")

    c = heat_client.get_client(options.port)
    result = c.list_metrics(**parameters)
    print c.format_metric(result)


@utils.catch_error('metric-put-data')
def metric_put_data(options, arguments):
    '''
    Create a datapoint for a specified metric
    '''
    usage = ('''Usage:
%s metric-put-data AlarmName Namespace MetricName Units MetricValue
e.g
%s metric-put-data HttpFailureAlarm system/linux ServiceFailure Count 1
''' % (scriptname, scriptname))

    # NOTE : we currently only support metric datapoints associated with a
    # specific AlarmName, due to the current engine/db cloudwatch
    # implementation, we should probably revisit this so we can support
    # more generic metric data collection
    parameters={}
    try:
        parameters['AlarmName'] = arguments.pop(0)
        parameters['Namespace'] = arguments.pop(0)
        parameters['MetricName'] = arguments.pop(0)
        parameters['MetricUnit'] = arguments.pop(0)
        parameters['MetricValue'] = arguments.pop(0)
    except IndexError:
        logging.error("Please specify the alarm, metric, unit and value")
        print usage
        return utils.FAILURE

    c = heat_client.get_client(options.port)
    result = c.put_metric_data(**parameters)
    print result


def create_options(parser):
    """
    Sets up the CLI and config-file options that may be
    parsed and program commands.

    :param parser: The option parser
    """
    parser.add_option('-v', '--verbose', default=False, action="store_true",
                      help="Print more verbose output")
    parser.add_option('-d', '--debug', default=False, action="store_true",
                      help="Print more verbose output")
    parser.add_option('-p', '--port', dest="port", type=int,
                      default=DEFAULT_PORT,
                      help="Port the heat API host listens on. "
                           "Default: %default")


def parse_options(parser, cli_args):
    """
    Returns the parsed CLI options, command to run and its arguments, merged
    with any same-named options found in a configuration file

    :param parser: The option parser
    """
    if not cli_args:
        cli_args.append('-h')  # Show options in usage output...

    (options, args) = parser.parse_args(cli_args)

    # HACK(sirp): Make the parser available to the print_help method
    # print_help is a command, so it only accepts (options, args); we could
    # one-off have it take (parser, options, args), however, for now, I think
    # this little hack will suffice
    options.__parser = parser

    if not args:
        parser.print_usage()
        sys.exit(0)

    command_name = args.pop(0)
    command = lookup_command(parser, command_name)

    if options.debug:
        logging.basicConfig(format='%(levelname)s:%(message)s',
            level=logging.DEBUG)
        logging.debug("Debug level logging enabled")
    elif options.verbose:
        logging.basicConfig(format='%(levelname)s:%(message)s',
            level=logging.INFO)
    else:
        logging.basicConfig(format='%(levelname)s:%(message)s',
            level=logging.WARNING)

    return (options, command, args)


def print_help(options, args):
    """
    Print help specific to a command
    """
    parser = options.__parser

    if not args:
        parser.print_usage()

    subst = {'prog': os.path.basename(sys.argv[0])}
    docs = [lookup_command(parser, cmd).__doc__ % subst for cmd in args]
    print '\n\n'.join(docs)


def lookup_command(parser, command_name):
    base_commands = {'help': print_help}

    watch_commands = {
        'describe': alarm_describe,
        'set-state': alarm_set_state,
        'metric-list': metric_list,
        'metric-put-data': metric_put_data}

    commands = {}
    for command_set in (base_commands, watch_commands):
        commands.update(command_set)

    try:
        command = commands[command_name]
    except KeyError:
        parser.print_usage()
        sys.exit("Unknown command: %s" % command_name)

    return command


def main():
    '''
    '''
    usage = """
%prog <command> [options] [args]

Commands:

    help <command>  Output help for one of the commands below

    describe                    Describe a specified alarm (or all alarms)

    set-state                   Temporarily set the state of an alarm

    metric-list                 List data-points for specified metric

    metric-put-data             Publish data-point for specified  metric

"""
    deferred_string = version.deferred_version_string(prefix='%prog ')
    oparser = optparse.OptionParser(version=str(deferred_string),
                                    usage=usage.strip())
    create_options(oparser)
    (opts, cmd, args) = parse_options(oparser, sys.argv[1:])

    try:
        start_time = time.time()
        result = cmd(opts, args)
        end_time = time.time()
        logging.debug("Completed in %-0.4f sec." % (end_time - start_time))
        sys.exit(result)
    except (RuntimeError,
            NotImplementedError,
            exception.ClientConfigurationError), ex:
        oparser.print_usage()
        logging.error("ERROR: %s" % ex)
        sys.exit(1)


if __name__ == '__main__':
    main()
