##############################################################################
# Copyright (c) 2004-2005 Black Rock City, LLC
# Copyright (c) 2004-2005 ObjectRealms, LLC
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
##############################################################################
"""
$Id: team.py 51263 2007-10-13 00:17:50Z rafrombrc $
"""

import os
from sets import Set

from zope.interface import implements

from AccessControl import getSecurityManager

from Products.CMFCore.WorkflowCore import WorkflowException
from Products.CMFCore.utils import getToolByName
from Products.Archetypes.BaseBTreeFolder import BaseBTreeFolder
from Products.Archetypes.public import registerType

from Products.TeamSpace.exceptions import MemberNotFound
from Products.TeamSpace.membership import TeamMembership
from Products.TeamSpace.permissions import *
from Products.TeamSpace.relations import TeamMembershipRelation
from Products.TeamSpace.relations import TeamSpaceTeamRelation
from Products.TeamSpace.relations import MemberTeamRelation
from Products.TeamSpace.zimport import *
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.TeamSpace.interfaces.team import ITeam
from Products.TeamSpace.interfaces.security import ITeamSecurity

_zmi = os.path.join(os.path.dirname(__file__), 'zmi')


team_type_information = {
  'title'          : 'Team',
  'id'             : 'Team',
  'portal_type'    : 'Team',
  'meta_type'      : 'Team',
  'description'    : 'A Collection of Members',
  'content_icon'   : 'team_icon.png',
  'product'        : 'TeamSpace',
  'factory'        : 'addTeam',
  'global_allow'   : 0,
  'immediate_view' : 'base_edit',
  'filter_content_types' : 1,
  'allowed_content_types': (),
  'actions': (
    {
    'name'         : 'Member List',
    'id'           : 'view',
    'action'       : 'ts_team_list_view',
    'permissions'  : (ViewTeam,),
    'category'     : 'object',
    },
    {
    'name'          : 'Member Mgmt',
    'id'            : 'membership',
    'action'        : 'ts_team_manage_memberships',
    'permissions'   : (ManageTeamMembership,),
    'category'      : 'object',
    },
    {
    'name'         : 'Member Roles',
    'id'           : 'member_roles',
    'action'       : 'ts_team_manage_roles',
    'permissions'  : (ManageTeam,),
    'category'     : 'object',
    },
    {
    'name'         : 'Edit',
    'id'           : 'edit',
    'action'       : 'base_edit',
    'permissions'  : (ManageTeam,),
    'category'     : 'object',
    },
    {
    'name'         : 'Properties',
    'id'           : 'metadata',
    'action'       : 'base_metadata',
    'permissions'  : (ManageTeam,),
    'category'     : 'object',
    },
  ),
  }

factory_type_information = (team_type_information, )

def addTeam(self, id, **kwargs):
    o = Team(id)
    self._setObject(id, o)
    o = getattr(self, id)
    o.initializeArchetype(**kwargs)
    return id

