# inequality_mixin.py

from google.appengine.ext import db
import re

class MultiInequalityMixin(object):
    """ Allows multiple inequality matches in a query.  Doesn't apply to GQL
    queries (yet) as I don't know how those work. """
    
    @classmethod
    def all(cls, **kwds):
        return MultiInequalityQuery(cls, **kwds)
    
    @classmethod
    def gql(cls, query_string, *args, **kwds):
        raise NotImplementedError("MultiInequalityGqlQuery isn't implemented yet")
        #return MultiInequalityGqlQuery(cls, query_string, *args, **kwds) 
    

# matches db._FILTER_REGEX, I hope.  Well, except for 'in' and plain '==?',
# which aren't inequalities.
prop_op_regex = re.compile(r'^\s*([^\s]+)\s+([<>]=?|!=)\s*$')

class MultiInequalityQuery(db.Query):
    
    """ Derives from db.Query, if you request inequalities on multiple 
    properties (normally not allowed by appengine) it does the first the
    usual way but then keeps the rest aside and 'post-filters' them. """
    
    def __init__(self, *args, **kwargs):
        self.ineq_prop = None
        self.ineq_post = []
        return super(MultiInequalityQuery, self).__init__(*args, **kwargs)

    
    def filter(self, prop_op, value):
        """ pass through most queries, except inequalities where this wouldn't
        be allowed. """
        
        def make_closure(prop, op, value):
            if (op == '>'):
                return lambda x: getattr(x, prop) > value
            elif (op == '<'):
                return lambda x: getattr(x, prop) < value
            elif (op == '>='):
                return lambda x: getattr(x, prop) >= value
            elif (op == '<='):
                return lambda x: getattr(x, prop) <= value
            elif (op == '!='):
                return lambda x: getattr(x, prop) != value
            else:
                raise NotImplementedError("Unknown op %s" % op)
         
        prop_op_match = prop_op_regex.match(prop_op)
        if prop_op_match:
            prop, op = prop_op_match.groups()
                        
            if self.ineq_prop not in (prop, None):
                self.ineq_post.append(make_closure(prop, op, value))
                return self
            self.ineq_prop = prop
            
        super(MultiInequalityQuery, self).filter(prop_op, value)
        return self
    
    
    def __iter__(self):
        """ Chain onto the Query.__iter__ but reject objects not passing
        all the postfilters """
        
        
        for x in super(MultiInequalityQuery, self).__iter__():
            try:
                if all([ f(x) for f in self.ineq_post ]):
                    yield x
            except AttributeError:
                pass
            
    
    def count(self, limit=None):
        """ Count the number of items out of the iterator.  Inefficient,
        but hey. """
        
        if not self.ineq_post:
            return super(MultiInequalityQuery, self).count(limit)
        
        c = 0
        for x in self:
            c = c + 1
            if limit is not None and c >= limit: break
        return c

    
    def fetch(self, limit, offset=0):
        """ Fetch a list of items.  Unlike Query.fetch, this just calls the
        iterator (a lot) so is unlikely to be fabulously efficient """
        
        if not self.ineq_post:
            return super(MultiInequalityQuery, self).fetch(limit, offset)

        c = 0
        things = []
        for x in self:
            if c >= offset: things.append(x)
            if c >= offset + limit: break
        return things
               
    # __getitem__ and get are already implemented in terms of fetch
    # so that should be okay.
    
