# Copyright (c) 2004 Infrae. All rights reserved.
# See also LICENSE.txt

# Python
import urllib
import urlparse
# Zope
from AccessControl import Permissions, Unauthorized
# Pydavclient
import dav
# Railroad
from Products.Railroad import interfaces, errors, utils

# local modules
import rrxml

###

DC_NS = u'http://purl.org/dc/elements/1.1/'

class Service:
    """Bare Railroad service object, to be used in a Zope context
    """

    __implements__ = (interfaces.IRailroadService, )
    
    def __init__(
        self, repository_url, services_url, repository_name, client_name):
        
        if not repository_url.endswith('/'):
            raise errors.InvalidURLError('The repository URL should end with a slash')
        
        if not services_url.endswith('/'):
            raise errors.InvalidURLError('The services URL should end with a slash')

        self._repository_url = repository_url
        self._services_url = services_url
        self._repository_name = repository_name
        self._client_name = client_name
        self._unique_id_to_path = {}
        self._path_to_unique_id = {}

    # ACCESSORS

    def get_railroad_service(self):
        """Useful to get to this service through acquisistion
        """
        return self.aq_inner

    def repository_url(self):
        """Return the base URL of the Railroad repository.
        """
        return self._repository_url

    def services_url(self):
        """Return the base URL for the Railroad services.
        """
        return self._services_url

    def repository_name(self):
        """Return the name the storage was configured with.
        """
        return self._repository_name

    def client_name(self):
        """Return the name this CMS is registered with on the 
        Railroad server.
        """
        return self._client_name

    def generate_unique_id(self, type=None):
        """Return a UUID as 36 character string.

        The optional type parameter defines which type of
        UUID is generated. Possible values are:
          1. time - time based UUID (default)
          2. random - completely random UUID
          3. choose - server systems choice
        """
        # there is a service/uuid/ on railroad now
        # we'll use it for now
        # prepare the request
        uuid_url = urlparse.urljoin(self.services_url(), 'uuid') + '/'
        params = { 'type': 'time' }
        if type is not None:
            params['type'] = type.strip().lower()
        data = urllib.urlencode(params)
        # do request an read result,
        # which is one line with uuid in text form
        r = urllib.urlopen(uuid_url, data)
        uuid = r.read()
        return uuid
    
    def resource_url_for(self, proxy):
        """Return the repository URL for the given proxy object.
        
        This is the canonical way to get the URL to the resource.

        The url is constructed out of the configured repository URL
        and the path of the resource on the repository.
        
        Raises ProxyNotAssignedError if there is no resource assigned to 
        this proxy.
        """
        resource_path = proxy.resource_path()
        if resource_path is None:
            raise errors.ProxyNotAssignedError

        repository_url = self.repository_url()            
        url = urlparse.urljoin(repository_url, resource_path)
        return url
    
    def proxy_for_unique_id(self, unique_id):
        """Get the Railroad proxy object for this unique id.
        
        Raises ProxyNotFoundError if there not proxy for this unique id.
        """
        try:
            path = self._unique_id_to_path[unique_id]
            proxy = self.restrictedTraverse(path)
        except KeyError, e:
            raise errors.ProxyNotFoundError
        return proxy
    
    def fetch_properties_for(self, proxy, user=None, pw=None):
        """Return a dict with all WebDAV properties (in unicode)
        
        Raises ProxyNotAssignedError if there is no resource assigned to 
        this proxy.
        """
        # unassigned proxies cannot retrieve properties
        if not proxy.resource_path():
            raise errors.ProxyNotAssignedError
        
        url = self.resource_url_for(proxy)
        dfile = dav.DAVResource(url)
        dfile.connect()
        if user is None and pw is None:
            user, pw = self.get_user_pw(self.REQUEST)
            
        dfile._conn.set_auth(user, pw)
        dfile._conn._realm = self.repository_name()
        dfile.update()
        ret = dfile.get_all_properties()
        ret = utils.decodePropertyDict(ret)
        return ret

    def find_unowned_resources ( self ):
        """Return a list of resources that are currently not owned.

        Parameters:
          mime_type   : Only this mime-type will be considered.
                        If not set (None) the result will include all types.
          start_time,
          end_time    : The lastmodified date/time range to consider.

        The returned list elements are dicts with the following keys:
          url         : The path part of the url to this resource.
          mime-type   : The mime-type of this resource.
          size        : The size in bytes.
          lastmodified: The ISO timestamp (as string) of the modification.
        """
        # get base service url and append search path
        surl = urlparse.urljoin(self.services_url(), 'search/find_unowned')
        params = {}
        form = self.REQUEST.form
        mime_type = form.get('mime-type')
        start_time = form.get('start-time')
        end_time = form.get('end-time')
        if mime_type is not None:
            params['mime-type'] = mime_type
        if start_time is not None:
            params['start-date'] = start_time
        if end_time is not None:
            params['end-date'] = end_time
        # issue request and read returned data
        data = urllib.urlencode(params)
        req = urllib.urlopen(surl, data)
        data = req.read()
        # XML parsing to follow
        ret = rrxml.parse_search_result(data)
        return ret

    # AUTHORIZATION

    def authorize(self, id, method='GET'):
        """Check if the proxy with the given unique id allows the
        request specified by method.
        """
        # The authorization is done by calling the appropriate method on
        # the proxy object, which triggers Zopes security mechanism.
        try:  
            proxy = self.proxy_for_unique_id(id)
        except errors.ProxyNotFoundError, e:
            # XXX should we setStatus 404 when the proxy is not found?
            return self._error_response(e)
        maydo = utils.checkPermission(self._getPermissionForMethod(method),proxy)
        if not maydo:
            # XXX shouldn't that be an instance of the exception?
            res = self._error_response(Unauthorized)
            return res
        ret = repr(proxy) + '\n'
        self.REQUEST.RESPONSE.setStatus(200)
        ret = ret + 'ok\n'
        return ret

    def get_user_pw(self, request):
        """Return the user name and password the user is logged in as.
        
        Returns two empty string in case the user is not logged in.
        """
        try:
            user, pw = request._authUserPW()
        except TypeError:
            user, pw = ('', '')
        return (user, pw)
    
    def _getPermissionForMethod(self,method):
        """Returns the CMS permission that is associated 
        with a certain RR method
        """
        return Permissions.view
    
    def _error_response(self, exception):
        request = self.REQUEST
        response = request.RESPONSE
        response.setStatus(401)
        realm = utils.get_realm()
        response.setHeader('WWW-Authenticate', 'basic realm="%s"' % realm, 1)
        ret = repr(exception) + '\n' + 'failed\n'
        return ret
          
    # MANIPULATORS

    def register_proxy(self, proxy):
        """Register the given proxy object with this service instance.

        The registration allow the mapping between the unique id of the
        proxy object and the object itself.
        
        See proxy_for_unique_id.
        """
        self._p_changed = 1
        unique_id = proxy.unique_id()
        path = proxy.getPhysicalPath()
        self._path_to_unique_id[path] = unique_id
        self._unique_id_to_path[unique_id] = path

    def unregister_proxy(self, proxy):
        """Unregister (forget) the given proxy object.

        The mapping for the proxy is removed from this service instance.
        """
        self._p_changed = 1
        unique_id = proxy.unique_id()
        path = proxy.getPhysicalPath()
        del self._path_to_unique_id[path]
        del self._unique_id_to_path[unique_id]
    
    def set_properties_for(self, proxy, props):
        """Set WebDAV properties where props is a dict with unicode
        keys and value.

        The keys are tuples in the form of (name, namespace-uri).
        
        Raises ProxyNotAssignedError if there no resource assigned
        to this proxy.
        """
        if not proxy.resource_path():
            raise errors.ProxyNotAssignedError

        url = self.resource_url_for(proxy)
        dfile = dav.DAVResource(url)
        dfile.connect()
        user, pw = self.get_user_pw(self.REQUEST)
        dfile._conn.set_auth(user, pw)
        dfile._conn._realm = self.repository_name()
        props = utils.encodePropertyDict(props)
        res = dfile.set_properties(props)
        return
