#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
#    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.

"""CLI interface for common Melange client opertaions.

Simple cli for creating ip blocks, adding policies and rules for ip address
allocations from these blocks.

"""

import optparse
import os
from os import environ as env
import sys

# If ../melange/__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, 'melange', '__init__.py')):
    sys.path.insert(0, possible_topdir)

from melange import version
from melange.common import auth
from melange.common import client as base_client
from melange.common import utils
from melange.ipam import client


def create_options(parser):
    """Sets up the CLI and config-file options.

    :param parser: The option parser
    :returns: None

    """
    parser.add_option('-v', '--verbose', default=False, action="store_true",
                      help="Print more verbose output")
    parser.add_option('-H', '--host', metavar="ADDRESS", default="0.0.0.0",
                      help="Address of Melange API host. "
                           "Default: %default")
    parser.add_option('-p', '--port', dest="port", metavar="PORT",
                      type=int, default=9898,
                      help="Port the Melange API host listens on. "
                           "Default: %default")
    parser.add_option('-t', '--tenant', dest="tenant", metavar="TENANT",
                      type=str, default=env.get('MELANGE_TENANT', None),
                      help="tenant id in case of tenant resources")
    parser.add_option('--auth-token', dest="auth_token",
                      metavar="MELANGE_AUTH_TOKEN",
                      default=env.get('MELANGE_AUTH_TOKEN', None),
                      type=str, help="Auth token received from keystone")
    parser.add_option('-u', '--username', dest="username",
                      metavar="MELANGE_USERNAME",
                      default=env.get('MELANGE_USERNAME', None),
                      type=str, help="Melange user name")
    parser.add_option('-k', '--api-key', dest="api_key",
                      metavar="MELANGE_API_KEY",
                      default=env.get('MELANGE_API_KEY', None),
                      type=str, help="Melange access key")
    parser.add_option('-a', '--auth-url', dest="auth_url",
                      metavar="MELANGE_AUTH_URL", type=str,
                      default=env.get('MELANGE_AUTH_URL', None),
                      help="Url of keystone service")
    parser.add_option('--timeout', dest="timeout",
                      metavar="MELANGE_TIME_OUT", type=int,
                      default=env.get('MELANGE_TIME_OUT', None),
                      help="timeout for melange client operations")


def parse_options(parser, cli_args):
    """Parses CLI options.

    Returns 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
    :returns: (options, args)

    """
    (options, args) = parser.parse_args(cli_args)
    if not args:
        parser.print_usage()
        sys.exit(2)
    return (options, args)


def usage():
    usage = """
%prog category action [args] [options]

Available categories:

    """
    for k, _v in categories.iteritems():
        usage = usage + ("\t%s\n" % k)
    return usage.strip()


categories = {
    'ip_block': client.IpBlockClient,
    'subnet': client.SubnetClient,
    'policy': client.PolicyClient,
    'unusable_ip_range': client.UnusableIpRangesClient,
    'unusable_ip_octet': client.UnusableIpOctetsClient,
    'allocated_ips': client.AllocatedIpAddressesClient,
    'ip_address': client.IpAddressesClient,
    'ip_route': client.IpRouteClient,
    'interface': client.InterfaceClient,
    'mac_address_range': client.MacAddressRangeClient,
    'allowed_ip': client.AllowedIpClient,
}


def lookup(name, hash):
    result = hash.get(name, None)
    if not result:
        print "%s does not match any options:" % name
        print_keys(hash)
        sys.exit(2)

    return result


def print_keys(hash):
    for k, _v in hash.iteritems():
        print "\t%s" % k


def methods_of(obj):
    """Gets callable public methods.

    Get all callable methods of an object that don't start with underscore
    returns a dictionary of the form dict(method_name, method)

    """

    def is_public_method(attr):
        return callable(getattr(obj, attr)) and not attr.startswith('_')

    return dict((attr, getattr(obj, attr)) for attr in dir(obj)
              if is_public_method(attr))


def auth_client_factory(options):
    if options.auth_url or options.auth_token:
        return auth.KeystoneClient(options.auth_url,
                                   options.username,
                                   options.api_key,
                                   options.auth_token)


def main():
    oparser = optparse.OptionParser(version='%%prog %s'
                                    % version.version_string(),
                                    usage=usage())
    create_options(oparser)
    (options, args) = parse_options(oparser, sys.argv[1:])

    script_name = os.path.basename(sys.argv[0])
    category = args.pop(0)
    http_client = base_client.HTTPClient(options.host,
                                         options.port,
                                         options.timeout)

    category_client_class = lookup(category, categories)

    client = category_client_class(http_client,
                                   auth_client_factory(options),
                                   options.tenant)

    client_actions = methods_of(client)
    if len(args) < 1:
        print "Usage: " + script_name + " category action [<args>]"
        print _("Available actions for %s category:") % category
        print_keys(client_actions)
        sys.exit(2)

    if category_client_class.TENANT_ID_REQUIRED and not options.tenant:
        print _("Please provide a tenant id for this action."
                "You can use option '-t' to provide the tenant id.")
        sys.exit(2)

    action = args.pop(0)
    fn = lookup(action, client_actions)

    # call the action with the remaining arguments
    try:
        print fn(*args)
        sys.exit(0)
    except TypeError:
        print _("Possible wrong number of arguments supplied")
        print "Usage: %s %s %s" % (script_name, category,
                                   utils.MethodInspector(fn))
        if options.verbose:
            raise
        sys.exit(2)
    except Exception:
        print _("Command failed, please check log for more info")
        if options.verbose:
            raise
        sys.exit(2)


if __name__ == '__main__':
    main()
