"""ReSTful APIs for arbitrary models

This module defines a lightweight ReSTful API manager
for arbitrary model objects.

It is primarily for our Javascript client pages to talk to.
We tried bigger projects like django-restapi, but didn't like them.
"""

from django.http import HttpResponse, Http404, HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.utils import simplejson
from django.core import serializers

class RestInterface:
    """Hook up a ReST interface to manage a list of classes.
    
    
    """
    def __init__(self, classes):
        self.klassMap = {}
        for klass in classes:
            lname = klass.__name__.lower()
            self.klassMap[lname] = klass
            
    def root(self, request, url):
        """Handles main URL routing for the Restful app.

        `url` is the remainder of the URL -- e.g. 'comments/comment/'.
        """
        #if request.method == 'GET' and not request.path.endswith('/'):
        #    return HttpResponseRedirect(request.path + '/')
        
        
        if url == 'jeditable_save/':
            return self.jeditable_save(request)
        
        else:
            #default handler maps next part onto a model class.
            parts = url.lower().split('/')
            if parts[-1] == '':
                parts = parts[:-1]
            target = parts[0]
            targetClass = self.klassMap.get(target, None)
            if targetClass is None:
                raise Http404("no object registered called '%s'.  Registered classes are %s" % (target, str(self.klassMap.keys())))
            if len(parts) == 1: 
                return self.rest_api_collection(request, targetClass)
            else:
                return self.rest_api_instance(request, targetClass, parts[1])
                
        
        msg = """
        ReportLab ReST API - debug
        url = %s
        request.path = %s
        """ % (url, request.path)
        return HttpResponse(msg, mimetype='text/plain')
        
    def __call__(self, request, url):
        "The object can be called by urlconf"
        return self.root(request, url)
        
        
    def jeditable_save(self, request):
        
        """Generic handler to "update something" as needed by jEditable
        
        2 parameters are submitted by jeditable:
        id and value.  The id should encode the type
        of object, id and the attribute to edit e.g.
           tag:ace:description
        or
            image:RLIMG_blahblah.gif:description
        """
        
        id = request.POST["id"]
        value = request.POST["value"]
        #print 'jeditable_save %s = %s' % (id, value)
        table, object_id, attribute = id.split(":")

        model = self.klassMap.get(table, None)
        if not model:
            raise Http404("No model named %s" % table)
                    
        object = model.objects.get(id=object_id)
        value = value.strip()
        #print 'got the object'
        try:
            setattr(object, attribute, value)
            object.save()
            #print 'saved it'
        except Exception, e:
            #print e
            return HttpResponse('failed: %s' % e)
        return HttpResponse(value)



    def rest_api_collection(self, request, model):
        """This is for urls routed to the 'collection'
        
        e.g. /imadj/api/image/ or /imadj/api/tag/
        
        This should handle querying and creation.
        """
        
        app_label = model._meta.app_label
        class_name = model.__name__.lower()
        
        if request.method == 'GET':
            qs = model.objects.all()

            #you can use any Django queryset parameters to filter,
            #provided the class you are querying supports them.
            #The control parameters below do what they see.

            CONTROL_PARAMS = ['__limit','__order_by']

            limit = request.GET.get('__limit', None)
            if limit:
                limit = int(limit)
        
            #supply comma-delimited like
            #   __order_by=-date_modified,description
            order_by = request.GET.get('__order_by', None)
            if order_by:
                order_by = order_by.split(',')
            
            params = {}
            for k, v in request.GET.items():
                #the request dict seems to be all unicode, but python keyword args
                #need to be 8-bit 
                
                k = k.encode('utf8')
                if k in CONTROL_PARAMS:  #
                    continue
                params[k] = v
            if params:
                qs = qs.filter(**params)
            
            #default order should be most recently updated first
            if order_by:
                qs = qs.order_by(*order_by)
            
            if limit:
                qs = qs[0:limit]
            
            
            data = serializers.serialize("json", qs, ensure_ascii=False, sort_keys=True, indent=4)
            #should use application/json but it makes debugging a PITA.
            #see if jquery swallows it like this..
            return HttpResponse(content=data,
                                mimetype='text/plain')

        elif request.method == 'POST':
            #POST to a collection should add, if they have permission.
            
            
            #Andy - this needs testing
            if not request.user.has_perm('%s.add_%s' % (app_label, class_name)):
                return HttpResponse("You do not have rights to add %s." % class_name, status=403)

            
            
            params = {}
            for k in request.POST.keys():
                if not k.startswith('_'):  #use this for a submit button
                    params[str(k)] = request.POST[k]
            instance = model(**params)
            instance.save()
            instance_url = '%s/%s/' % (request.path, instance.id)
            #return HttpResponse('Created instance of %s with id %s' % (class_name, instance.id))
            return HttpResponseRedirect(instance_url)
        
        
    def rest_api_instance(self, request, model, id):
        """This is for urls routed to the 'instance'
        
        e.g. /imadj/api/tag/football
        
        This can be used for update and delete.
        It attempts to check the admin permissions for this
        object, if present.
        """
        instance = get_object_or_404(model, pk=id)

        app_label = model._meta.app_label
        class_name = model.__name__.lower()  #used in responses


        if request.method == 'GET':
            #cool idea - get params as queryset filters...
            qs = model.objects.filter(pk=id)
            data = serializers.serialize("json", qs, ensure_ascii=False, sort_keys=True, indent=4)
            #should use application/json but it makes debugging a PITA.
            #see if jquery swallows it like this..
            return HttpResponse(content=data,
                                mimetype='text/plain')
        
        elif request.method == 'POST':
            #try to update object with each parameter
            #in some cases, the string conversions might not work;
            #ideally we'd use form_for_model to check, but also
            #do the thing below for any attributes not covered

            #Andy - this needs testing - attempt to use admin permissions
            if not request.user.has_perm('%s.change_%s' % (app_label, class_name)):
                return HttpResponse("You do not have rights to change %s." % class_name, status=403)

            for key, value in request.POST.items():
                setattr(instance, key, value)
            instance.save()
            return HttpResponse("updated", mimetype='text/plain')

        elif request.method == 'DELETE':
            if not request.user.has_perm('%s.delete_%s' % (app_label, class_name)):
                return HttpResponse("You do not have rights to delete %s." % class_name, status=403)


            instance.delete()
            return HttpResponse("deleted %s %s" % (class_name, id), mimetype='text/plain')

