import f5
import f5.util
import re

from bigsuds import ServerError

# Convert PoolMember objects into a list of address, port dictionaries
def pms_to_addrportsq(poolmembers):
    """ Converts PoolMembers into a list of address, port dictionaries """
    return [{'address': p._node.name, 'port': p._port} for p in poolmembers]


# Truncate lbmethod
def munge_lbmethod(lbmethods):
    return [l[10:].lower() for l in lbmethods]


# Un-truncate lbmethod
def unmunge_lbmethod(lbmethods):
    return['LB_METHOD_' + l.upper() for l in lbmethods]


class Pool(object):
    __version = 11
    __wsdl = 'LocalLB.Pool'

    def __init__(self, name, lb=None, description=None, lbmethod=None,
            members=None, minimum_active_member=None, minimum_up_member=None,
            slow_ramp_time=None, fromdict=None):

        self._lb = lb

        if fromdict is not None:
            if lb is not None:
                self.dictionary = fromdict
            else:
                self._dictionary = fromdict
        else:
            self.__name                 = name
            self._active_member_count   = None
            self._description           = description
            self._lbmethod              = lbmethod
            self._members               = members
            self._minimum_active_member = minimum_active_member
            self._minimum_up_member     = minimum_up_member
            self._slow_ramp_time        = None
            self._statistics            = None

        self._lbcall = self.__lbcall

    def __repr__(self):
        return "f5.Pool('%s')" % (self._name)

    def __str__(self):
        return self._name

    # This just adds the wsdl to calls to the lb for convenience
    def __lbcall(self, call, *args, **kwargs):
        return self.lb._call(self.__wsdl + '.' + call, *args, **kwargs)

    @classmethod
    def _lbcall(cls, lb, call, *args, **kwargs):
        return lb._call(cls.__wsdl + '.' + call, *args, **kwargs)

    ###########################################################################
    # Properties
    ###########################################################################
    # Asynchronous properties are prefixed with a '_'
    #
    # All properties are fetched directly from the lb, but also stored in local 
    # variables prefixed with an underscore '_' for convenience.
    #
    # If you want to fetch an attribute without calling the lb, get the
    # attribute prefixed with an underscore.

    #### LB ####
    @property
    def lb(self):
        return self._lb

    @lb.setter
    def lb(self, value):
        self.refresh()
        self._lb = value

    #### NAME ####
    @property
    def name(self):
        return self.__name

    @property
    def _name(self):
        return self.__name

    @_name.setter
    @f5.util.updatefactorycache
    def _name(self, value):
        self.__name = name

    #### ACTIVE_MEMBER_COUNT ####
    @property
    def active_member_count(self):
        self._active_member_count = self._lbcall('get_active_member_count', [self._name])[0]
        return self._active_member_count

    #### DESCRIPTION ####
    @property
    def description(self):
        self._description = self._lbcall('get_description', [self._name])[0]
        return self._description

    @description.setter
    @f5.util.lbwriter2
    def description(self, value):
        self._lbcall('set_description', [self._name], [value])
        self._description = value

    #### LBMETHOD ####
    @property
    def lbmethod(self):
        self._lbmethod = munge_lbmethod(self._lbcall('get_lb_method', [self._name]))[0]
        return self._lbmethod

    @lbmethod.setter
    def lbmethod(self, value):
        self._lbcall('set_lb_method', [self._name], unmunge_lbmethod([value]))
        self._lbmethod = value.lower()

    #### MEMBERS ####
    @property
    def members(self):
        self._members = f5.PoolMember._get(self._lb, pools=[self], minimal=True)
        return self._members

    @members.setter
    @f5.util.lbtransaction
    def members(self, value):
        current = self._lbcall('get_member', [self._name])
        should  = pms_to_addrportsq(value)

        self._lbcall('remove_member', [self._name], [current])
        self._lbcall('add_member', [self._name], [should])
        self._members = value

    #### MINIMUM_ACTIVE_MEMBER ####
    @property
    def minimum_active_member(self):
        self._minimum_active_member = self._lbcall(
                'get_minimum_active_member', [self._name])[0]
        return self._minimum_active_member

    @minimum_active_member.setter
    @f5.util.lbwriter2
    def minimum_active_member(self, value):
        self._lbcall('set_minimum_active_member', [self._name], [value])
        self._minimum_active_member = value

    #### MINIMUM_UP_MEMBER ####
    @property
    def minimum_up_member(self):
        self._minimum_up_member = self._lbcall(
                'get_minimum_up_member', [self._name])[0]
        return self._minimum_up_member

    @minimum_up_member.setter
    @f5.util.lbwriter2
    def minimum_up_member(self, value):
        self._lbcall('set_minimum_up_member', [self._name], [value])
        self._minimum_up_member = value

    #### SLOW_RAMP_TIME ####
    @property
    def slow_ramp_time(self):
        self._slow_ramp_time = self._lbcall(
                'get_slow_ramp_time', [self._name])[0]
        return self._slow_ramp_time

    @slow_ramp_time.setter
    @f5.util.lbwriter2
    def slow_ramp_time(self, value):
        self._lbcall('set_slow_ramp_time', [self._name], [value])
        self._slow_ramp_time = value

    #### STATISTICS ####
    @property
    def statistics(self):
        self._statistics = self._lbcall('get_statistics',
                             [self._name])['statistics'][0]
        return self._statistics

    ###########################################################################
    # Private API
    ###########################################################################
    @classmethod
    def _get_objects(cls, lb, names, minimal=False):
        """Returns a list of Pool objects from a list of pool names"""

        if not names:
            return []

        pools = cls.factory.create(names, lb)

        if not minimal:
            active_member_count   = cls._lbcall(lb, 'get_active_member_count',
                                        names)
            description           = cls._lbcall(lb, 'get_description', names)
            lbmethod              = cls._lbcall(lb, 'get_lb_method', names)
            members               = cls._lbcall(lb, 'get_member', names)
            minimum_active_member = cls._lbcall(lb, 'get_minimum_active_member',
                                        names)
            minimum_up_member     = cls._lbcall(lb, 'get_minimum_up_member',
                                        names)
            slow_ramp_time        = cls._lbcall(lb, 'get_slow_ramp_time', names)
            statistics            = cls._lbcall(lb, 'get_statistics', names)

            for idx,pool in enumerate(pools):
                pool._active_member_count   = active_member_count[idx]
                pool._description           = description[idx]
                pool._lbmethod              = lbmethod[idx]
                pool._minimum_active_member = minimum_active_member[idx]
                pool._minimum_up_member     = minimum_up_member[idx]
                pool._slow_ramp_time        = slow_ramp_time[idx]
                pool._statistics            = statistics['statistics'][idx]

                pool._members = f5.PoolMember._get_objects(lb, [pool],
                                    [members[idx]], minimal=True)

        return pools

    @classmethod
    def _get(cls, lb, pattern=None, minimal=False):
        names = cls._lbcall(lb, 'get_list')

        if not names:
            return []

        if pattern is not None:
            if not isinstance(pattern, re._pattern_type):
                pattern = re.compile(pattern)
            names = [name for name in names if pattern.match(name)]

        return cls._get_objects(lb, names, minimal)

    ###########################################################################
    # Public API
    ###########################################################################
    def refresh(self):
        """Fetch all attributes from the lb"""
        self.active_member_count
        self.description
        self.lbmethod
        self.members
        self.minimum_active_member
        self.minimum_up_member
        self.slow_ramp_time
        self.statistics

    def exists(self):
        try:
            self._lbcall('get_description', [self._name])
        except ServerError as e:
            if 'was not found' in str(e):
                return False
            else:
                raise
        except:
            raise

        return True

    def reset_statistics(self):
        self._lbcall('reset_statistics', [self._name])

    @f5.util.lbtransaction
    def save(self):
        if not self.exists():
            if self._lbmethod is None or self._members is None:
                raise RuntimeError('lbmethod and members must be set on create')
            self._lbcall('create_v2', [self._name],
                    [unmunge_lbmethod([self._lbmethod])[0]], [self._members])

            if self._description is not None:
                self.description = self._description

    @f5.util.lbwriter2
    def delete(self):
        """Delete the pool from the lb"""
        self._lbcall('delete_pool', [self._name])

