import logging
from pprint import pformat
from collections import namedtuple

from mongoquery import Query

log = logging.getLogger(__name__)
Change = namedtuple('Change', 'op lq obj')


class LiveQuery(object):

    def __init__(self, collection, qspec, callback=None):
        if callback is None:
            callback = self.log_entry
        self._collection = collection
        self._qspec = qspec
        self._callback = callback
        self.ns = '{}.{}'.format(collection.database.name, collection.name)
        self._query = Query(qspec)
        if '_id' in qspec:
            self._query_by_id = Query({'_id': qspec['_id']})
        else:
            self._query_by_id = None
        self._result_set = {}

    def log_entry(self, entry):
        log.info('CHANGE %s %s:\n%s', entry.op, entry.ns, pformat(entry.obj))

    def refresh(self, emit=False):
        old_result_ids = list(self._result_set)
        cursor = self._collection.find(self._qspec)
        results = {obj['_id']: obj for obj in cursor}
        if emit:
            for oid in old_result_ids:
                if oid not in results:
                    self._callback(Change('d', self, oid))
            for obj in results.values():
                self._callback(Change('a', self, obj))
        self._result_set = results

    def add(self, obj):
        self._result_set[obj['_id']] = obj
        self._callback(Change('a', self, obj))

    def discard(self, oid):
        obj = self._result_set.pop(oid, None)
        if obj:
            self._callback(Change('d', self, obj))

    def handle(self, entry):
        ns, op, o2, o, obj = (
            entry['ns'], entry['op'],
            entry.get('o2'), entry['o'], entry.get('obj'))
        if ns != self.ns:
            return
        if op == 'i':
            if self._query.match(o):
                return self.add(o)
        elif op == 'd':
            log.info('DISCARD because DELETE')
            return self.discard(o['_id'])
        elif entry['op'] == 'u':
            if self._query.match(obj):
                return self.add(obj)
            else:
                log.debug(
                    'DISCARD because NOMATCH\nentry: %r\nqspec: %r',
                    entry, self._qspec)
                return self.discard(o2['_id'])

@@ -172,16 +172,19 @@
  'op lq 
+ts 
 obj')%0A%0A%0A
@@ -1314,24 +1314,28 @@
 ef add(self,
+ ts,
  obj):%0A     
@@ -1404,32 +1404,36 @@
 hange('a', self,
+ ts,
  obj))%0A%0A    def 
@@ -1441,24 +1441,28 @@
 iscard(self,
+ ts,
  oid):%0A     
@@ -1558,24 +1558,28 @@
 e('d', self,
+ ts,
  obj))%0A%0A    
@@ -1610,16 +1610,20 @@
 %0A       
+ ts,
  ns, op,
@@ -1645,24 +1645,37 @@
 %0A           
+ entry%5B'ts'%5D,
  entry%5B'ns'%5D
@@ -1884,16 +1884,20 @@
 elf.add(
+ts, 
 o)%0A     
@@ -2108,16 +2108,20 @@
 elf.add(
+ts, 
 obj)%0A   
@@ -2307,16 +2307,20 @@
 discard(
+ts, 
 o2%5B'_id'

Include timestamp in change objects