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

import copy
import json
import optparse
import os
import pickle
import sys

from reddwarfclient import client
from reddwarfclient.xml import ReddwarfXmlClient
from reddwarfclient import exceptions


def methods_of(obj):
    """Get all callable methods of an object that don't start with underscore
    returns a list of tuples of the form (method_name, method)"""
    result = {}
    for i in dir(obj):
        if callable(getattr(obj, i)) and not i.startswith('_'):
            result[i] = getattr(obj, i)
    return result


def check_for_exceptions(resp, body):
    if resp.status in (400, 422, 500):
            raise exceptions.from_response(resp, body)


def print_actions(cmd, actions):
    """Print help for the command with list of options and description"""
    print ("Available actions for '%s' cmd:") % cmd
    for k, v in actions.iteritems():
        print "\t%-20s%s" % (k, v.__doc__)
    sys.exit(2)


def print_commands(commands):
    """Print the list of available commands and description"""

    print "Available commands"
    for k, v in commands.iteritems():
        print "\t%-20s%s" % (k, v.__doc__)
    sys.exit(2)


def limit_url(url, limit=None, marker=None):
    if not limit and not marker:
        return url
    query = []
    if marker:
        query.append("marker=%s" % marker)
    if limit:
        query.append("limit=%s" % limit)
    query = '?' + '&'.join(query)
    return url + query


class CliOptions(object):
    """A token object containing the user, apikey and token which
       is pickleable."""

    APITOKEN = os.path.expanduser("~/.apitoken")

    DEFAULT_VALUES = {
        'username':None,
        'apikey':None,
        'tenant_id':None,
        'auth_url':None,
        'auth_type':'keystone',
        'service_type':'reddwarf',
        'service_name':'Reddwarf',
        'region':'RegionOne',
        'service_url':None,
        'insecure':False,
        'verbose':False,
        'debug':False,
        'token':None,
        'xml':None,
    }

    def __init__(self, **kwargs):
        for key, value in self.DEFAULT_VALUES.items():
            setattr(self, key, value)

    @classmethod
    def default(cls):
        kwargs = copy.deepcopy(cls.DEFAULT_VALUES)
        return cls(**kwargs)

    @classmethod
    def load_from_file(cls):
        try:
            with open(cls.APITOKEN, 'rb') as token:
                return pickle.load(token)
        except IOError:
            pass  # File probably not found.
        except:
            print("ERROR: Token file found at %s was corrupt." % cls.APITOKEN)
        return cls.default()


    @classmethod
    def save_from_instance_fields(cls, instance):
        apitoken = cls.default()
        for key, default_value in cls.DEFAULT_VALUES.items():
            final_value = getattr(instance, key, default_value)
            setattr(apitoken, key, final_value)
        with open(cls.APITOKEN, 'wb') as token:
            pickle.dump(apitoken, token, protocol=2)


    @classmethod
    def create_optparser(cls):
        oparser = optparse.OptionParser(
            usage="%prog [options] <cmd> <action> <args>",
            version='1.0', conflict_handler='resolve')
        file = cls.load_from_file()
        def add_option(*args, **kwargs):
            if len(args) == 1:
                name = args[0]
            else:
                name = args[1]
            kwargs['default'] = getattr(file, name, cls.DEFAULT_VALUES[name])
            oparser.add_option("--%s" % name, **kwargs)

        add_option("verbose", action="store_true",
                   help="Show equivalent curl statement along "
            "with actual HTTP communication.")
        add_option("debug", action="store_true",
                   help="Show the stack trace on errors.")
        add_option("auth_url", help="Auth API endpoint URL with port and "
            "version. Default: http://localhost:5000/v2.0")
        add_option("username", help="Login username")
        add_option("apikey", help="Api key")
        add_option("tenant_id",
                   help="Tenant Id associated with the account")
        add_option("auth_type",
            help="Auth type to support different auth environments, \
                                Supported values are 'keystone', 'rax'.")
        add_option("service_type",
            help="Service type is a name associated for the catalog")
        add_option("service_name",
            help="Service name as provided in the service catalog")
        add_option("service_url",
            help="Service endpoint to use if the catalog doesn't have one.")
        add_option("region", help="Region the service is located in")
        add_option("insecure", action="store_true",
                   help="Run in insecure mode for https endpoints.")
        add_option("token", help="Token from a prior login.")
        add_option("xml", action="store_true", help="Changes format to XML.")


        oparser.add_option("--secure", action="store_false", dest="insecure",
                   help="Run in insecure mode for https endpoints.")
        oparser.add_option("--json", action="store_false", dest="xml",
                   help="Changes format to JSON.")
        oparser.add_option("--terse", action="store_false", dest="verbose",
                   help="Toggles verbose mode off.")
        oparser.add_option("--hide-debug", action="store_false", dest="debug",
                   help="Toggles debug mode off.")
        return oparser


class ArgumentRequired(Exception):
    def __init__(self, param):
        self.param = param

    def __str__(self):
        return 'Argument "--%s" required.' % self.param


