"""
$URL: svn+ssh://svn.mems-exchange.org/repos/trunk/durus/history.py $
$Id: history.py 29189 2007-01-12 15:42:02Z dbinger $
"""
from durus.connection import Connection
from durus.file_storage import FileStorage2
from durus.persistent import PersistentObject


class _HistoryIndex (dict):
    """
    A substitute offset index used in HistoryFileStorage.
    This keeps a history of updates and provides methods for
    moving backward and forward in that history.

    """
    def __init__(self, other_dict):
        dict.__init__(self)
        self.history = []
        self.future = []
        self.update(other_dict)

    def update(self, arg):
        self.history.append(arg.copy())
        dict.update(self, arg)

    def previous_transaction(self):
        if not self.history:
            return None
        transaction_offsets = self.history.pop()
        self.future.append(transaction_offsets)
        for oid in transaction_offsets:
            del self[oid]
            for offsets in reversed(self.history):
                if oid in offsets:
                    self[oid] = offsets[oid]
                    break
        return transaction_offsets.copy()

    def next_transaction(self):
        if not self.future:
            return None
        transaction_offsets = self.future.pop()
        self.history.append(transaction_offsets)
        dict.update(self, transaction_offsets)
        return transaction_offsets.copy()


class HistoryFileStorage (FileStorage2):
    """
    This variant of storage allows stepping forward and backward
    among the transaction records.
    """
    def __init__(self, filename=None, repair=False):
        assert not repair
        FileStorage2.__init__(self,
            filename=filename, repair=False)
        self.invalid = {}

    def end(self):
        raise RuntimeError("No commits allowed for this storage.")

    def _set_concrete_class_for_magic(self):
        pass

    def get_history_index(self):
        return self.history_index

    def set_history_index(self, value):
        self.history_index = _HistoryIndex(value)

    index = property(get_history_index, set_history_index)

    def previous(self):
        invalidations = self.get_history_index().previous_transaction()
        if invalidations is None:
            return False
        else:
            self.invalid.update(invalidations)
            return True

    def next(self):
        invalidations = self.get_history_index().next_transaction()
        if invalidations is None:
            return False
        else:
            self.invalid.update(invalidations)
            return True

    def sync(self):
        result = self.invalid.keys()
        self.invalid.clear()
        return result


class HistoryConnection (Connection):
    """
    A Connection that provides access to a FileStorage with
    the ability reverse and advance transactions.
    """
    def __init__(self, filename):
        Connection.__init__(self, HistoryFileStorage(filename))

    def previous(self):
        """() -> bool
        Move to the previous transaction.
        Returns False when there is no previous transaction.
        """
        result = self.get_storage().previous()
        self.abort()
        return result

    def next(self):
        """() -> bool
        Move to the next transaction.
        Returns False when there is no next transaction.
        """
        result = self.get_storage().next()
        self.abort()
        return result

    def previous_instance(self, obj):
        """
        Reverse transactions until there is some change in obj.
        When you reach a transaction in which obj no longer exists,
        the obj will be a ghost, but attempts to load the state will
        fail.
        """
        assert isinstance(obj, PersistentObject)
        if obj._p_is_ghost():
            obj._p_load_state()
        while True:
            self.previous()
            if obj._p_is_ghost():
                try:
                    obj._p_load_state()
                except KeyError:
                    return None
                else:
                    return obj

    def next_instance(self, obj):
        """
        Advance transactions until there is some change in obj.
        If obj is not a ghost after calling this, it means we are
        at the current version.
        """
        assert isinstance(obj, PersistentObject)
        if obj._p_is_ghost():
            obj._p_load_state()
        while self.next():
            if obj._p_is_ghost():
                break
        return obj
