from zope.schema import TextLine
from zope.interface import Interface, implements
from zope.component import ComponentLookupError
from zope.security.interfaces import NoInteraction
from zope.security.management import getInteraction
from zope.security.interfaces import IGroup, IGroupAwarePrincipal

from zope.app.container.contained import Contained
from zope.app.authentication import interfaces
from zope.app.folder.folder import Folder
from zope.app.component.hooks import getSite
from zope.app.security.settings import Allow, Deny, Unset
from zope.app.securitypolicy import principalpermission

# Note that is *not* the group folder that store the groups...
from zope.app.authentication.groupfolder import IGroupFolder

class IPrincipalPrefix(Interface):
    prefix = TextLine(
        title=u"Prefix",
        description=u"Prefix to be added to all principal ids to assure "
        "that all ids are unique within the authentication service",
        required=False,
        missing_value=u"",
        default=u'',
        readonly=True)

class ISearchSchema(Interface):
    """Search Interface for this Principal Provider"""

    search = TextLine(
        title=u"Search String",
        description=u"A Search String",
        required=False,
        default=u'',
        missing_value=u'')

class PrincipalInfo:

    implements(interfaces.IPrincipalInfo)

    def __init__(self, id, login, title, description):
        self.id = id
        self.login = login
        self.title = title
        self.description = description

    def __repr__(self):
        return 'PrincipalInfo(%r)' % self.id
    
class ContentSpaceAuthenticatorBase(Folder):
    """An AuthenticatorPlugin that defers to content space.
    """

    implements(interfaces.IAuthenticatorPlugin,
               interfaces.IQueriableAuthenticator,
               interfaces.IQuerySchemaSearch,
               IGroupFolder,
               IPrincipalPrefix)

    schema = ISearchSchema

    def __init__(self, prefix=''):
        self.prefix = unicode(prefix)
        super(ContentSpaceAuthenticatorBase, self).__init__()

    def authenticateCredentials(self, credentials):
        """Return principal info if credentials can be authenticated
        """
        if not isinstance(credentials, dict):
            return None
        if not ('login' in credentials and 'password' in credentials):
            return None
        principal = self.getContentSpacePrincipal(credentials['login'])
        if principal is None:
            return None
        # XXX a group won't be authenticated ever, right?
        if principal.password != credentials['password']:
            return None
        return PrincipalInfo(self.prefix + principal.login,
                             principal.login, principal.title,
                             principal.description)

    def principalInfo(self, id):
        if not id.startswith(self.prefix):
            return None
        principal = self.getContentSpacePrincipal(id[len(self.prefix):])
        if principal is None:
            return None
        return PrincipalInfo(id, principal.login, principal.title,
                             principal.description)

    def search(self, query, start=None, batch_size=None):
        """Search through this principal provider."""
        search = query.get('search')
        if search is None:
            return []
        return self.searchContentSpace(search, start, batch_size)

    def getGroupsForPrincipal(self, principal_id):
        if not principal_id.startswith(self.prefix):
            return []
        principal_id = principal_id[len(self.prefix):]
        user = self.getUserFolder().get(principal_id)
        if user is None:
            return []
        return [(self.prefix + groupname) for groupname in user.groups]
    
    def getPrincipalsForGroup(self, groupid):
        # XXX prefix handling may be borked
        group = self.getGroupFolder().get(groupid)
        if group is None:
            return []
        return group.users
    
    def getUserFolder(self):
        """Override this in a subclass.
        """
        raise NotImplementedError

    def getGroupFolder(self):
        """Override this in a subclass.
        """
        raise NotImplementedError

    def getIpGroupFolder(self):
        raise NotImplementedError
    
    def __contains__(self, groupid):
        return groupid in self.getGroupFolder()
        
    def getContentSpacePrincipal(self, login):
        user = self.getUserFolder().get(login)
        if user is not None:
            return user
        group = self.getGroupFolder().get(login)
        if group is not None:
            return group
        return self.getIpGroupFolder().get(login)
        
    def searchContentSpace(self, search, start, batch_size):
        # XXX should look in groups too..
        folder = self.getUserFolder()
        search = search.lower()
        i = 0
        n = 1
        for value in folder.values():
            if (search in value.title.lower() or
                search in value.description.lower() or
                search in value.login.lower()):
                if not ((start is not None and i < start)
                        or (batch_size is not None and n > batch_size)):
                    n += 1
                    yield self.prefix + value.login
                i += 1
               
class DocumentLibraryAuthenticator(ContentSpaceAuthenticatorBase):
    """
    Content space in this case is defined as a 'users' folder just under the
    site.
    """

    def getUserFolder(self):
        return getSite()['users']

    def getGroupFolder(self):
        return getSite()['groups']

    def getIpGroupFolder(self):
        return getSite()['ipgroups']

class PrincipalPermissionManager(
    principalpermission.AnnotationPrincipalPermissionManager):
    """This is a bit of a hack to allow people with the right ip address
    permission to access a document.
    """
    
    def getPermissionsForPrincipal(self, principal_id):
        result = super(PrincipalPermissionManager,
                       self).getPermissionForPrincipal(principal_id)
        if self._isIpPermitted():
            result.append('documentlibrary.ViewDocument')
        return result
        
    def getSetting(self, permission_id, principal_id, default=Unset):
        result = super(PrincipalPermissionManager,
                       self).getSetting(permission_id, principal_id, default)
        if result is not default:
            return result
        if self._isIpPermitted():
            return Allow
        return result
    
    def _isIpPermitted(self):
        document = self._context
        ip = getIp()
        ipgroups = getSite()['ipgroups']
        for groupname in document.access:
            ipgroup = ipgroups.get(groupname)
            if ipgroup is not None:
                if ipgroup.inGroup(ip):
                    return True
        return False

def getIp():
    try:
        request = getInteraction().participations[0]
        try:
            return request['REMOTE_ADDR']
        except KeyError:
            return None

        # XXX in Zope 3, there's no way to configure
        # trusted_proxies, and HTTP_X_FORWARDED_FOR probably
        # won't appear in ENVIRON
##         environ = request.environ
##         if environ.has_key('REMOTE_ADDR'):
##             ip = environ['REMOTE_ADDR']
##             if (environ.has_key('HTTP_X_FORWARDED_FOR') and
##                 ip in trusted_proxies):
##                 # REMOTE_ADDR is one of our trusted local proxies. Not really
##                 # very remote at all. The proxy can tell us the IP of the real
##                 # remote client in the forwarded-for header
##                 ip = environ['HTTP_X_FORWARDED_FOR'].split(',')[-1].strip()
##         else:
##             ip = None
    except (NoInteraction, ComponentLookupError):
        return None
