# Copyright (c) 2005 Infrae. All rights reserved.
# See also LICENSE.txt
import sets

from persistent import Persistent

from zope.interface import implements
from zope import event
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm

from zope.app import zapi
from zope.app.exception.interfaces import UserError
from zope.app.container.contained import Contained
from zope.app.folder import Folder
from zope.app.component.hooks import getSite
from zope.app.securitypolicy.interfaces import IPrincipalRoleManager
from zope.app.event import objectevent

from hurry.query.interfaces import IQuery
from hurry.query.set import AnyOf
from hurry.query import Eq, In

# documentlibrary
from documentlibrary.core import interfaces

# XXX get prefix from some central place
prefix = 'documentlibrary'

class UserInfo(Persistent, Contained):

    implements(interfaces.IUserInfoContained)

    def __init__(
            self, login, password, role, title, email, description, groups):
        self._role = None
        self._email = None
        
        self.login = login
        self.password = password
        self.role = role
        self.title = title
        self.email = email
        self.description = description
        self.groups = sets.Set(groups)
        
    def _set_role(self, role):
        manager = IPrincipalRoleManager(getSite())
        principal_id = prefix + self.login
        
        if self._role is not None:
            manager.unsetRoleForPrincipal(getDocumentLibraryRole(self._role),
                                          principal_id)
        unownDocumentsForUser(self)

        self._role = role
        
        manager.assignRoleToPrincipal(getDocumentLibraryRole(role),
                                      principal_id)
        ownDocumentsForUser(self)

    def _get_role(self):
        return self._role

    role = property(_get_role, _set_role)
    
    def _set_email(self, value):
        if self._email is not None:
            unownDocumentsForUser(self)
        if value is not None:
            value = value.lower()
        self._email = value
        ownDocumentsForUser(self)
        
    def _get_email(self):
        return self._email
    
    email = property(_get_email, _set_email)
    
class UserFolder(Folder):

    implements(interfaces.IUserFolder)

    def chooseName(self, name, obj):
        return obj.login
            
    def checkName(self, name, obj):
        # XXX should check whether login name is not existing in
        # groupfolder..
        if name in self:
            raise UserError(u'Login name already exists: %s' % name)

class BaseGroupFolder(Folder):
    def chooseName(self, name, obj):
        return obj.login

    def checkName(self, name, obj):
        # XXX should check whether login name is not existing in
        # userfolder..
        if name in self:
            raise UserError(u'Group name already exists: %s' % name)

class BaseGroupInfo(Persistent, Contained):
    def __init__(self, login, title, description):
        self.login = login
        self.title = title
        self.description = description
        
class GroupInfo(BaseGroupInfo):

    implements(interfaces.IGroupInfoContained)
    
    def __init__(self, login, title, description, users):
        super(GroupInfo, self).__init__(login, title, description)
        self.users = users

    def _set_users(self, usernames):
        users = getSite()['users']
        # remove this group from all users
        for username in list(self.users):
            user = users[username]
            user._p_changed = True
            user.groups.remove(self.login)
            event.notify(
                objectevent.ObjectModifiedEvent(user))
            
        # and add it again
        for username in usernames:
            user = users[username]
            user._p_changed = True
            user.groups.add(self.login)
            event.notify(
                objectevent.ObjectModifiedEvent(user))
        
    def _get_users(self):
        q = zapi.getUtility(IQuery)
        return sets.Set([user.login for user in q.searchResults(
            AnyOf(('user_catalog', 'groups'), [self.login]))])
    
    users = property(_get_users, _set_users)

class GroupFolder(BaseGroupFolder):
    implements(interfaces.IGroupFolder)

class IpGroupInfo(BaseGroupInfo):
    implements(interfaces.IIpGroupInfo)

    def __init__(self, login, title, description, ip_ranges):
        super(IpGroupInfo, self).__init__(login, title, description)
        self.ip_ranges = ip_ranges
    
    def inGroup(self, ip):
        for ip_range in self.ip_ranges:
            if ip in ip_range:
                return True
        return False
    
class IpGroupFolder(BaseGroupFolder):
    implements(interfaces.IIpGroupFolder)

# XXX basing role on user-visible name is not very good -- we need
# to separate token interfaces.SUBMITTER etc from end-user visible
# name at some point
def userAddedHandler(event):
    object = event.object
    if not interfaces.IUserInfo.providedBy(object):
        return
    site = getSite()
    IPrincipalRoleManager(site).assignRoleToPrincipal(
        getDocumentLibraryRole(object.role),
        prefix + object.login)
    ownDocumentsForUser(object)
        
def userRemovedHandler(event):
    object = event.object
    if not interfaces.IUserInfo.providedBy(object):
        return
    site = getSite()
    IPrincipalRoleManager(site).unsetRoleForPrincipal(
        getDocumentLibraryRole(object.role),
        prefix + object.login)
    unownDocumentsForUser(object)