class CommandsBase(object):
    params = []

    def __init__(self, parser):
        self._parse_options(parser)

    def get_client(self):
        """Creates the all important client object."""
        try:
            if self.xml:
                client_cls = ReddwarfXmlClient
            else:
                client_cls = client.ReddwarfHTTPClient
            if self.verbose:
                client.log_to_streamhandler(sys.stdout)
                client.RDC_PP = True

            return client.Dbaas(self.username, self.apikey, self.tenant_id,
                          auth_url=self.auth_url,
                          auth_strategy=self.auth_type,
                          service_type=self.service_type,
                          service_name=self.service_name,
                          region_name=self.region,
                          service_url=self.service_url,
                          insecure=self.insecure,
                          client_cls=client_cls)
        except:
            if self.debug:
                raise
            print sys.exc_info()[1]

    def _safe_exec(self, func, *args, **kwargs):
        if not self.debug:
            try:
                return func(*args, **kwargs)
            except:
                print(sys.exc_info()[1])
                return None
        else:
            return func(*args, **kwargs)

    @classmethod
    def _prepare_parser(cls, parser):
        for param in cls.params:
            parser.add_option("--%s" % param)

    def _parse_options(self, parser):
        opts, args = parser.parse_args()
        for param in opts.__dict__:
            value = getattr(opts, param)
            setattr(self, param, value)

    def _require(self, *params):
        for param in params:
            if not hasattr(self, param):
                raise ArgumentRequired(param)
            if not getattr(self, param):
                raise ArgumentRequired(param)

    def _make_list(self, *params):
        # Convert the listed params to lists.
        for param in params:
            raw = getattr(self, param)
            if isinstance(raw, list):
                return
            raw = [item.strip() for item in raw.split(',')]
            setattr(self, param, raw)

    def _pretty_print(self, func, *args, **kwargs):
        if self.verbose:
            self._safe_exec(func, *args, **kwargs)
            return  # Skip this, since the verbose stuff will show up anyway.
        def wrapped_func():
            result = func(*args, **kwargs)
            print json.dumps(result._info, sort_keys=True, indent=4)
        self._safe_exec(wrapped_func)

    def _dumps(self, item):
        return json.dumps(item, sort_keys=True, indent=4)

    def _pretty_list(self, func, *args, **kwargs):
        result = self._safe_exec(func, *args, **kwargs)
        if self.verbose:
            return
        for item in result:
            print self._dumps(item._info)

    def _pretty_paged(self, func, *args, **kwargs):
        try:
            limit = self.limit
            if limit:
                limit = int(limit, 10)
            result = func(*args, limit=limit, marker=self.marker, **kwargs)
            if self.verbose:
                return  # Verbose already shows the output, so skip this.
            for item in result:
                print self._dumps(item._info)
            if result.links:
                for link in result.links:
                    print self._dumps((link))
        except:
            if self.debug:
                raise
            print sys.exc_info()[1]


class Auth(CommandsBase):
    """Authenticate with your username and api key"""
    params = [
              'apikey',
              'auth_strategy',
              'auth_type',
              'auth_url',
              'insecure',
              'options',
              'region',
              'service_name',
              'service_type',
              'service_url',
              'tenant_id',
              'username',
             ]

    def __init__(self, parser):
        super(Auth, self).__init__(parser)
        self.dbaas = None

    def login(self):
        """Login to retrieve an auth token to use for other api calls"""
        self._require('username', 'apikey', 'tenant_id', 'auth_url')
        try:
            self.dbaas = self.get_client()
            self.dbaas.authenticate()
            self.token = self.dbaas.client.auth_token
            self.service_url = self.dbaas.client.service_url
            CliOptions.save_from_instance_fields(self)
            print(self.token)
        except:
            if self.debug:
                raise
            print sys.exc_info()[1]


class AuthedCommandsBase(CommandsBase):
    """Commands that work only with an authicated client."""

    def __init__(self, parser):
        """Makes sure a token is available somehow and logs in."""
        super(AuthedCommandsBase, self).__init__(parser)
        try:
            self._require('token')
        except ArgumentRequired:
            if self.debug:
                raise
            print('No token argument supplied. Use the "auth login" command '
                  'to log in and get a token.\n')
            sys.exit(1)
        try:
            self._require('service_url')
        except ArgumentRequired:
            if self.debug:
                raise
            print('No service_url given.\n')
            sys.exit(1)
        self.dbaas = self.get_client()
        # Actually set the token to avoid a re-auth.
        self.dbaas.client.auth_token = self.token
        self.dbaas.client.authenticate_with_token(self.token, self.service_url)


class Paginated(object):
    """ Pretends to be a list if you iterate over it, but also keeps a
        next property you can use to get the next page of data. """

    def __init__(self, items=[], next_marker=None, links=[]):
        self.items = items
        self.next = next_marker
        self.links = links

    def __len__(self):
        return len(self.items)

    def __iter__(self):
        return self.items.__iter__()

    def __getitem__(self, key):
        return self.items[key]

    def __setitem__(self, key, value):
        self.items[key] = value

    def __delitem(self, key):
        del self.items[key]

    def __reversed__(self):
        return reversed(self.items)

    def __contains__(self, needle):
        return needle in self.items