from zope.interface import implements
from zope.app.annotation.interfaces import IAnnotations
from zope.component.exceptions import ComponentLookupError
from Acquisition import aq_base
from BTrees.OOBTree import OOBTree
from DateTime import DateTime
from AccessControl import getSecurityManager

from OFS.interfaces import IObjectWillBeRemovedEvent
from Products.ploneundelete.interfaces import IUndeleteSupport
from Products.CMFCore.utils import getToolByName
from Products.CMFEditions.interfaces.IArchivist import ArchivistUnregisteredError

ANNOTATION_KEY = "ploneundelete.objectlist"

def prepareForUndelete(obj, event):
    """An event subscriber which makes sure that an object can be undeleted
       by saving a version and adding an indicator to the parent indicating
       how to retrieve the object.
    """

    parent = event.oldParent
    try:
        undelete_container = IUndeleteSupport(parent)
    except (ComponentLookupError, TypeError):
        return

    undelete_container.saveObjectInfo(obj)


class UndeleteSupport(object):
    implements(IUndeleteSupport)

    def __init__(self, context):
        self.context = context
        annotations = IAnnotations(context)
        self.deleted_objs = annotations.get(ANNOTATION_KEY, None)
        if self.deleted_objs is None:
            self.deleted_objs = annotations[ANNOTATION_KEY] = OOBTree()

    def saveObjectInfo(self, obj):
        # We need to make sure the object is in this folder
        assert(aq_base(self.context[obj.getId()]) is aq_base(obj))
#        # If so we use the object from the folder, in case some unexpected
#        # wrapping has happened
        obj = self.context[obj.getId()]
        repository = getToolByName(self.context, 'portal_repository', None)
        if repository is None:
            raise AttributeError, "CMFEditions must be installed for "\
                                  "undelete support."
        if obj.portal_type not in repository.getVersionableContentTypes():
            return
        # Only save a new version if the object isn't up to date, CMFEditions
        # should not rely on the unrelibale CMF modification date for this.
        needs_save = True
        undo_autoapply = False

        try:
            needs_save = not repository.isUpToDate(obj)
        except ArchivistUnregisteredError:
            needs_save = True
            if not repository.autoapply:
                repository.setAutoApplyMode(True)
                undo_autoapply = True

        if needs_save:
            repository.save(obj, comment="Immediately before deletion")
        if undo_autoapply:
            repository.setAutoApplyMode(False)

        uidtool = getToolByName(self.context, 'portal_uidhandler')
        history = repository.getHistory(obj)
        uid = uidtool.queryUid(obj, None)
        revision = len(history)-1
        # get the user explicitly
        user_id = getSecurityManager().getUser().getUserName()

        obj_metadata = {'date': DateTime().ISO(),
                        'id': obj.getId(),
                        'title': obj.Title(),
                        'revision': revision,
                        'user': user_id,
                        # redundantly include the uid to make like easier
                        'uid': uid}

        self.deleted_objs[uid] = obj_metadata
        pass

    def listDeleted(self):
        deleted = self.deleted_objs.values()
        # sort on date deleted, most recent first, we make a copy so we can
        decorated = [(d['date'], d) for d in deleted]
        decorated.sort()
        decorated.reverse()
        return [d[1] for d in decorated]

    def getMetadataFor(self, uid):
        return self.deleted_objs[uid]

    def undelete(self, uid):
        repository = getToolByName(self.context, 'portal_repository', None)
        if repository is None:
            raise AttributeError, "CMFEditions must be installed for "\
                                  "undelete support."
        info = self.deleted_objs[uid]
        version_id = info['revision']
        new_id = info['id']

        repository.restore(uid, selector=version_id, container=self.context,
                           new_id=new_id)
        # remove the history entry
        del self.deleted_objs[uid]