# Copyright 2010 Jacob Kaplan-Moss

# Copyright 2011 OpenStack LLC.
# Copyright 2012 Rackspace

# 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.

"""
Base utilities to build API operation managers and objects on top of.
"""

import pyrax.utils as utils


class BaseResource(object):
    """
    A resource represents a particular instance of an object (server, flavor,
    etc). This is pretty much just a bag for attributes.
    """
    HUMAN_ID = False
    NAME_ATTR = "name"

    def __init__(self, manager, info, loaded=False):
        self._loaded = loaded
        self.manager = manager
        self._info = info
        self._add_details(info)


    @property
    def human_id(self):
        """Subclasses may override this to provide a pretty ID which can be used
        for bash completion.
        """
        if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID:
            return utils.slugify(getattr(self, self.NAME_ATTR))
        return None


    def _add_details(self, info):
        """
        Takes the dict returned by the API call and sets the
        corresponding attributes on the object.
        """
        for (key, val) in info.iteritems():
            setattr(self, key, val)


    def __getattr__(self, key):
        """
        Many objects are lazy-loaded: only their most basic details
        are initially returned. The first time any of the other attributes
        are referenced, a GET is made to get the full details for the
        object.
        """
        if not self.loaded:
            self.get()
        # Attribute should be set; if not, it's not valid
        try:
            return self.__dict__[key]
        except KeyError:
            raise AttributeError("'%s' object has no attribute '%s'." % (self.__class__, key))


    def __repr__(self):
        reprkeys = sorted(key for key in self.__dict__.keys()
               if (key[0] != "_") and (key != "manager"))
        info = ", ".join("%s=%s" % (key, getattr(self, key)) for key in reprkeys)
        return "<%s %s>" % (self.__class__.__name__, info)


    def get(self):
        """Gets the details for the object."""
        # set 'loaded' first ... so if we have to bail, we know we tried.
        self.loaded = True
        if not hasattr(self.manager, "get"):
            return
        new = self.manager.get(self)
        if new:
            self._add_details(new._info)


    def delete(self):
        """Deletes the object."""
        # set 'loaded' first ... so if we have to bail, we know we tried.
        self.loaded = True
        if not hasattr(self.manager, "delete"):
            return
        self.manager.delete(self)


    def __eq__(self, other):
        """
        Two resource objects that represent the same entity in the cloud
        should be considered equal if they have the same ID. If they
        don't have IDs, but their attribute info matches, they are equal.
        """
        if not isinstance(other, self.__class__):
            return False
        if hasattr(self, "id") and hasattr(other, "id"):
            return self.id == other.id
        return self._info == other._info


    def reload(self):
        """
        Since resource objects are essentially snapshots of the entity they
        represent at the time they are created, they do not update as the
        entity updates. For example, the 'status' attribute can change, but
        the instance's value for 'status' will not. This method will refresh
        the instance with the current state of the underlying entity.
        """
        new_obj = self.manager.get(self.id)
        self._add_details(new_obj._info)


    def _get_loaded(self):
        return self._loaded

    def _set_loaded(self, val):
        self._loaded = val

    loaded = property(_get_loaded, _set_loaded)
