from collections import defaultdict

__all__ = ['Eagerload']


def fieldgetter(item):
    names = item.split('.')

    def proxy(doc):
        for name in names[:-1]:
            doc = getattr(doc, name)

        return doc

    return proxy


class Meta(object):
    def __init__(self, field):
        self.key = field.get_key()
        self.depth = field.get_key().count('.')

        if field.__class__.__name__ == 'ListField':
            self.field = field.field
            self.name = field.name + '_'
            self.multi = True
        else:
            self.field = field
            self.name = field.name + '_id'
            self.multi = False


class Eagerload(object):
    def __init__(self, only=None):
        self.only = only
        self.fields = []
        self.documents = []
        self.document_cls = None

        self.single = {}
        self.multi = {}

    def add_field(self, field):
        meta = Meta(field)

        if not self.document_cls:
            self.document_cls = meta.field.document_cls
        else:
            assert self.document_cls is meta.field.document_cls, 'All fields must load the same document.'

        self.fields.append(meta)

        return self

    def add_documents(self, documents):
        if isinstance(documents, list):
            for document in documents:
                self.add_document(document)
        else:
            self.add_document(documents)

        return self

    def add_document(self, document):
        for meta in self.fields:
            if not meta.depth:
                self._add_document(meta, document)
            else:
                try:
                    documents = fieldgetter(meta.key)(document)

                    if isinstance(documents, list):
                        for document in documents:
                            self._add_document(meta, document)
                    else:
                        self._add_document(meta, document)
                except AttributeError:
                    pass

        return self

    def _add_document(self, meta, document):
        if getattr(document, meta.name, None):
            self.documents.append((meta, document))

    def flush(self):
        if not len(self.documents):
            return

        mapping = defaultdict(list)

        for meta, document in self.documents:
            id_values = getattr(document, meta.name)

            if id_values:
                if meta.multi:
                    for i, id_value in enumerate(id_values):
                        mapping[id_value].append((document._data[meta.field.owner.name], None, i))
                else:
                    mapping[id_values].append((document._data, meta.field.name, -1))

        if mapping:
            cls = self.document_cls

            ids = mapping.keys()

            if len(ids) == 1:
                values = cls.objects.filter(cls.id == ids[0])
            else:
                values = cls.objects.filter(cls.id.in_(ids))

            if self.only is not None:
                values = values.only(*self.only)

            for value in values:
                for data, name, i in mapping[value._data['id']]:
                    if name is None:
                        data[i] = value
                    else:
                        data[name] = value
@@ -2270,34 +2270,18 @@
     
-mapping = defaultdict(list
+ids = set(
 )%0A%0A 
@@ -2337,25 +2337,22 @@
         
-id_values
+ref_id
  = getat
@@ -2391,25 +2391,22 @@
      if 
-id_values
+ref_id
 :%0A      
@@ -2454,253 +2454,83 @@
     
-for i, id_value in enumerate(id_values):%0A                        mapping%5Bid_value%5D.append((document._data%5Bmeta.field.owner.name%5D, None, i))%0A                else:%0A                    mapping%5Bid_values%5D.append((document._data, meta.field.name, -1)
+ids %7C= set(ref_id)%0A                else:%0A                    ids.add(ref_id
 )%0A%0A 
@@ -2539,23 +2539,19 @@
      if 
-mapping
+ids
 :%0A      
@@ -2585,42 +2585,8 @@
 ls%0A%0A
-            ids = mapping.keys()%0A%0A
     
@@ -2672,11 +2672,14 @@
  ids
-%5B0%5D
+.pop()
 )%0A  
@@ -2753,13 +2753,19 @@
 in_(
+list(
 ids))
+)
 %0A%0A  
@@ -2850,16 +2850,56 @@
 .only)%0A%0A
+            documents = self.documents%0A%0A
         
@@ -2923,16 +2923,48 @@
 values:%0A
+                remaining = %5B%5D%0A%0A
         
@@ -2979,90 +2979,223 @@
 for 
-da
+me
 ta, 
-name, i in mapping%5Bvalue._data%5B'id'%5D%5D:%0A                    if name is None:%0A
+document in documents:%0A                    if meta.multi:%0A                        try:%0A                            data = document._data%5Bmeta.field.owner.name%5D%0A%0A                            while True:%0A        
     
@@ -3219,17 +3219,36 @@
    data%5B
-i
+data.index(value.id)
 %5D = valu
@@ -3273,52 +3273,359 @@
     
-else:%0A                        data%5Bname%5D = value
+    except (ValueError, IndexError, KeyError):%0A                            pass%0A                    elif document._data.get(meta.field.name) == value._data%5B'id'%5D:%0A                        document._data%5Bmeta.field.name%5D = value%0A                        continue%0A%0A                    remaining.append((meta, document))%0A%0A                documents = remaining%0A

fix conjure...