"""
$URL: svn+ssh://sting.mems-exchange.org/repos/trunk/DurusWorks/qp/lib/keep.py $
$Id: keep.py 32474 2010-06-09 15:09:26Z dbinger $
"""
from durus.btree import BTree
from durus.persistent import PersistentObject
from durus.persistent_dict import PersistentDict
from qp.lib.spec import either, datetime_with_tz, mapping, spec, integer
from qp.lib.spec import require, get_spec_problems, anything, specify
from qp.lib.util import rand_int
from qp.lib.tz import UTC

class Keyed (object):
    """
    An item with an int key used as a key in an Keep.
    Note that the key attribute is set when the item is put in the Keep,
    so there is no set_key() method.
    This is a mixin for PersistentObject classes.
    """
    key_is = integer

    __slots__ = []

    def __init__(self):
        assert isinstance(self, PersistentObject)
        self.key = None

    def get_key(self):
        return self.key


class KeyCounter (PersistentObject):
    """
    The base class.  Generates keys using random 64 bit ints.
    """
    def next(self):
        """() -> integer
        This base implementation just uses a big random number.
        """
        return rand_int(64)


class Counter (KeyCounter):
    """
    Generates keys by incrementing them.
    """
    next_available_is = integer

    __slots__ = ['next_available']

    def __init__(self):
        self.next_available = 1

    def next(self):
        result = self.next_available
        self.next_available = self.next_available + 1
        return result


class GrowingRandomCounter (KeyCounter):
    """
    Keep a key counter that generates keys randomly, and gradually
    expands the numbers of bits in the random numbers.
    The idea is to take advantage of randomly distributed keys, but
    to try to keep the numbers relatively small.
    """
    bits_is = integer
    used_is = integer

    __slots__ = ['bits', 'used']

    def __init__(self):
        specify(self, bits=10, used=0)

    def next(self):
        self.used = self.used + 1
        # If more than 1% of the possible values have been used,
        # double the space of possible values.
        if (self.used / (2 ** self.bits)) > 0.01:
            self.bits += 1
        return rand_int(self.bits)


class Keep (object):
    """
    A simple database that stores a mapping of objects by key.
    This is a mixin for PersistentObject classes.
    """
    value_spec_is = spec(
        anything,
        "Specifies the type of values allowed in mapping")
    mapping_is = mapping({integer:Keyed}, either(PersistentDict, BTree))
    key_counter_is = KeyCounter
    __slots__ = []

    def __init__(self, value_spec=Keyed, mapping_class=BTree, counter_class=Counter):
        assert isinstance(self, PersistentObject)
        self.mapping = mapping_class()
        self.value_spec = value_spec
        self.set_counter(counter_class())

    def set_counter(self, counter):
        specify(self, key_counter=counter)

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

    def get_mapping(self):
        return self.mapping

    def get(self, key, default=None):
        return self.mapping.get(key, default)

    def itervalues(self):
        for value in self.mapping.itervalues():
            yield value

    def iterkeys(self):
        for key in self.mapping.iterkeys():
            yield key

    def iteritems(self):
        for item in self.mapping.iteritems():
            yield item

    def add(self, value):
        require(value, self.value_spec)
        assert value.key is None
        for attempt in range(1000):
            value.key = self.key_counter.next()
            if value.key not in self.mapping:
                break
        else:
            raise KeyError('Unable to find unused key for %r.' % value)
        if get_spec_problems(value):
            raise TypeError(''.join(get_spec_problems(value)))
        self.mapping[value.key] = value

    def remove(self, value):
        require(value, self.value_spec)
        assert value.key is self.mapping[value.key].key
        del self.mapping[value.key]


class Stamped (object):
    """
    Provides a timestamp.
    This is a mixin for PersistentObject classes.  
    """
    stamp_is = datetime_with_tz

    __slots__ = []

    def __init__(self):
        assert isinstance(self, PersistentObject)
        self.set_stamp()

    def get_stamp(self):
        return self.stamp

    def set_stamp(self):
        self.stamp = UTC.now()


def stamp_sorted(stamped_sequence):
    items = [(x.stamp, x) for x in stamped_sequence]
    return [x for (stamp, x) in sorted(items)]

def reverse_stamp_sorted(stamped_sequence):
    result = stamp_sorted(stamped_sequence)
    result.reverse()
    return result

