#!/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 json
import logging

from urlparse import urlparse
# 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 import  boto_client_cloudwatch as heat_client
from heat import version
from heat.common import config
from heat.common import exception
from heat 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

"""
    oparser = optparse.OptionParser(version='%%prog %s'
                                    % version.version_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()