def groupRemovedHandler(group, event):
    # make sure no users list this group anymore
    group.users = []
    
def getDocumentLibraryRole(role):
    return 'documentlibrary.' + role + 'User'

def ownDocumentsForUser(user):
    if (user.role not in
        [interfaces.SUBMITTER, interfaces.LIBRARIAN, interfaces.MANAGER]):
        return
    q = zapi.getUtility(IQuery)
    documents = q.searchResults(
        AnyOf(('document_catalog', 'emails'), [user.email]))
    principal_id = prefix + user.login
    for document in documents:
        manager = IPrincipalRoleManager(document)
        manager.assignRoleToPrincipal('documentlibrary.DocumentOwner',
                                      principal_id)

def unownDocumentsForUser(user):
    q = zapi.getUtility(IQuery)
    documents = q.searchResults(
        AnyOf(('document_catalog', 'emails'), [user.email]))
    principal_id = prefix + user.login
    for document in documents:
        manager = IPrincipalRoleManager(document)
        manager.unsetRoleForPrincipal('documentlibrary.DocumentOwner',
                                       principal_id)

class UserInfoLookup(Persistent, Contained):
    implements(interfaces.IUserInfoLookup)
    
    def getUserInfo(self, username):
        try:
            return getSite()['users'][username]
        except KeyError:
            return None

    def getUserInfoDefault(self, username):
        userinfo = self.getUserInfo(username)
        if userinfo is not None:
            return userinfo
        return UserInfo(username, None, 'Manager', username, None, u'', [])

    def getGroupInfo(self, groupname):
        site = getSite()
        try:
            return site['groups'][groupname]
        except KeyError:
            try:
                return site['ipgroups'][groupname]
            except KeyError:
                return None
        
    def getEmailAddresses(self, usernames):
        """Get email addresses for users.
        
        Users that do not exist will be silently skipped -- they're
        presumably defined outside of the document library and thus
        have no email address associated with them.
        """
        result = []
        users = getSite()['users']
        for username in usernames:
            userinfo = self.getUserInfo(username)
            if userinfo is not None:
                result.append(userinfo.email)
        return result

    def getFullname(self, username):
        userinfo = self.getUserInfo(username)
        if userinfo is None:
            return username
        return userinfo.title

    def getUserInfoForEmail(self, email):
        q = zapi.getUtility(IQuery)
        users = q.searchResults(Eq(('user_catalog', 'email'), email))
        if not users:
            return None
        # only get first user
        for user in users:
            return user

def getUsernamesForRoles(roles):
    query = zapi.getUtility(IQuery)
    users = query.searchResults(
        In(('user_catalog', 'role'), roles))
    return sorted([user.login for user in users])

def UserVocabulary(context):
    users = sorted(getSite()['users'].keys())
    return SimpleVocabulary.fromValues(users)

def LibrarianVocabulary(context):
    return SimpleVocabulary.fromValues(
        getUsernamesForRoles([interfaces.LIBRARIAN, interfaces.MANAGER]))

def SubmitterVocabulary(context):
    return SimpleVocabulary.fromValues(
        getUsernamesForRoles([interfaces.SUBMITTER, interfaces.LIBRARIAN,
                              interfaces.MANAGER]))

def GroupVocabulary(context):
    groups = sorted(getSite()['groups'].keys())
    return SimpleVocabulary.fromValues(groups)

class AllGroupVocabulary(SimpleVocabulary):
    def __init__(self, context):
        terms = [SimpleTerm('zope.Everybody', title='EVERYBODY')]
        for groupname, group in getSite()['ipgroups'].items():
            terms.append(SimpleTerm(groupname,
                                    title=group.title + ' (IP-based)'))
        for groupname, group in getSite()['groups'].items():
            terms.append(SimpleTerm(groupname,
                                    title=group.title))
        super(AllGroupVocabulary, self).__init__(terms)

class ResponsibleDocumentUsers(object):
    
    implements(interfaces.IResponsibleDocumentUsers)
    
    def __init__(self, context):
        self.context = context
        
    def getLibrarians(self):
        category = self.context.getCategory()
        return category.getOwningLibrarians()
    
    def getAuthors(self):
        lookup = zapi.getUtility(interfaces.IUserInfoLookup)
        authors = []
        for firstname, lastname, email in self.context.authors:
            user = lookup.getUserInfoForEmail(email)
            if user is None:
                continue
            authors.append(user.login)
        return authors
            
    def getAllResponsible(self):
        return self.getLibrarians() + self.getAuthors()
    
    def getNearestLibrarian(self):
        category = self.context.getCategory()
        return category.getNearestLibrarian()
        
