import os
import string

import zLOG

from Products.StandardCacheManagers import AcceleratedHTTPCacheManager, RAMCacheManager
from Products.CMFDefault.Document import addDocument
from Globals import package_home
from Products.CMFPlone import cmfplone_globals
from Products.CMFCore.utils import getToolByName
from Products.CMFQuickInstallerTool import QuickInstallerTool, AlreadyInstalled
from Products.CMFCore.TypesTool import FactoryTypeInformation
from Products.CMFCore import CachingPolicyManager
from Products.CMFCore.DirectoryView import createDirectoryView, manage_listAvailableDirectories
from Products.CMFCore.Expression import Expression

from Products.CMFPlone.migrations.v2.plone2_base import setupExtEditor, addDocumentActions, addActionIcons
from Products.CMFPlone.migrations.migration_util import safeEditProperty, addLinesToProperty
from Products.CMFPlone.setup import ConfigurationMethods
from Products.CMFPlone import ToolNames, transaction
from portlet_migration import upgradeSlots2Portlets
import plone2_base

from Acquisition import aq_base
from ZODB.POSException import ConflictError

def oneX_twoBeta2(portal):
    """ Migrations from 1.0.x to 2.x """
    out = []

    out.append("Setting type of plone portal to 'Plone Site")
    portal._setPortalTypeName('Plone Site')

    #create the QuickInstaller
    out.append("Creating a quick installer")
    if not hasattr(portal.aq_explicit,'portal_quickinstaller'):
        portal.manage_addProduct['CMFQuickInstallerTool'].manage_addTool('CMF QuickInstaller Tool', None)
    out.append("Adding in a group user folder")
    addGroupUserFolder(portal)

    out.append("Removing deprecated portal_form_validation tool")
    if 'portal_form_validation' in portal.objectIds():
        portal.manage_delObjects('portal_form_validation')

    out.append("Setting default page values")
    props = portal.portal_properties.site_properties
    default_values = ['index_html', 'index.html', 'index.htm', 'FrontPage']
    safeEditProperty(props, 'default_page', default_values, 'lines')
    if props.hasProperty('allow_sendto'):
        props._delProperty('allow_sendto')
    addLinesToProperty(props, 'use_folder_tabs', 'Plone Site')
    if props.hasProperty('portal_factory_types'):
        props._delProperty('portal_factory_types')

    out.append("Turning on syndication")
    portal.portal_syndication.isAllowed=1 #turn syndication on

    out.append("Adding document actions")
    addDocumentActions(portal)
    out.append("Adding action icons")
    addActionIcons(portal)
    out.append("Adding cache accelerators")
    addCacheAccelerators(portal)

    out.append("Adding cache accelerators")
    if 'portal_interface' not in portal.objectIds():
        portal.manage_addProduct['CMFPlone'].manage_addTool('Portal Interface Tool')

    out.append("Altering skins to reflect plone 2 new skin paths")
    fixupPlone2SkinPaths(portal, out)
    # add portal_prefs

    out.append("Adding in a control panel")
    addControlPanel(portal)
    out.append("Upgrading with new portal factory")
    upgradePortalFactory(portal)
    updateNavigationProperties(portal)

    #Support for cropping descriptions in search results
    safeEditProperty(props,'search_results_description_length',160,'int')
    safeEditProperty(props,'ellipsis','...','string')

    #Set ext_editor property in site_properties
    setupExtEditor(portal)

    out.append("Adding in new form controller")
    addFormController(portal)

    out.append("Moving portraits into the memberdata tool")
    #migrate Memberdata, Membership and Portraits
    migrateMemberdataTool(portal)
    migratePortraits(portal)

    out.append("Moving all tools to the new Plone base classes")
    migrateTools(portal)
    out.append(("Moving some old templates out of the way, such as header and footer, since these will cause breakage", zLOG.WARNING))
    moveOldTemplates(portal)
    out.append("Updating nav tree")
    migrateNavTree(portal)
    out.append("Adding error log")
    ConfigurationMethods.addErrorLog(None, portal)
    out.append("Assigning titles to tools")
    ConfigurationMethods.assignTitles(None, portal)

    out.append(("Marking old fs directory views out-dated", zLOG.WARNING))
    deprFsViews(portal)
    removed = removeOldSkins(portal)
    out.append(("Setting default skin to Plone Default and removing old skin from portal_skins. Removed: %s" % ','.join(removed), zLOG.WARNING))

    out.append("Updating slots to portlets")
    upgradeSlots2Portlets(portal)
    out.append("Setting up calendar tool")
    plone2_base.setupCalendar(portal)
    out.append("Fixing portal_undo actions")
    fixUndoActions(portal)
    out.append("Fixing folder contents action")
    fixFolderActions(portal)
    removed = removeTranslationService(portal)
    if removed:
        out.append(("Removing deprecated translation service. Plone 2 is using the PlacelessTranslationService for translation. Your old translation data is still stored in the Localizer.", zLOG.WARNING))
    out.append('Adding plone tableless directory view')
    addPloneTableless(portal)
    return out