class Team(BaseBTreeFolder):
    """ Holds team membership objects and relates them to their members """

    meta_type = "Team"
    archetype_name = "Team"
    portal_type = meta_type
    _at_rename_after_creation = True

    implements(ITeam, ITeamSecurity)

    manage_options=(
        {'label':'Policy', 'action':'manage_localTeamPolicyForm'},
        )  + BaseBTreeFolder.manage_options

    manage_localTeamPolicyForm=PageTemplateFile('local_team_policy', _zmi)

    security = ClassSecurityInfo()
    use_folder_tabs = 0

    membership_reference_class = TeamMembershipRelation

    _default_membership_type = ''
    
    def __init__(self, id):
        BaseBTreeFolder.__init__(self, id)
        # Assigned durning initializeArchetype from portal_teams
        self._allowed_team_roles = ()
        self._active_states = ()
        self._default_roles = ()
        self._default_membership_type = ''

    security.declareProtected(ManageTeamMembership, 'reindexTeamSpaceSecurity')
    def reindexTeamSpaceSecurity(self):
        """
        reindex security for this team and all teamspaces associated
        with this team.
        """
        for space in self.getTeamSpaces():
            space.reindexObjectSecurity()
        self.reindexObjectSecurity()

    security.declareProtected(ManageTeamMembership, 'addMember')
    def addMember(self, member_id, membership_type=None, reindex=True):
        """
        add a membership object with the given member id and associate
        via archetypes reference engine with the member object
        """
        pm_tool = getToolByName(self, 'portal_membership')
        pt_tool = getToolByName(self, 'portal_types')
        teams_tool = getToolByName(self, 'portal_teams')

        membership = self._getOb(member_id, None)
        if membership is not None: # member already exists
            return

        member = pm_tool.getMemberById(member_id)

        if member is None:
            raise MemberNotFound(str(member_id))

        if membership_type is None:
            membership_type = self.getDefaultMembershipType()
        elif not membership_type in teams_tool.allowed_membership_types:
            raise RuntimeError("Invalid Membership Type %r" % membership_type)

        pt_tool.constructContent(membership_type, self, member_id)

        membership = self._getOb(member_id)
        refclass = self.membership_reference_class
        member.addReference(membership,
                            relationship=refclass.relationship,
                            referenceClass=refclass)

        # Assign the default Role to the member
        # We want to assign the default roles to the member
        membership.editTeamRoles(self._default_roles)

        # And notify space objects as these are the most likely
        # subclasses that could hook the event.
        spaces = self.getTeamSpaces()
        for space in spaces:
            space._updateMember('add', member, membership, self)
        if reindex:
            self.reindexTeamSpaceSecurity()

        return membership

    security.declareProtected(ManageTeamMembership, 'addMembers')
    def addMembers(self, member_ids, membership_type=None, action=None):
        """
        create multiple memberships and apply workflow transition specified
        by 'action'

        NOTE: this method is very forgiving... if a member already exists,
        or if the workflow action can't be taken, no error will be raised,
        and the rest of the memberships will still be added
        """
        if membership_type is None:
            membership_type = self.getDefaultMembershipType()

        addM = self.addMember
        [addM(id, membership_type=membership_type, reindex=False) \
                for id in member_ids]

        if action:
            wf_tool = getToolByName(self, 'portal_workflow')
            doaction = wf_tool.doActionFor
            getM = self.getMembershipByMemberId

            for id in member_ids:
                try:
                    doaction(getM(id), action)
                except WorkflowException:
                    pass # be forgiving 
        self.reindexTeamSpaceSecurity()

    security.declareProtected(ManageTeamMembership, 'removeMember')
    def removeMember(self, member_id):
        """
        remove the member's membership if it exists
        """
        membership = self._getOb(member_id, None)
        if membership:
            self.manage_delObjects([member_id])
        self.reindexTeamSpaceSecurity()

    security.declareProtected(ViewTeam, 'getTeams')
    def getTeams(self):
        """
        this seemingly superfluous method allows template code to
        to treat teams and spaces in the same manner
        """
        return [self]

    security.declareProtected(ViewAllTeam, 'getMembershipBrains')
    def getMembershipBrains(self):
        """
        return the portal_catalog brains for all of the team's
        membership objects
        """
        cat = getToolByName(self, 'portal_catalog')
        path = '/'.join(self.getPhysicalPath())
        return cat.unrestrictedSearchResults(path={'query': path, 'depth':1},
                   Type=self.getAllowedMembershipTypes())

    security.declareProtected(ViewTeam, 'getFilteredMembers')
    def getFilteredMembers(self):
        """
        filter automatically based on current user's permissions
        """
        cur_user = getSecurityManager().getUser()
        mems = []
        for mem in self.getMembers():
            if cur_user.has_permission('View', mem):
                mems.append(mem)
        return mems

    security.declareProtected(ViewAllTeam, 'getMemberships')
    def getMemberships(self):
        """
        returns all memberships for a team
        """
        return self.objectValues(self.getAllowedMembershipTypes())

    def _getMembershipByMemberId(self, member_id):
        """
        real implementation of membership retrieval; currently uses
        containment, can be improved to use references
        """
        return self._getOb(member_id, None)

    security.declareProtected(ViewAllTeam, 'getMembershipByMemberId')
    def getMembershipByMemberId(self, member_id, active_only=False):
        """
        this is a stub so if we want to expand to use references
        instead of containment
        """
        mship = self._getMembershipByMemberId(member_id)
        if (mship is None) or (not active_only):
            return mship
        wftool = getToolByName(self, 'portal_workflow')
        status = wftool.getInfoFor(mship, 'review_state')
        if status in self._active_states:
            return mship

    security.declareProtected(ViewAllTeam, 'getMembers')
    def getMembers(self):
        """
        return all members in the team
        """
        ttool = getToolByName(self, 'portal_teams')
        rship = MemberTeamRelation.relationship
        mbrains = ttool.getRefSourceBrains(self,
                                           relationship=rship)
        return [m.getObject() for m in mbrains]

    security.declareProtected(ViewTeam, 'getActiveMembers')
    def getActiveMembers(self):
        """
        return all the members that are active in this team
        """
        return self.getMembersByStates(self._active_states)

    security.declareProtected(ViewTeam, 'getActiveMemberIds')
    def getActiveMemberIds(self):
        """
        return all the member ids that are active in this team
        """
        return self.getMemberIdsByStates(self._active_states)

    security.declareProtected(ViewTeam, 'getActiveMemberships')
    def getActiveMemberships( self ):
        """
        return the memberships for all members active in this team.
        """
        return self.getMembershipsByStates(self._active_states)

    security.declareProtected(ViewAllTeam, 'getMembersByStates')
    def getMembersByStates(self, wf_states):
        """
        return all members whose membership has the given status
        """
        mtool = getToolByName(self, 'portal_membership')
        mem_ids = self.getMemberIdsByStates(wf_states)
        return [mtool.getMemberById(m_id) for m_id in mem_ids]

    security.declareProtected(ViewAllTeam, 'getMemberIdsByStates')
    def getMemberIdsByStates(self, wf_states):
        """
        return all member ids whose membership has the given status

        XXX assumes mship id == member id
        """
        brains = self.getMembershipBrains()
        return [b.id for b in brains if b.review_state in wf_states]

    security.declareProtected(ViewAllTeam, 'getMemberIdsByStates')
    def getMembershipsByStates(self, wf_states):
        """
        return all memberships of the given states
        """
        brains = self.getMembershipBrains()
        return [b._unrestrictedGetObject() for b in brains
                if b.review_state in wf_states]

    security.declareProtected(ViewAllTeam, 'getMemberIds')
    def getMemberIds(self):
        """
        return the member ids of all members in this team

        # XXX assumes mship id == member id
        """
        brains = self.getMembershipBrains()
        return [b.id for b in brains]

    security.declareProtected(ManageTeam, 'setAllowedTeamRoles')
    def setAllowedTeamRoles(self, team_roles):
        """
        Set the roles that may be assigned via the team system ui to
        a member.
        """
        valid_roles = self.valid_roles()
        res = [tr for tr in team_roles if tr in valid_roles]
        self._allowed_team_roles = tuple( res )

    security.declareProtected(ManageTeam, 'setActiveStates')
    def setActiveStates(self, active_states):
        """
        set the active workflow statuses
        XXX *not* the same as states
        """
        self._active_states = tuple( active_states )

    security.declareProtected(ManageTeam, 'setDefaultRoles')
    def setDefaultRoles(self, default_roles):
        """
        set the default role for new memberships
        """
        valid_roles = self.valid_roles()
        res = [tr for tr in default_roles if tr in valid_roles]
        self._default_roles = tuple( res )

    security.declareProtected(ManageTeam, 'setDefaultRoles')
    def getDefaultRoles(self):
        """
        get the default roles for new members
        """
        return self._default_roles

    # XXX security on the following might be too restrictive.
    security.declareProtected(ManageTeam, 'getAllowedTeamRoles')
    def getAllowedTeamRoles(self):
        """
        get the roles that can be assigned to team members as part of their
        membership
        """
        return self._allowed_team_roles

    security.declareProtected(ManageTeam, 'getOrphanedRoles')
    def getOrphanedRoles(self):
        """
        get the roles that are now hidden from the team zmi interface
        """
        tt = getToolByName(self, 'portal_teams')

        orphans = Set(self._allowed_team_roles) - Set(tt.getDefaultAllowedRoles())
        return tuple(orphans)

    security.declareProtected(ManageTeam, 'getActiveStates')
    def getActiveStates(self):
        """
        get the set of workflow status information that corresponds to
        member's membership workflow status who should be considered
        active for this team.
        """
        return self._active_states

    security.declarePublic('getAllowedMembershipTypes')
    def getAllowedMembershipTypes(self):
        """
        get the set of allowed team membership types
        """
        return self.allowed_content_types

    security.declareProtected(ManageTeam, 'getDefaultMembershipType')
    def getDefaultMembershipType(self):
        """
        get the default team membership type
        """
        return self._default_membership_type

    security.declareProtected(ManageTeam, 'setDefaultMembershipType')
    def setDefaultMembershipType(self, mship_type):
        """
        set the default membership type
        """
        if mship_type not in self.getAllowedMembershipTypes():
            raise ValueError, "Unsupported team membership type: %s" % mship_type
        self._default_membership_type = mship_type

    security.declarePublic('getTeamSpaces')
    def getTeamSpaces(self):
        """
        returns any associated TeamSpaces
        """
        return self.getBRefs(relationship=TeamSpaceTeamRelation.relationship)

    security.declareProtected(ManageTeam, 'manage_updateRoles')
    def manage_updateRoles(self, member_roles, REQUEST=None):
        """
        member roles is a list of dictionary like objects specifying
        member(ship) id and a set of team roles. the roles are compared
        to the existing roles, and updated if they differ.
        """

        for mroles in member_roles:
            roles = mroles.get('roles', [])
            mid   = mroles['member_id']

            membership = self._getOb(mid, None)
            if membership is None:
                continue

            existing_roles = membership.getTeamRoles()
            existing_roles.sort()
            roles.sort()

            if roles != existing_roles:
                membership.editTeamRoles( roles )

        self.reindexTeamSpaceSecurity()

        if REQUEST is not None:
            return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])

    security.declareProtected(ManageTeam, 'manage_localTeamPolicy')
    def manage_localTeamPolicy(self,
                               allowed_membership_types=None,
                               default_membership_type=None,
                               allowed_roles=None,
                               default_roles=None,
                               default_active_states=None,
                               REQUEST=None):
        """Form processing management method for local team policy assignment"""
        self.setAllowedTeamRoles(allowed_roles)

        if default_roles==None:
            default_roles=()

        self.allowed_content_types = allowed_membership_types
        self.setDefaultMembershipType(default_membership_type)
        self.setDefaultRoles(default_roles)
        self.setActiveStates(default_active_states)
        if REQUEST:
            return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])

    security.declareProtected(ManageTeam, 'manage_updateAllowedMembershipTypes')
    def manage_updateAllowedMembershipTypes(self, allowed_types, REQUEST=None):
        """ Manage your allowed team membership subclasses """

        self.allowed_content_types=tuple(allowed_types)

        if REQUEST:
            return REQUEST.RESPONSE.redirect(REQUEST['HTTP_REFERER'])

    def __nonzero__(self): return 1

    #################################
    # initialize instance
    def initializeArchetype(self, **kw):
        BaseBTreeFolder.initializeArchetype(self, **kw)
        # Use our aq context to find the team tool and get our initial state

        tt = getToolByName(self, 'portal_teams')

        self.allowed_content_types = tt.allowed_membership_types

        if not self._active_states:
            self._active_states = tt.getDefaultActiveStates()

        if not self._allowed_team_roles:
            self._allowed_team_roles = tt.getDefaultAllowedRoles()

        if not self._default_roles:
            self._default_roles = tt.getDefaultRoles()

        if not self._default_membership_type:
            self._default_membership_type = tt.getDefaultMembershipType()

    #################################
    # ITeamSecurity implementation
    def _getTeamsForLocalRoles(self):
        return [ self ]

registerType(Team)

