import os

from five.localsitemanager import find_next_sitemanager
from five.localsitemanager import make_objectmanager_site
from five.localsitemanager.registry import FiveVerifyingAdapterLookup
from five.localsitemanager.registry import PersistentComponents
from plone.app.portlets.utils import convert_legacy_portlets
from plone.portlets.interfaces import IPortletManager
from plone.portlets.interfaces import ILocalPortletAssignmentManager
from plone.portlets.constants import CONTEXT_CATEGORY as CONTEXT_PORTLETS
from zope.location.interfaces import ISite
from zope.component import getMultiAdapter
from zope.component import getSiteManager
from zope.component import getUtility
from zope.component.globalregistry import base
from zope.component.interfaces import ComponentLookupError
from zope.site.hooks import setSite

from Acquisition import aq_base
from App.Common import package_home
from Products.Archetypes.interfaces import IArchetypeTool
from Products.Archetypes.interfaces import IReferenceCatalog
from Products.Archetypes.interfaces import IUIDCatalog
from Products.CMFActionIcons.interfaces import IActionIconsTool
from Products.CMFCalendar.interfaces import ICalendarTool
from Products.CMFCore.ActionInformation import Action
from Products.CMFCore.ActionInformation import ActionCategory
from Products.CMFCore.interfaces import IActionsTool
from Products.CMFCore.interfaces import ICachingPolicyManager
from Products.CMFCore.interfaces import ICatalogTool
from Products.CMFCore.interfaces import IContentTypeRegistry
from Products.CMFCore.interfaces import IDiscussionTool
from Products.CMFCore.interfaces import IMemberDataTool
from Products.CMFCore.interfaces import IMembershipTool
from Products.CMFCore.interfaces import IMetadataTool
from Products.CMFCore.interfaces import IPropertiesTool
from Products.CMFCore.interfaces import IRegistrationTool
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.interfaces import ISkinsTool
from Products.CMFCore.interfaces import ISyndicationTool
from Products.CMFCore.interfaces import ITypesTool
from Products.CMFCore.interfaces import IUndoTool
from Products.CMFCore.interfaces import IURLTool
from Products.CMFCore.interfaces import IConfigurableWorkflowTool
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.DirectoryView import createDirectoryView
from Products.CMFDiffTool.interfaces import IDiffTool
from Products.CMFEditions.interfaces import IArchivistTool
from Products.CMFEditions.interfaces import IPortalModifierTool
from Products.CMFEditions.interfaces import IPurgePolicyTool
from Products.CMFEditions.interfaces.IRepository import IRepositoryTool
from Products.CMFEditions.interfaces import IStorageTool
from Products.CMFFormController.interfaces import IFormControllerTool
from Products.CMFQuickInstallerTool.interfaces import IQuickInstallerTool
from Products.CMFUid.interfaces import IUniqueIdAnnotationManagement
from Products.CMFUid.interfaces import IUniqueIdGenerator
from Products.CMFUid.interfaces import IUniqueIdHandler
from Products.DCWorkflow.DCWorkflow import DCWorkflowDefinition
from Products.DCWorkflow.exportimport import WorkflowDefinitionConfigurator, _initDCWorkflow
from Products.GenericSetup.interfaces import ISetupTool
from Products.MailHost.interfaces import IMailHost
from Products.MimetypesRegistry.interfaces import IMimetypesRegistryTool
from Products.PloneLanguageTool.interfaces import ILanguageTool
from Products.PlonePAS.interfaces.group import IGroupTool
from Products.PlonePAS.interfaces.group import IGroupDataTool
from Products.PortalTransforms.interfaces import IPortalTransformsTool
from Products.ResourceRegistries.interfaces import ICSSRegistry
from Products.ResourceRegistries.interfaces import IJSRegistry
from Products.StandardCacheManagers import RAMCacheManager

from Products.CMFPlone import cmfplone_globals
from Products.CMFPlone.factory import _DEFAULT_PROFILE
from Products.CMFPlone.interfaces import IFactoryTool
from Products.CMFPlone.interfaces import IPloneSiteRoot
from Products.CMFPlone.interfaces import IPloneTool
from Products.CMFPlone.interfaces import ITranslationServiceTool

from plone.app.upgrade.utils import installOrReinstallProduct
from plone.app.upgrade.utils import loadMigrationProfile
from plone.app.upgrade.utils import logger

try:
    from Products.ATContentTypes.interface import IATCTTool
    from Products.ATContentTypes.migration.v1_2 import upgradeATCTTool
    HAS_ATCT = True
except ImportError:
    HAS_ATCT = False