def doit(self):
    raise NotImplementedError, "This should be run from migrations now, we have it fixed"

def fixActionsOn(tool):
    if not hasattr(tool.aq_explicit, '_cloneActions'):
        return
    actions=tool._cloneActions()
    for a in actions:
        _action=a.action.text.split('/')
        if 'portal_form' in _action:
            _action.remove('portal_form')
        a.action=Expression('/'.join(_action))
    tool._actions=tuple(actions)

def removePortalFormFromActions(portal):
    at=getToolByName(portal, 'portal_actions')
    aps=at.action_providers
    for ap in aps:
        tool=getToolByName(portal, ap)
        fixActionsOn(tool)

    tt=getToolByName(portal, 'portal_types')
    for ptype in tt.objectValues():
        try:
            fixActionsOn(ptype)
        except ConflictError:
            raise
        except:
            print 'fixActionsOn, ', ptype.getId(), 'failed. '

def _migrate(portal, toolid, name, attrs):
    orig=getToolByName(portal, toolid)
    portal.manage_delObjects(toolid)
    portal.manage_addProduct['CMFPlone'].manage_addTool(name)
    tool = getToolByName(portal, toolid)
    for attr in attrs:
        setattr(tool, attr, aq_base(getattr(aq_base(orig), attr)))
    return aq_base(orig)

def moveOldTemplates(portal):
    #attempt to move old templates out the wway
    st = getToolByName(portal, 'portal_skins')
    custom = st.custom
    for id in custom.objectIds():
        if id in ('main_template', 'header', 'footer', 'folder_contents', 'plone.css'):
            st.custom.manage_renameObjects([id], ['premigration_'+id])

def migrateTools(portal):
    _migrate(portal, 'portal_actionicons', ToolNames.ActionIconsTool, ['_icons','_lookup'])
    _migrate(portal, 'portal_actions', ToolNames.ActionsTool, ['_actions', 'action_providers'])
    _migrate(portal, 'portal_calendar', ToolNames.CalendarTool, [])
    _migrate(portal, 'portal_registration', ToolNames.RegistrationTool, ['_actions'])
    _migrate(portal, 'portal_url', ToolNames.URLTool, ['_actions',])
    _migrate(portal, 'portal_undo', ToolNames.UndoTool, ['_actions'])
    _migrate(portal, 'portal_catalog', ToolNames.CatalogTool, ['_actions', '_catalog'])
    _migrate(portal, 'portal_metadata', ToolNames.MetadataTool, ['_actions', 'element_specs'])
    _migrate(portal, 'portal_syndication', ToolNames.SyndicationTool, [])
    _migrate(portal, 'portal_discussion', ToolNames.DiscussionTool, ['_actions'])
    _migrate(portal, 'portal_groups', ToolNames.GroupsTool, ['_actions',
        'groupworkspaces_id', 'groupWorkspacesCreationFlag',
        'groupWorkspaceType', ])
    _migrate(portal, 'portal_groupdata', ToolNames.GroupDataTool, ['_actions',
        '_members'])

    orig=_migrate(portal, 'portal_types', ToolNames.TypesTool, ['_actions', 'meta_types'])
    tt = getToolByName(portal, 'portal_types')
    for info in orig._objects:
        obj = aq_base(getattr(orig, info['id']))
        tt._setObject(info['id'], obj)

    avail_paths=manage_listAvailableDirectories()
    orig = _migrate(portal, 'portal_skins', ToolNames.SkinsTool, ['selections', '_actions'])
    st = getToolByName(portal, 'portal_skins')
    for info in orig._objects:
        obj = aq_base(getattr(orig, info['id']))
        if obj.meta_type=='Filesystem Directory View' and \
           obj.id not in st.objectIds():
            for avail in avail_paths:
                if obj._dirpath.endswith(avail):
                    createDirectoryView(st, avail, obj.id)
        else:
            if obj.meta_type!='Filesystem Directory View':
                st._setObject(info['id'], obj)
    st.request_varname = orig.request_varname
    st.default_skin = orig.default_skin

    at = getToolByName(portal, 'portal_actions')
    ap = list(at.action_providers)
    if 'portal_types' not in ap:
        ap.append('portal_types')
    if 'portal_properties' not in ap:
        ap.append('portal_properties')
    at.action_providers = tuple(ap)

