import cherrypy
import glob
import os.path
from base import IdString
import view

def request_tuple():
    mimeType = 'html'
    try:
        accept = cherrypy.request.headers['Accept']
        if 'application/json' in accept or accept == '*/*':
            mimeType = 'json'
    except KeyError:
        pass
    return cherrypy.request.method, mimeType

class Response(object):
    '_GET etc. methods can return this to pass back HTML output'
    def __init__(self, content):
        self.content = content
    def __call__(self):
        return self.content

class Redirect(Response):
    '_GET etc. methods can return this to force redirection to a URL'
    def __call__(self):
        return view.redirect(self.content)


class Collection(object):
    '''subclass this by adding the following kinds of methods:

    1. HTTP verbs, e.g. GET, POST, DELETE, as follows
    _POST(self, docID, **kwargs): create the specified document.
    _search(self, **kwargs): search the collection based on kwargs.

    2. representation generators for a specific verb and mimeType, e.g.
    get_html(self, doc, **kwargs): for a GET request,
    return HTML representation of the doc object.
    This will typically be a renderer of a Jinja2 template.
    '''
    def __init__(self, name, klass, templateEnv=None, templateDir='_templates',
                 docArgs=None, collectionArgs=None, **templateArgs):
        self.name = name
        self.klass = klass
        if docArgs is None:
            docArgs = {}
        self.docArgs = docArgs
        self.collectionArgs = collectionArgs
        if templateEnv: # load our template files
            self.bind_templates(templateEnv, templateDir, **templateArgs)

    def default(self, docID=None, *args, **kwargs):
        'process all requests for this collection'
        try:
            method, mimeType = request_tuple()
            if docID: # a specific document from this collection
                docID = IdString(docID) # implements proper cmp() vs. ObjectId
                invalidResponse = self.check_permission(method, docID, *args, 
                                                        **kwargs)
                if invalidResponse:
                    return invalidResponse
                if not args: # perform the request
                    return self._request(method, mimeType, docID, **kwargs)
                else: # pass request on to subcollection
                    try:
                        subcoll = getattr(self, args[0])
                    except AttributeError:
                        return view.report_error('no such subcollection: %s.%s'
                                                 % (self.name, args[0]), 404)
                    try:
                        parents = kwargs['parents'].copy()
                    except KeyError:
                        parents = {}
                    try:
                        parents[self.name] = self._GET(docID, parents=parents)
                    except KeyError:
                        return view.report_error('invalid ID: %s' % docID, 404,
                                                 """Sorry, the data ID %s that
    you requested does not exist in the database.
    Please check whether you have the correct ID.""" % docID)
                    kwargs['parents'] = parents # pass dict of parents
                    return subcoll.default(*args[1:], **kwargs)
            elif method == 'GET': # search the collection
                return self._request('search', mimeType, **kwargs)
            else:
                return view.report_error('REST does not permit collection-%s' 
                                         % method, 405)
        except Exception:
            return view.report_error('REST collection error', 500)
    default.exposed = True

    def _request(self, method, mimeType, *args, **kwargs):
        'dispatch to proper handler method, or return appropriate error'
        try: # do we support this method?
            action = getattr(self, '_' + method)
        except AttributeError:
            return view.report_error('%s objects do not allow %s' 
                                     % (self.name, method), 405)
        try: # execute the request
            o = action(*args, **kwargs)
        except KeyError:
            return view.report_error('Not found: %s: args=%s, kwargs=%s'
                   % (self.name, str(args), str(kwargs)), status=404,
                                     webMsg="""Sorry, the data that
you requested does not exist in the database.
Please check whether you have the correct ID or spelling.""")
        if isinstance(o, Response):
            return o() # send the redirect
        try: # do we support this mimeType?
            viewFunc = getattr(self, method.lower() + '_' + mimeType)
        except AttributeError:
            return view.report_error('%s objects cannot return %s' 
                                     % (self.name, mimeType), 406)
        try:
            return viewFunc(o, **kwargs)
        except Exception:
            return view.report_error('view function error', 500)

    def _GET(self, docID, parents={}, **kwargs):
        'default GET method'
        kwargs.update(self.docArgs)
        if not parents: # works with documents with unique ID
            return self.klass(docID, **kwargs)
        elif len(parents) == 1: # works with ArrayDocument
            return self.klass((parents.values()[0]._id, docID), **kwargs)
        else: # multiple parents
            return self.klass(docID, parents=parents, **kwargs)
            
    def bind_templates(self, env, dirpath, **kwargs):
        '''load template files of the form get_paper.html, bind as
        attrs of the form get_html'''
        for fname in glob.glob(os.path.join(dirpath,
                                            '*_%s.html' % self.name)):
            basename = os.path.basename(fname)
            template = env.get_template(basename)
            methodName = basename.split('_')[0] + '_html'
            v = view.TemplateView(template, self.name, **kwargs)
            setattr(self, methodName, v)
            
    def check_permission(self, method, docID, *args, **kwargs):
        'this authentication stub allows all requests'
        return False
