text: # -*- coding: utf-8 -*-
  #------------------------------------------------------------------------------
  # file: $Id$
  # auth: Philip J Grabner <phil@canary.md>
  # date: 2015/04/02
  # copy: (C) Copyright 2015-EOT Canary Health, Inc., All Rights Reserved.
  #------------------------------------------------------------------------------
  
  from collections import OrderedDict
  
  import six
  import morph
  import sqlalchemy
  import sqlalchemy.orm
  
  from .decoder import SmartSort
  
  #------------------------------------------------------------------------------
  class Engine(object):
    '''
    The `Engine` performs the actual pagination work of narrowing down
    the result set to the desired window.
    '''
  
    #----------------------------------------------------------------------------
    def __init__(self, comparers={}, *args, **kw):
      super(Engine, self).__init__(*args, **kw)
      self.comparers = OrderedDict()
      if morph.isstr(comparers):
        comparers = [comparers]
      if morph.isseq(comparers) and len(comparers) > 0 \
          and not morph.isseq(comparers[0]):
        self.comparers.update((k, k) for k in comparers)
      else:
        self.comparers.update(comparers)
  
    #----------------------------------------------------------------------------
    def extend(self, *args, **kw):
      params = dict(comparers=self.comparers)
      for arg in args:
        params.update(arg)
      params.update(kw)
      return self.__class__(**params)
  
    #----------------------------------------------------------------------------
    def apply(self, p8n, result):
      '''
      Returns a two-element tuple of ``(narrowed_value, page_attributes)``.
      '''
      if isinstance(result, sqlalchemy.orm.Query):
        return self.apply_sqlalchemy_orm_query_query(p8n, result)
      if isinstance(result, (list, tuple)):
        return self.apply_list(p8n, result)
      try: result = tuple(result)
      except: pass
      try:
        implementation = getattr(self, 'apply_' + self.route(result))
      except:
        raise ValueError('No pagination available for %r' % (type(result),))
      return implementation(p8n, result)
  
    #----------------------------------------------------------------------------
    def route(self, value):
      ret = type(value)
      if ret.__module__ in ('__builtin__', 'builtin'):
        return ret.__name__.lower()
      return '.'.join([ret.__module__, ret.__name__]).lower()
  
    #----------------------------------------------------------------------------
    def sorters(self, p8n):
      if p8n.sort is not SmartSort:
        return p8n.sort
      return [(key, True) for key in self.comparers.keys()]
  
    #----------------------------------------------------------------------------
    def apply_list(self, p8n, value):
      sorters = self.sorters(p8n)
      def sortfunc(a, b):
        for meth, asc in sorters:
          spec = self.comparers[meth]
          if spec is None:
            continue
          elif six.callable(spec):
            try:
              ret = spec(p8n, value, a, b)
            except TypeError:
              ret = spec(a, b)
          elif morph.isstr(spec):
            try:
              ret = cmp(getattr(a, spec), getattr(b, spec))
            except AttributeError:
              ret = cmp(a[spec], b[spec])
          else:
            raise ValueError(
              'pagination sort comparer must be a callable,'
              ' an attribute, or an item key')
          if ret == 0:
            continue
          if asc:
            return ret
          if ret < 0:
            return 1
          return -1
        return 0
      value = sorted(value, cmp=sortfunc)
      count = len(value)
      if p8n.limit > 0:
        value = value[p8n.offset : p8n.offset + p8n.limit]
      else:
        value = value[p8n.offset : ]
      return (value, dict(count=count))
  
    #----------------------------------------------------------------------------
    def apply_sqlalchemy_orm_query_query(self, p8n, query):
      for meth, asc in self.sorters(p8n):
        spec = self.comparers[meth]
        if spec is None:
          continue
        elif six.callable(spec):
          query = spec(pagination=p8n, query=query, method=meth, ascending=asc)
        elif morph.isstr(spec):
          query = query.order_by(spec + ( '' if asc else ' DESC' ))
        else:
          query = query.order_by(spec if asc else sqlalchemy.desc(spec))
      count = query.count()
      query = query.offset(p8n.offset)
      if p8n.limit > 0:
        query = query.limit(p8n.limit)
      return (query, dict(count=count))
  
  #------------------------------------------------------------------------------
  # end of $Id$
  #------------------------------------------------------------------------------
  