def migrateNavTree(portal):
    pp=getToolByName(portal,'portal_properties')
    p = getattr(pp , 'navtree_properties', None)

    if not p:
        setupNavTreePropertySheet(pp)

    # these won't set the property... so they won't change a existing navtree
    # since it has them already, so this is pointless. What I believe we want to do
    # here is add TempFolder to the properties...

#    if not p.hasProperty('metaTypesNotToList'):
#        p._setProperty('metaTypesNotToList',['CMF Collector','CMF Collector Issue','CMF Collector Catalog','TempFolder'],'lines')
#    if not p.hasProperty('parentMetaTypesNotToQuery'):
#        p._setProperty('parentMetaTypesNotToQuery',['TempFolder'],'lines')

    # these are all new
    #if not p.hasProperty('typesForcedFolderContents'):
    #    p._setProperty('typesForcedFolderContents', [] , 'lines')
    if not p.hasProperty('bottomLevel'):
        p._setProperty('bottomLevel', 65535 , 'int')
    if not p.hasProperty('idsNotToList'):
        p._setProperty('idsNotToList', [] , 'lines')
    addLinesToProperty(p, 'metaTypesNotToList', 'TempFolder')
    addLinesToProperty(p, 'parentMetaTypesNotToQuery', 'TempFolder')
    safeEditProperty(p, 'croppingLength', 256, 'int')


def migrateMemberdataTool(portal):
    orig_md = getToolByName(portal, 'portal_memberdata')
    portal.manage_delObjects('portal_memberdata')
    portal.manage_addProduct['CMFPlone'].manage_addTool(ToolNames.MemberDataTool)

    md = getToolByName(portal, 'portal_memberdata')
    _actions = getattr(orig_md, '_actions')
    md._actions = aq_base(_actions)
    _members = getattr(orig_md, '_members')
    md._members = aq_base(_members)
    _properties = getattr(orig_md, '_properties')
    md._properties = aq_base(_properties)
    # migrate properties
    for attr in orig_md.propertyIds():
        setattr(md, attr, aq_base(getattr(aq_base(orig_md), attr)))

PORTRAIT_ID='MyPortrait'
def migratePortraits(portal):
    from StringIO import StringIO
    mt = getToolByName(portal, 'portal_membership')
    acl_users = getToolByName(portal, 'acl_users')

    for id in mt.listMemberIds():
        personal=mt.getPersonalFolder(id)
        if personal and PORTRAIT_ID in personal.objectIds():
            portrait=getattr(personal, PORTRAIT_ID)
            buf = StringIO(portrait.data)
            buf.filename=PORTRAIT_ID
            buf.seek(0) #lovely Zope mixing OFS interfaces w/ HTTP interfaces
            mt.changeMemberPortrait(buf, id)

def _newDV(st, dir):
    createDirectoryView(st, os.path.join('CMFPlone', 'skins', dir))

def fixupPlone2SkinPaths(portal, out):
    st=getToolByName(portal, 'portal_skins')
    to_add=['cmf_legacy','plone_portlets', 'plone_form_scripts', 'plone_prefs']
    for item in to_add:
        if item not in portal.portal_skins.objectIds():
            _newDV(st, item)

    to_remove=['plone_templates/ui_slots', 'plone_scripts/form_scripts',
               'plone_3rdParty/CMFCollector', 'plone_3rdParty/CMFCalendar']
    skins = st.getSkinSelections()
    for skin in skins:
        path = st.getSkinPath(skin)
        path = map(string.strip, string.split(path,','))
        for old in to_remove:
            if old in path:
                path.remove(old)
        for new in to_add:
            if new not in path:
                try:
                    path.insert(path.index('plone_forms'), new)
                except ValueError:
                    out.append(("No path plone_forms was found, so couldn't add %s into skin %s" % (new, skin), zLOG.ERROR))
        st.addSkinSelection(skin, ','.join(path))
    return out

