from Acquisition import aq_inner
from Acquisition import aq_parent
from opengever.base.adapters import CHILD_REF_KEY
from opengever.base.adapters import DOSSIER_KEY
from opengever.base.adapters import PREFIX_REF_KEY
from opengever.base.adapters import REPOSITORY_FOLDER_KEY
from opengever.base.interfaces import IReferenceNumberFormatter
from opengever.base.interfaces import IReferenceNumberPrefix
from opengever.base.interfaces import IReferenceNumberSettings
from opengever.dossier.behaviors.dossier import IDossierMarker
from opengever.dossier.templatedossier import ITemplateDossier
from opengever.maintenance.debughelpers import setup_app
from opengever.maintenance.debughelpers import setup_plone
from opengever.repository.interfaces import IRepositoryFolder
from opengever.repository.repositoryroot import IRepositoryRoot
from opengever.task.task import ITask
from plone import api
from plone.registry.interfaces import IRegistry
from zope.annotation.interfaces import IAnnotations
from zope.app.intid.interfaces import IIntIds
from zope.component import getUtility
from zope.component import queryAdapter
import transaction


SEPARATOR = '-' * 78


class ReferenceNumberHelper(object):
    """Helper class for dealing with reference numbers.
    """

    def __init__(self, log_func, site):
        self.log = log_func
        self.site = site

    def get_repo_dossier_separator(self, obj=None):
        registry = getUtility(IRegistry)
        proxy = registry.forInterface(IReferenceNumberSettings)

        formatter = queryAdapter(obj,
                                 IReferenceNumberFormatter,
                                 name=proxy.formatter)
        return formatter.repository_dossier_seperator

    def get_new_mapping(self, key, obj):
        parent = aq_parent(aq_inner(obj))
        ann = IAnnotations(parent)

        if IDossierMarker.providedBy(obj):
            mapping_base = ann.get(DOSSIER_KEY)
        elif IRepositoryFolder.providedBy(obj) or IRepositoryRoot.providedBy(obj):
            mapping_base = ann.get(REPOSITORY_FOLDER_KEY)
        else:
            raise Exception("Unknown object type!")

        if not mapping_base:
            return None

        mapping = mapping_base.get(key)
        return mapping


class ReferenceNumberFixer(object):
    """This is the fix for some previously run fixscripts.

    It attempts to fix broken reference numbers. A new reference number has
    been generated by mistake while moving content. Some fix-scrips have then
    attempted to revert these reference numbers to their previous state.  This
    seems to have failed in come cases:

    The reference numbers are now in an inconsistent state and have different
    values in child_mapping and prefix_mapping. This script reverts the
    reference numbers to the state as defined in child_mapping. If multiple
    values are defined in child_mapping it takes the higher (later) one.

    """
    def __init__(self, log_func, site):
        self.catalog = api.portal.get_tool('portal_catalog')
        self.parent_logger = log_func
        self.site = site
        self.helper = ReferenceNumberHelper(log_func, site)
        self.intids = getUtility(IIntIds)
        self.ignored_ids = ['vorlagen']
        self.objs_to_reindex = set()

    def log(self, msg):
        msg = "    " + msg
        return self.parent_logger(msg)

    def _fix_wrong_mappings(self, obj):
        """Detect the following errors:

        - entry of reference number in prefix_mapping available
        - no entry in child_mapping for that refernece numbers, but for
          other (previous) reference numbers for that content object

        """
        parent = aq_parent(aq_inner(obj))
        local_number = IReferenceNumberPrefix(parent).get_number(obj)
        intid = self.intids.getId(obj)

        try:
            child_mapping = self.helper.get_new_mapping(CHILD_REF_KEY, obj)
            prefix_mapping = self.helper.get_new_mapping(PREFIX_REF_KEY, obj)

            has_child_mapping = child_mapping.get(local_number) == intid
            has_prefix_mapping = prefix_mapping.get(intid) == local_number
            is_assigned_a_refnum = intid in set(child_mapping.values())

            if not has_child_mapping:
                if is_assigned_a_refnum:

                    self._revert_to_refnum_in_child_mapping(
                        obj, parent, local_number, intid, child_mapping, prefix_mapping)
                else:
                    self.log("WARNING: obj %s not in child mapping of parent!" % obj)

            if not has_prefix_mapping:
                self.log("WARNING: obj %s not in prefix mapping of parent!" % obj)
        except Exception, e:
            self.log("WARNING: '%s' for %s" % (e, obj))

    def _revert_to_refnum_in_child_mapping(self, obj, parent, local_number, intid, child_mapping, prefix_mapping):
        previous_refnums = []
        for key, value in child_mapping.iteritems():
            if value == intid:
                previous_refnums.append(key)
        max_previous_refnum = unicode(max(map(int, previous_refnums)))
        assert int(local_number) > int(max_previous_refnum)

        # revert refnum to previous entry
        prefix_mapping[intid] = max_previous_refnum

        self.log("INFO: reverted %s (%s) from %s to %s" % (obj, intid, local_number, max_previous_refnum))

        assert IReferenceNumberPrefix(parent).get_number(obj) == max_previous_refnum

        for brain in self.catalog(path='/'.join(obj.getPhysicalPath())):
            self.objs_to_reindex.add(brain.getObject())

    def fix_child_mappings(self):
        dossier_brains = self.catalog(object_provides=IDossierMarker.__identifier__)
        for brain in dossier_brains:
            obj = brain.getObject()

            if ITemplateDossier.providedBy(obj):
                continue
            if obj.id in self.ignored_ids:
                continue

            self._fix_wrong_mappings(obj)

        for obj in self.objs_to_reindex:
            obj.reindexObject(idxs=['reference'])
            if ITask.providedBy(obj):
                obj.get_sql_object().sync_with(obj)


def main():
    app = setup_app()

    print SEPARATOR
    plone = setup_plone(app, [])

    # prevents erroneous execution
    transaction.doom()

    def log(msg):
        print msg

    fixer = ReferenceNumberFixer(log, plone)

    print "Running 'fixing broken mappings'..."
    fixer.fix_child_mappings()
    print "Done"


if __name__ == '__main__':
    main()
