# -*- test-case-name: twisted.test.test_popsicle -*-
# Twisted, the Framework of Your Internet
# Copyright (C) 2001-2002 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

from twisted.internet import defer, error

import os
import weakref

from twisted.python import log

from twisted.persisted.styles import instance, instancemethod

try:
    True, False
except NameError:
    True, False = 1, 0

class Freezer:
    DELETE = 0
    SAVE = 1
    def __init__(self):
        self.persistentObjects = weakref.WeakKeyDictionary()
        self.dirtySet = {}
        self.cleaning = False
        self.pendingCall = None

    def addSaver(self, obj, saver):
        """Add an interested saver to a particular object.
        """
        ent = self.persistentObjects.get(obj)
        if not ent:
            ent = [None, []]
            self.persistentObjects[obj] = ent
        ent[1].append(saver)

    def removeSaver(self, obj, saver):
        ent = self.persistentObjects.get(obj)
        if not ent:
            return
        ent[1].remove(saver)

    def setPersistentReference(self, obj, pref):
        ent = self.persistentObjects.get(obj)
        if not ent:
            ent = [pref, []]
            self.persistentObjects[obj] = ent
        else:
            ent[0] = pref

    def getPersistentReference(self, obj, repo=None):
        ent = self.persistentObjects.get(obj)
        if not ent:
            pr = PersistentReference(None, None, obj)
            self.setPersistentReference(obj, pr)
            ent = self.persistentObjects[obj]
            if repo is not None:
                pr.acquireOID(repo)
        return ent[0]

    def dirty(self, obj, dirt=SAVE):
        if self.cleaning:
            raise RuntimeError("Can't flag an object as dirty while cleaning.")
        self._dirty(obj)
        from twisted.internet import reactor
        if not self.pendingCall:
            self.pendingCall = reactor.callLater(1.0, self._cleanTime)

    def _dirty(self, obj, dirt=SAVE):
        self.dirtySet[obj] = dirt

    def register(self, obj, repo):
        pr = self.getPersistentReference(obj, repo)
        self.dirty(obj)
        return pr

    def delete(self, obj):
        return self.dirty(obj, Freezer.DELETE)

    def _cleanTime(self):
        self.pendingCall = None
        self.clean()

    def clean(self):
        """Persistence tick.  Clean out the fridge, persist everything that
        wants to be saved.
        """
        #log.msg("cleaning popsicle")
        if self.pendingCall:
            try:
                self.pendingCall.cancel()
            except error.AlreadyCancelled:
                # If it's already cancelled, we don't have to do anything.
                pass 
            self.pendingCall = None
        self.cleaning = True
        savers = {}
        try:
            while self.dirtySet:
                l = self.dirtySet.items()
                self.dirtySet.clear()
                for obj, doSave in l:
                    ent = self.persistentObjects.get(obj)
                    if not ent:
                        continue
                    for saver in ent[1]:
                        savers[saver] = 1
                        try:
                            if doSave:
                                # print 'saving',obj
                                saver.save(obj)
                            else:
                                # print 'deleting',obj
                                saver.delete(obj)
                        except:
                            # TODO: more error handling: backout transactions?
                            # restore state of objects?  what else?
                            log.deferr()
        finally:
            self.cleaning = False
            for saver in savers.keys():
                try:
                    saver.cleaned()
                except:
                    log.deferr()
            

try:
    theFreezer
except NameError:
    # Create the public interface singleton.
    theFreezer = Freezer()
    ref = theFreezer.getPersistentReference
    clean = theFreezer.clean
    dirty = theFreezer.dirty
    register = theFreezer.register

class ISaver:
    def save(self, object):
        """Save (and optionally index) the given object.
        """

    def delete(self, object):
        """Remove the given object.
        """


class PersistentReference:
    """I am a reference to a persistent object.

    The most interesting external interface is __call__: instances of this
    class should generally be treated as zero-argument functions which return
    Deferreds.
    """

    # Maintenance notes: there are 2 states for instances of this class.
    # either self.oid and self.repo are set to non-None values, in which case
    # this represents a stored object, or self.obj is set, in which case this
    # represents a value that *wants* to be stored.  In state 2, the object is
    # mutable, so care is taken to cache such objects (see
    # Freezer.getPersistentReference).  However, in state 1, prefs are
    # effectively immutable, so multiple may be instantiated with the same
    # OID/repo pair.
    
    def __init__(self, oid, repo, obj):
        self.oid = oid
        self.repo = repo
        self.obj = obj

    def __call__(self):
        """Load my object into memory, and return a Deferred which will fire
        it."""
        if self.obj is not None:
            return defer.succeed(self.obj)
        return self.repo.loadRef(self)

    def acquireOID(self, repo=None):
        """Take a PersistentReference that isn't really persistent yet, and
        associate it with a repository, give it an OID, and set it dirty.
        This should happen *synchronously during persistence*.
        """
        # TODO: better name for this method!!!
        if self.obj is not None:
            assert self.oid is None, "inconsistent state of p-ref: %s" % self.oid
            if repo is None:
                assert theFreezer._savingRepo is not None, "Calling acquireOID without repo outside of save!"
                repo = theFreezer._savingRepo
            o = self.obj
            self.repo = repo
            self.oid = repo.generateOID(o)
            self.obj = None
            theFreezer.addSaver(o, repo)
            # use internal API to avoid warning: we *really really* want to dirty
            theFreezer._dirty(o)
            repo.cache(self.oid, o, finished=0)
            return self.oid
        elif ((self.repo is repo) or
              repo is None and self.repo is theFreezer._savingRepo):
            assert self.oid is not None, "inconsistent state of p-ref"
            return self.oid
        else:
            # I don't belong to this repo, but I _do_ belong to someone else.
            return None
        ### assert False, "wrong repository!"