def upgradePortalFactory(portal):
    site_props = portal.portal_properties.site_properties
    if not hasattr(site_props,'portal_factory_types'):
        site_props._setProperty('portal_factory_types',('',), 'lines')

    typesTool = getToolByName(portal, 'portal_types')
    # add temporary folder type for portal_factory
    if not hasattr(typesTool, 'TempFolder'):
        typesTool.manage_addTypeInformation(FactoryTypeInformation.meta_type,
                                             id='TempFolder',
                                             typeinfo_name='CMFCore: Portal Folder')
        folder = typesTool.Folder
        tempfolder = typesTool.TempFolder
        tempfolder.content_meta_type='TempFolder'
        tempfolder.icon = folder.icon
        tempfolder.global_allow = 0  # make TempFolder not implicitly addable
        tempfolder.allowed_content_types=(typesTool.listContentTypes())


def addControlPanel(portal):
    from Products.CMFPlone.ToolNames import ControlPanelTool
    addPloneTool=portal.manage_addProduct['CMFPlone'].manage_addTool
    if not hasattr(portal.aq_explicit,'portal_controlpanel'):
        addPloneTool(ControlPanelTool, None)
    # must be done here because controlpanel depends on
    # portal_actionicons concerning icon registration
    portal.portal_controlpanel.registerDefaultConfiglets()


def addCacheAccelerators(portal):
    # add in caches, HTTPCache and RamCache
    meta_type = AcceleratedHTTPCacheManager.AcceleratedHTTPCacheManager.meta_type
    if 'HTTPCache' not in portal.objectIds(meta_type):
        AcceleratedHTTPCacheManager.manage_addAcceleratedHTTPCacheManager(portal, 'HTTPCache')
    meta_type = RAMCacheManager.RAMCacheManager.meta_type
    if 'RAMCache' not in portal.objectIds(meta_type):
        RAMCacheManager.manage_addRAMCacheManager(portal, 'RAMCache')
    if 'caching_policy_manager' not in portal.objectIds():
        CachingPolicyManager.manage_addCachingPolicyManager(portal)


def addGroupUserFolder(portal):
    """ We _must_ commit() here.  Because the Portal does not really exist in
        the ZODB.  And the acl_users object we are moving *must* have a _p_jar
        attribute.  We get the Connection object by commit()ing.

        NOTE: In the Install routine for GRUF it does a subtransaction commit()
        so that you can manipulate the acl_users folders.
    """
    transaction.savepoint(optimistic=True)

    qi=getToolByName(portal, 'portal_quickinstaller')
    addGRUFTool=portal.manage_addProduct['GroupUserFolder'].manage_addTool
    if "portal_groups" not in portal.objectIds():
        qi.installProduct('GroupUserFolder', locked=1)
        addGRUFTool('CMF Groups Tool')
        addGRUFTool('CMF Group Data Tool')

def setupHelpSection(portal):
    # create and populate the 'plone_help' folder in the root of the plone
    # the contents are STX files in CMFPlone/docs
    if 'plone_help' not in portal.objectIds():
        portal.invokeFactory(type_name='Folder', id='plone_help')
        plone_help=portal.plone_help
        docs_path=os.path.join(package_home(cmfplone_globals), 'help')
        for filename in os.listdir(docs_path):
            _path=os.path.join(docs_path, filename)
            if not os.path.isdir(_path):
                doc=open(_path, 'r')
                addDocument(plone_help, filename, filename, '',
                            'structured-text', doc.read())
                getattr(plone_help,filename)._setPortalTypeName('Document')

def addFormController(portal):
    """ """
    try:
        qi=getToolByName(portal, 'portal_quickinstaller')
        qi.installProduct('CMFFormController', locked=1)
    except AlreadyInstalled:
        qi.notifyInstalled('CMFFormController') #Portal.py got to it first

def addActionIcons(portal):
    """ After installing QuickInstaller.  We must savepoint(optimistic=True) a
        subtrnx so that we will be able to addActionIcons() to the tool
    """
    try:
        qi=getToolByName(portal, 'portal_quickinstaller')
        qi.installProduct('CMFActionIcons', locked=1)
    except AlreadyInstalled:
        qi.notifyInstalled('CMFActionIcons') #Portal.py got to it first

    ai=getToolByName(portal, 'portal_actionicons')
    ai.addActionIcon('plone', 'sendto', 'mail_icon.gif', 'Send-to')

    ai.addActionIcon('plone', 'print', 'print_icon.gif', 'Print')
    ai.addActionIcon('plone', 'rss', 'rss.gif', 'Syndication')
    ai.addActionIcon('plone', 'extedit', 'extedit_icon.gif', 'ExternalEdit')