def three0_alpha1(context):
    """2.5.x -> 3.0-alpha1
    """
    loadMigrationProfile(context, 'profile-plone.app.upgrade.v30:2.5.x-3.0a1')

    portal = getToolByName(context, 'portal_url').getPortalObject()

    # The ATCT tool has lost all type migration functionality and quite some
    # metadata and index information stored on it needs to be updated.
    if HAS_ATCT:
        upgradeATCTTool(portal)

    # Install CMFEditions and CMFDiffTool
    installProduct('CMFEditions', portal, hidden=True)


def alpha1_alpha2(context):
    """ 3.0-alpha1 -> 3.0-alpha2
    """
    loadMigrationProfile(context, 'profile-plone.app.upgrade.v30:3.0a1-3.0a2')


def alpha2_beta1(context):
    """ 3.0-alpha2 -> 3.0-beta1
    """
    loadMigrationProfile(context, 'profile-plone.app.upgrade.v30:3.0a2-3.0b1')

    portal = getToolByName(context, 'portal_url').getPortalObject()

    # Install PloneLanguageTool
    installProduct('PloneLanguageTool', portal, hidden=True)


def enableZope3Site(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    if not ISite.providedBy(portal):
        make_objectmanager_site(portal)
        logger.info('Made the portal a Zope3 site.')
    try:
        components = portal.getSiteManager()
    except ComponentLookupError:
        next = find_next_sitemanager(portal)
        if next is None:
            next = base
        name = '/'.join(portal.getPhysicalPath())
        components = PersistentComponents(name, (next,))
        components.__parent__ = portal
        portal.setSiteManager(components)
        logger.info("Site manager '%s' added." % name)
    else:
        if components.utilities.LookupClass != FiveVerifyingAdapterLookup:
            # for CMF 2.1 beta instances
            components.__parent__ = portal
            components.utilities.LookupClass = FiveVerifyingAdapterLookup
            components.utilities._createLookup()
            components.utilities.__parent__ = components
            logger.info('LookupClass replaced.')
    # Make sure to set the new site as the new active one
    setSite(portal)


def migrateOldActions(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    special_providers = ['portal_controlpanel',
                         'portal_types',
                         'portal_workflow']
    # We don't need to operate on the providers that are still valid and
    # should ignore the control panel as well
    providers = [obj for obj in portal.objectValues()
                     if hasattr(obj, '_actions') and
                     obj.getId() not in special_providers]
    non_empty_providers = [p for p in providers if len(p._actions) > 0]
    for provider in non_empty_providers:
        for action in provider._actions:
            category = action.category
            # check if the category already exists, otherwise create it
            new_category = getattr(aq_base(portal.portal_actions), category, None)
            if new_category is None:
                portal.portal_actions._setObject(category, ActionCategory(id=category))
                new_category = portal.portal_actions[category]

            # Special handling for Expressions
            url_expr = ''
            if action.action:
                url_expr = action.action.text
            available_expr = ''
            if action.condition:
                available_expr = action.condition.text

            new_action = Action(action.id,
                title=action.title,
                description=action.description,
                url_expr=url_expr,
                available_expr=available_expr,
                permissions=action.permissions,
                visible = action.visible)

            # Only add an action if there isn't one with that name already
            if getattr(aq_base(new_category), action.id, None) is None:
                new_category._setObject(action.id, new_action)

        # Remove old actions from upgraded providers
        provider._actions = ()
    logger.info('Upgraded old actions to new actions stored in portal_actions.')


def _check_ascii(text):
    try:
        unicode(text, 'ascii')
    except UnicodeDecodeError:
        return False
    return True


def updateActionsI18NDomain(context):
    actions = getToolByName(context, 'portal_actions')
    actions = actions.listActions()
    domainless_actions = [a for a in actions if not a.i18n_domain]
    for action in domainless_actions:
        if _check_ascii(action.title) and _check_ascii(action.description):
            action.i18n_domain = 'plone'
    if domainless_actions:
        logger.info('Updated actions i18n domain attribute.')


def updateFTII18NDomain(context):
    types = getToolByName(context, 'portal_types')
    types = types.listTypeInfo()
    domainless_types = [fti for fti in types if not fti.i18n_domain]
    for fti in domainless_types:
        if _check_ascii(fti.title) and _check_ascii(fti.description):
            fti.i18n_domain = 'plone'
    if domainless_types:
        logger.info('Updated type informations i18n domain attribute.')


def addPortletManagers(context):
    """Add new portlets managers."""
    loadMigrationProfile(context, 'profile-Products.CMFPlone:plone',
            steps=['portlets'])


def convertLegacyPortlets(context):
    """Convert portlets defined in left_slots and right_slots at the portal
    root to use plone.portlets. Also block portlets in the Members folder.

    Note - there may be other portlets defined elsewhere. These will require
    manual upgrade from the @@manage-portlets view. This is to avoid a
    full walk of the portal (i.e. waking up every single object) looking for
    potential left_slots/right_slots!
    """
    portal = getToolByName(context, 'portal_url').getPortalObject()
    convert_legacy_portlets(portal)
    logger.info('Converted legacy portlets at the portal root')
    logger.info('NOTE: You may need to convert other portlets manually.')
    logger.info(' - to do so, click "manage portlets" in the relevant folder.')

    members = getattr(portal, 'Members', None)
    if members is not None:
        membersRightSlots = getattr(aq_base(members), 'right_slots', None)
        if membersRightSlots == []:
            rightColumn = getUtility(IPortletManager, name=u'plone.rightcolumn', context=portal)
            portletAssignments = getMultiAdapter((members, rightColumn,), ILocalPortletAssignmentManager)
            portletAssignments.setBlacklistStatus(CONTEXT_PORTLETS, True)
            logger.info('Blacklisted contextual portlets in the Members folder')


def installProduct(product, portal, out=None, hidden=False):
    """Quickinstalls a product if it is not installed yet."""
    if out is None:
        out = []
    installOrReinstallProduct(portal, product, out, hidden=hidden)


registration = (('mimetypes_registry', IMimetypesRegistryTool),
                ('portal_transforms', IPortalTransformsTool),
                ('portal_actionicons', IActionIconsTool),
                ('portal_discussion', IDiscussionTool),
                ('portal_metadata', IMetadataTool),
                ('portal_properties', IPropertiesTool),
                ('portal_syndication', ISyndicationTool),
                ('portal_undo', IUndoTool),
                ('MailHost', IMailHost),
                ('portal_diff', IDiffTool),
                ('portal_uidannotation', IUniqueIdAnnotationManagement),
                ('portal_uidgenerator', IUniqueIdGenerator),
               )
if HAS_ATCT:
    registration += (('portal_atct', IATCTTool),)

invalid_regs = (ILanguageTool, IArchivistTool, IPortalModifierTool,
                IPurgePolicyTool, IRepositoryTool, IStorageTool,
                IFormControllerTool, IReferenceCatalog, IUIDCatalog,
                ICalendarTool, IActionsTool, ICatalogTool,
                IContentTypeRegistry, ISkinsTool, ITypesTool, IURLTool,
                IConfigurableWorkflowTool, IPloneTool, ICSSRegistry,
                IJSRegistry, IUniqueIdHandler, IFactoryTool, IMembershipTool,
                IGroupTool, IGroupDataTool, IMemberDataTool,
                ICachingPolicyManager, IRegistrationTool, IArchetypeTool,
                ITranslationServiceTool, IQuickInstallerTool,
                ISetupTool,
               )

def registerToolsAsUtilities(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    sm = getSiteManager(portal)

    portalregistration = ((portal, ISiteRoot),
                          (portal, IPloneSiteRoot),)

    for reg in portalregistration:
        if sm.queryUtility(reg[1]) is None:
            sm.registerUtility(aq_base(reg[0]), reg[1])

    for reg in registration:
        if sm.queryUtility(reg[1]) is None:
            if reg[0] in portal.keys():
                tool = aq_base(portal[reg[0]])
                sm.registerUtility(tool, reg[1])

    for reg in invalid_regs:
        if sm.queryUtility(reg) is not None:
            sm.unregisterUtility(provided=reg)

    logger.info("Registered tools as utilities.")


def addReaderAndEditorRoles(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    if 'Reader' not in portal.valid_roles():
        portal._addRole('Reader')
    if 'Editor' not in portal.valid_roles():
        portal._addRole('Editor')
    if 'Reader' not in portal.acl_users.portal_role_manager.listRoleIds():
        portal.acl_users.portal_role_manager.addRole('Reader')
    if 'Editor' not in portal.acl_users.portal_role_manager.listRoleIds():
        portal.acl_users.portal_role_manager.addRole('Editor')

    viewRoles = [r['name'] for r in portal.rolesOfPermission('View') if r['selected']]
    modifyRoles = [r['name'] for r in portal.rolesOfPermission('Modify portal content') if r['selected']]

    if 'Reader' not in viewRoles:
        viewRoles.append('Reader')
        portal.manage_permission('View', viewRoles, True)

    if 'Editor' not in modifyRoles:
        modifyRoles.append('Editor')
        portal.manage_permission('Modify portal content', modifyRoles, True)

    logger.info('Added reader and editor roles')


def migrateLocalroleForm(context):
    portal_types = getToolByName(context, 'portal_types', None)
    if portal_types is not None:
        for fti in portal_types.objectValues():
            if not hasattr(fti, '_aliases'):
                fti._aliases={}

            aliases = fti.getMethodAliases()
            new_aliases = aliases.copy()
            for k, v in aliases.items():
                if 'folder_localrole_form' in v:
                    new_aliases[k] = v.replace('folder_localrole_form', '@@sharing')
            fti.setMethodAliases(new_aliases)

            for a in fti.listActions():
                expr = a.getActionExpression()
                if 'folder_localrole_form' in expr:
                    a.setActionExpression(expr.replace('folder_localrole_form', '@@sharing'))
    logger.info('Ensured references to folder_localrole_form point to @@sharing now')


def reorderUserActions(context):
    portal_actions = getToolByName(context, 'portal_actions', None)
    if portal_actions is not None:
        user_category = getattr(portal_actions, 'user', None)
        if user_category is not None:
            new_actions = ['login', 'join', 'mystuff', 'preferences', 'undo', 'logout']
            new_actions.reverse()
            for action in new_actions:
                if action in user_category.objectIds():
                    user_category.moveObjectsToTop([action])


def updateMemberSecurity(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    pprop = getToolByName(portal, 'portal_properties')
    portal.manage_permission('Add portal member', roles=['Manager','Owner'], acquire=0)
    pprop.site_properties.manage_changeProperties(allowAnonymousViewAbout=False)

    portal.manage_changeProperties(validate_email=True)

    pmembership = getToolByName(portal, 'portal_membership')
    pmembership.memberareaCreationFlag = 0
    logger.info("Updated member management security")


def updatePASPlugins(context):
    from Products.PlonePAS.Extensions.Install import activatePluginInterfaces

    portal = getToolByName(context, 'portal_url').getPortalObject()

    activatePluginInterfaces(portal, 'mutable_properties')
    activatePluginInterfaces(portal, 'source_users')
    activatePluginInterfaces(portal, 'credentials_cookie_auth',
            disable=['ICredentialsResetPlugin', 'ICredentialsUpdatePlugin'])
    if not portal.acl_users.objectIds(['Plone Session Plugin']):
        from plone.session.plugins.session import manage_addSessionPlugin
        manage_addSessionPlugin(portal.acl_users, 'session')
        activatePluginInterfaces(portal, "session")
        logger.info("Added Plone Session Plugin.")


def updateConfigletTitles(portal):
    """Update titles of some configlets"""
    controlPanel = getToolByName(portal, 'portal_controlpanel', None)
    if controlPanel is not None:
        collection = controlPanel.getActionObject('Plone/portal_atct')
        language = controlPanel.getActionObject('Plone/PloneLanguageTool')
        navigation = controlPanel.getActionObject('Plone/NavigationSettings')
        types = controlPanel.getActionObject('Plone/TypesSettings')
        users = controlPanel.getActionObject('Plone/UsersGroups')
        users2 = controlPanel.getActionObject('Plone/UsersGroups2')

        if collection is not None:
            collection.title = "Collection"
        if language is not None:
            language.title = "Language"
        if navigation is not None:
            navigation.title = "Navigation"
        if types is not None:
            types.title = "Types"
        if users is not None:
            users.title = "Users and Groups"
        if users2 is not None:
            users2.title = "Users and Groups"


def updateKukitJS(context):
    """Use the unpacked kukit-src.js and pack it ourself.
    """
    jsreg = getToolByName(context, 'portal_javascripts', None)
    old_id = '++resource++kukit.js'
    new_id = '++resource++kukit-src.js'
    if jsreg is not None:
        script_ids = jsreg.getResourceIds()
        if old_id in script_ids and new_id in script_ids:
            jsreg.unregisterResource(old_id)
        elif old_id in script_ids:
            jsreg.renameResource(old_id, new_id)
            logger.info("Use %s instead of %s" % (new_id, old_id))
        resource = jsreg.getResource(new_id)
        if resource is not None:
            resource.setCompression('full')
            logger.info("Set 'full' compression on %s" % new_id)


def addCacheForResourceRegistry(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    ram_cache_id = 'ResourceRegistryCache'
    if not ram_cache_id in portal.objectIds():
        RAMCacheManager.manage_addRAMCacheManager(portal, ram_cache_id)
        cache = getattr(portal, ram_cache_id)
        settings = cache.getSettings()
        settings['max_age'] = 24*3600 # keep for up to 24 hours
        settings['request_vars'] = ('URL',)
        cache.manage_editProps('Cache for saved ResourceRegistry files', settings)
        logger.info('Created RAMCache %s for ResourceRegistry output' % ram_cache_id)
    reg = getToolByName(portal, 'portal_css', None)
    if reg is not None and getattr(aq_base(reg), 'ZCacheable_setManagerId', None) is not None:
        reg.ZCacheable_setManagerId(ram_cache_id)
        reg.ZCacheable_setEnabled(1)
        logger.info('Associated portal_css with %s' % ram_cache_id)
    reg = getToolByName(portal, 'portal_javascripts', None)
    if reg is not None and getattr(aq_base(reg), 'ZCacheable_setManagerId', None) is not None:
        reg.ZCacheable_setManagerId(ram_cache_id)
        reg.ZCacheable_setEnabled(1)
        logger.info('Associated portal_javascripts with %s' % ram_cache_id)


def removeTablelessSkin(context):
    st = getToolByName(context, 'portal_skins')
    if 'Plone Tableless' in st.getSkinSelections():
        st.manage_skinLayers(['Plone Tableless'], del_skin=True)
        logger.info("Removed the Plone Tableless skin")
    if st.default_skin=='Plone Tableless':
        st.default_skin='Plone Default'
        logger.info("Changed the default skin to 'Plone Default'")


def addObjectProvidesIndex(context):
    """Add the object_provides index to the portal_catalog.
    """
    catalog = getToolByName(context, 'portal_catalog')
    if 'object_provides' not in catalog.indexes():
        catalog.addIndex('object_provides', 'KeywordIndex')
        logger.info("Added object_provides index to portal_catalog")


def removeMyStuffAction(context):
    """The mystuff action is now covered by the dashboard"""
    actions = getToolByName(context, 'portal_actions')
    if getattr(actions, 'user', None) is None:
        return
    category=actions.user
    if 'mystuff' in category.objectIds():
        category.manage_delObjects(ids=['mystuff'])
        logger.info("Removed the mystuff user action")


def addMissingWorkflows(context):
    """Add new Plone 3.0 workflows
    """
    portal = getToolByName(context, 'portal_url').getPortalObject()
    wft = getToolByName(portal, 'portal_workflow', None)
    if wft is None:
        return

    new_workflow_ids = [ 'intranet_workflow', 'intranet_folder_workflow',
                        'one_state_workflow', 'simple_publication_workflow']
    encoding = 'utf-8'
    path_prefix = os.path.join(package_home(cmfplone_globals), 'profiles',
            'default', 'workflows')

    for wf_id in new_workflow_ids:
        if wf_id in wft.objectIds():
            logger.info("Workflow %s already installed; doing nothing" % wf_id)
            continue

        path = os.path.join(path_prefix, wf_id, 'definition.xml')
        body = open(path,'r').read()

        wft._setObject(wf_id, DCWorkflowDefinition(wf_id))
        wf = wft[wf_id]
        wfdc = WorkflowDefinitionConfigurator(wf)

        ( workflow_id
        , title
        , state_variable
        , initial_state
        , states
        , transitions
        , variables
        , worklists
        , permissions
        , scripts
        , description
        , manager_bypass
        , creation_guard
        ) = wfdc.parseWorkflowXML(body, encoding)

        _initDCWorkflow( wf
                       , title
                       , description
                       , manager_bypass
                       , creation_guard
                       , state_variable
                       , initial_state
                       , states
                       , transitions
                       , variables
                       , worklists
                       , permissions
                       , scripts
                       , portal     # not sure what to pass here
                                    # the site or the wft?
                                    # (does it matter at all?)
                      )
        logger.info("Added workflow %s" % wf_id)


def restorePloneTool(context):
    portal = getToolByName(context, 'portal_url').getPortalObject()
    tool = getToolByName(portal, "plone_utils")
    if tool.meta_type == 'PlonePAS Utilities Tool':
        from Products.CMFPlone.PloneTool import PloneTool

        # PloneSite has its own security check for manage_delObjects which
        # breaks in the test runner. So we bypass this check.
        super(portal.__class__, portal).manage_delObjects(['plone_utils'])
        portal._setObject(PloneTool.id, PloneTool())
        logger.info("Replaced obsolete PlonePAS version of plone tool "
                    "with the normal one.")


def updateImportStepsFromBaseProfile(context):
    """Updates the available import steps for existing sites."""
    context.setBaselineContext("profile-%s" % _DEFAULT_PROFILE)