Pool.factory = f5.util.CachedFactory(Pool)


class PoolList(list):
    def __init__(self,
            lb        = None,
            pattern   = None,
            partition = '/',
            fromdict  = None):

        self._lb = lb
        self._partition = partition
        self._pattern   = pattern

        if lb is not None:
            self.refresh()
        else:
            self.dictionary = fromdict

    @f5.util.restore_session_values
    def refresh(self):
        self.lb.active_folder = self._partition
        if self._partition == '/':
            self.lb.recursive_query = True

        pools = Pool._get(self._lb, self._pattern)
        del self[:]
        self.extend(pools)

    @f5.util.lbtransaction
    def sync(self, create=False):
        if create is True:
            self._lbcall('create_v2', [self.names], self._getattr('_lbmethod'),
                    [self._getattr('_members')])
        else:
            self.lbmethod = self._getattr('_lbmethod')
            self.members  = self._getattr('_members')

        self.description = self._getattr('_description')

    def _lbcall(self, call, *args, **kwargs):
        return Pool._lbcall(self._lb, call, *args, **kwargs)

    def _setattr(self, attr, values):
        if len(values) != len(self):
                raise ValueError('value must be of same length as list')

        for idx,pool in enumerate(self):
            setattr(pool, attr, values[idx])

    def _getattr(self, attr):
        return [getattr(pool, attr) for pool in self]

    @property
    def partition(self):
        return self._partition

    @partition.setter
    def partition(self, value):
        self._partition = value
        refresh()

    @property
    def pattern(self):
        return self._pattern

    @pattern.setter
    def pattern(self, value):
        self._pattern =  value
        self.refresh()

    #### DESCRIPTION ####
    @property
    def description(self):
        values = self._lbcall('get_description', self.names)
        self._setattr('_description', values)
        return values

    @description.setter
    @f5.util.multisetter
    def description(self, values):
        self._lbcall('set_description', self.names,  values)
        self._setattr('_description',  values)

    @property
    def _description(self):
        return self._getattr('_description')

    @_description.setter
    @f5.util.multisetter
    def _lbmethod(self, values):
        self._setattr('_description',  values)

    #### LBMETHOD ####
    @property
    def lbmethod(self):
        values = self._lbcall('get_lbmethod', self.names)
        self._setattr('_lbmethod', values)
        return values

    @property
    def _lbmethod(self):
        return self._getattr('_lbmethod')

    @_lbmethod.setter
    @f5.util.multisetter
    def _lbmethod(self, values):
        self._setattr('_lbmethod',  values)

    #### LB ####
    @property
    def lb(self):
        return self._lb

    @lb.setter
    @f5.util.multisetter
    def lb(self, value):
        self._setattr('_lb', value)
        self._lb = value

    #### NAME ####
    @property
    def names(self):
        return self._getattr('name')

    @property
    def _names(self):
        return self._names

    @_names.setter
    def _names(self, values):
        self._setattr('_name', values)

    ### RATE_LIMIT ###
    @property
    def rate_limit(self):
        values = self._lbcall('get_rate_limit', self.names)
        self._setattr('_rate_limit', values)
        return values

    @rate_limit.setter
    @f5.util.multisetter
    def rate_limit(self, values):
        self._lbcall('set_rate_limit', self.names, values)
        self._setattr('_rate_limit', values)

    @property
    def _rate_limit(self):
        return self._getattr('_rate_limit')

    @_rate_limit.setter
    @f5.util.multisetter
    def _rate_limit(self, values):
        return self._setattr('_rate_limit', values)

    ### RATIO ###
    @property
    def ratio(self):
        values = self._lbcall('get_ratio', self.names)
        self._setattr('_ratio', values)
        return values

    @ratio.setter
    @f5.util.multisetter
    def ratio(self, values):
        self._lbcall('set_ratio', self.names, values)
        self._setattr('_ratio', values)

    @property
    def _ratio(self):
        return self._getattr('_ratio')

    @_ratio.setter
    @f5.util.multisetter
    def _ratio(self, values):
        return self._setattr('_ratio', values)

    #### STATUS_DESCR ####
    @property
    def status_descr(self):
        values = [s['status_description'] for s in self._lbcall('get_object_status', self.names)]
        self._setattr('_status_descr', values)
        return values

    @property
    def _status_descr(self):
        return self._getattr('_status_descr')

    #### DICTIONARY ####
    @property
    def dictionary(self):
        d = {}
        d['lb']        = self.lb
        d['partition'] = self.partition
        d['pattern']   = self.pattern

        self.address
        self.av_status
        self.connection_limit
        self.description
        self.dynamic_ratio
        self.enabled
        self.rate_limit
        self.ratio
        self.status_descr

        d['nodes'] =  [node._dictionary for node in self]
        
        return d

    @property
    def _dictionary(self):
        d = {}

        d['lb']        = self.lb
        d['partition'] = self.partition
        d['pattern']   = self.pattern
        # We're in asynchronous mode so we can simply use Node's builtin ._dictionary
        d['nodes']     = [node._dictionary for node in self]

        return d

    @dictionary.setter
    @f5.util.lbtransaction
    def dictionary(self, _dict):
        # Set asynchronous attributes so we don't refresh from lb
        self._lb        = _dict['lb']
        self._partition = _dict['partition']
        self._pattern   = _dict['pattern']

        del self[:]
        self.extend(Node.factory.create([d['name'] for d in _dict['nodes']], self._lb))

        self.connection_limit = [node['connection_limit'] for node in _dict['nodes']]
        self.description      = [node['description'] for node in _dict['nodes']]
        self.dynamic_ratio    = [node['dynamic_ratio'] for node in _dict['nodes']]
        self.enabled          = [node['enabled'] for node in _dict['nodes']]
        self.rate_limit       = [node['rate_limit'] for node in _dict['nodes']]
        self.ratio            = [node['ratio'] for node in _dict['nodes']]

    @_dictionary.setter
    def _dictionary(self, _dict):
        self._lb        = _dict['lb']
        self._partition = _dict['partition']
        self._pattern   = _dict['pattern']

        nodes = Node.factory.create([d['name'] for d in _dict['nodes']], self._lb)
        # We're in asynchronous mode so we can simply use Node's builtin ._dictionary
        for idx, node in enumerate(nodes):
            node._dictionary = _dict['nodes'][idx]

        del self[:]
        self.extend(nodes)