def addDocumentActions(portal):
    at = portal.portal_actions

    at.addAction('extedit',
                 'Edit this file in an external application (Requires Zope ExternalEditor installed)',
                 'string:$object_url/external_edit',
                 "python: hasattr(portal.portal_properties.site_properties, 'ext_editor') and portal.portal_properties.site_properties.ext_editor and object.absolute_url() != portal_url",
                 'Modify portal content',
                 'document_actions')

    at.addAction('rss',
                 'RSS feed of this folder\'s contents',
                 'string:$object_url/RSS',
                 'python:portal.portal_syndication.isSyndicationAllowed(object)',
                 'View',
                 'document_actions')

    at.addAction('sendto',
                 'Send this page to somebody',
                 'string:$object_url/portal_form/sendto_form',
                 '',
                 'View',
                 'document_actions')

    at.addAction('print',
                 'Print this page',
                 'string:javascript:this.print();',
                 '',
                 'View',
                 'document_actions')

def updateNavigationProperties(portal):
    nav_props = portal.portal_properties.navigation_properties
    nav = (('default.reconfig.success', 'url:plone_control_panel'),
           )
    for id, action in nav:
        if nav_props.hasProperty(id):
            nav_props._updateProperty(id, action)
        else:
            nav_props._setProperty(id, action)

def deprFsViews(portal):
    st = getToolByName(portal, 'portal_skins')
    fsViewIds = ['Images', 'actionicons', 'calendar', 'content', 'control',
        'generic', 'no_css', 'nouvelle', 'topic', 'zpt_calendar', 'zpt_content',
        'zpt_control', 'zpt_generic', 'zpt_topic',
        ]
    for fsViewId in fsViewIds:
        view = getattr(st, fsViewId, None)
        if view:
            view.title += 'Deprecated, can safely be deleted'

    for skin in st.getSkinSelections():
        oldpath = st.getSkinPath(skin)
        oldpath = [p.strip() for p in oldpath.split(',')]
        # all non depr pathes
        newpath = [p for p in oldpath if p not in fsViewIds and p != 'cmf_legacy' ]
        # all depr pathes that are in oldpath
        deprpath = [p for p in oldpath if p in fsViewIds ]
        # add depr pathes to the end after cmf_legacy
        newpath.append('cmf_legacy')
        newpath.extend(deprpath)
        # remove all plone_styles/ subdirs
        newpath = [ p for p in newpath if not p.startswith('plone_styles/') ]

        path = ','.join(newpath)
        st.addSkinSelection(skin, path)

def removeOldSkins(portal):
    st = getToolByName(portal, 'portal_skins')
    st.default_skin = 'Plone Default'
    removed = []
    skins = ['Plone Autumn', 'Plone Core', 'Plone Core Inverted',
             'Plone Corporate', 'Plone Greensleeves', 'Plone Kitty',
             'Plone Mozilla', 'Plone Mozilla New', 'Plone Prime',
             'Plone Zed', ]
    skinList = st._getSelections()
    for skin in skins:
        if skinList.has_key(skin):
            del skinList[skin]
            removed.append(skin)
    return removed

def fixUndoActions(portal):
    st = getToolByName(portal, 'portal_undo')
    # remove old actions
    for action in st._actions:
        if action.id in ('undo',):
            del action
    st.addAction('undo',
                 'Undo',
                 'string:${portal_url}/undo_form',
                 'member',
                 'List undoable changes',
                 'user')

def fixFolderActions(portal):
    st = getToolByName(portal, 'portal_actions')
    for action in st._actions:
        if action.id == 'folderContents':
            action.visible = 1

def removeTranslationService(portal):
    """remove old translation service
    """
    ts = 'translation_service'
    if hasattr(portal.aq_explicit, ts):
        portal.manage_delObjects(ts)
        return 1

def addPloneTableless(portal):
    st = getToolByName(portal, 'portal_skins')
    if not hasattr(aq_base(st), 'plone_tableless'):
        _newDV(st, 'plone_tableless')

def registerMigrations():
    MigrationTool.registerUpgradePath('1.0.1','1.1alpha2',upg_1_0_1_to_1_1)
    MigrationTool.registerUpgradePath('1.0.2','1.1alpha2',upg_1_0_1_to_1_1)
    MigrationTool.registerUpgradePath('1.0.3','1.1alpha2',upg_1_0_1_to_1_1)

if __name__=='__main__':
    registerMigrations()
