/*
 * LensKit, an open source recommender systems toolkit.
 * Copyright 2010-2014 LensKit Contributors.  See CONTRIBUTORS.md.
 * Work on LensKit has been funded by the National Science Foundation under
 * grants IIS 05-34939, 08-08692, 08-12148, and 10-17697.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.lenskit.data.dao;

import com.google.common.collect.Ordering;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import org.lenskit.data.entities.*;
import org.lenskit.util.io.ObjectStream;
import org.lenskit.util.io.ObjectStreams;

import javax.annotation.Nullable;
import java.util.*;

/**
 * A DAO backed by one or more collections of entities.
 */
public class EntityCollectionDAO extends AbstractDataAccessObject {
    private final Map<EntityType, EntityCollection> storage;

    EntityCollectionDAO(Map<EntityType, EntityCollection> data) {
        storage = data;
    }

    /**
     * Create a new DAO builder.
     * @return The entity collection DAO builder.
     */
    public static EntityCollectionDAOBuilder newBuilder() {
        return new EntityCollectionDAOBuilder();
    }

    /**
     * Create a new event collection DAO.
     * @param data The data to store in the DAO.
     * @return The DAO.
     */
    public static EntityCollectionDAO create(Entity... data) {
        return newBuilder().addEntities(data).build();
    }

    /**
     * Create a new event collection DAO.
     * @param data The data to store in the DAO.
     * @return The DAO.
     */
    public static EntityCollectionDAO create(Collection<Entity> data) {
        return newBuilder().addEntities(data).build();
    }


    @Override
    public Set<EntityType> getEntityTypes() {
        return storage.keySet();
    }

    @Override
    public LongSet getEntityIds(EntityType type) {
        EntityCollection entities = storage.get(type);
        if (entities != null) {
            return entities.idSet();
        } else {
            return LongSets.EMPTY_SET;
        }
    }

    @Nullable
    @Override
    public Entity lookupEntity(EntityType type, long id) {
        EntityCollection entities = storage.get(type);
        if (entities != null) {
            return entities.lookup(id);
        } else {
            return null;
        }
    }

    @Nullable
    @Override
    public <E extends Entity> E lookupEntity(EntityType type, long id, Class<E> view) {
        Entity entity = lookupEntity(type, id);
        if (entity == null) {
            return null;
        } else {
            return Entities.project(entity, view);
        }
    }

    @Override
    public ObjectStream<Entity> streamEntities(EntityType type) {
        Iterable<Entity> data = storage.get(type);
        if (data != null) {
            return ObjectStreams.wrap(data.iterator());
        } else {
            return ObjectStreams.empty();
        }
    }

    @Override
    public <E extends Entity> ObjectStream<E> streamEntities(EntityQuery<E> query) {
        EntityCollection data = storage.get(query.getEntityType());
        if (data == null) {
            return ObjectStreams.empty();
        }

        Collection<Entity> candidates;
        List<Attribute<?>> filters = query.getFilterFields();
        if (filters.isEmpty()) {
            candidates = data;
        } else {
            // optimize by trying to look up the first condition
            Attribute<?> f1 = filters.get(0);
            candidates = data.find(f1);
        }

        // FIXME don't filter if we don't need to
        ObjectStream<E> stream =
                ObjectStreams.transform(ObjectStreams.filter(ObjectStreams.wrap(candidates), query),
                                        Entities.projection(query.getViewType()));
        List<SortKey> sort = query.getSortKeys();
        if (sort.isEmpty()) {
            return stream;
        }

        // we must sort
        List<E> list = ObjectStreams.makeList(stream);
        Ordering<Entity> ord = null;
        for (SortKey k: sort) {
            if (ord == null) {
                ord = k.ordering();
            } else {
                ord = ord.compound(k.ordering());
            }
        }
        Collections.sort(list, ord);
        return ObjectStreams.wrap(list);
    }
}

@@ -3842,37 +3842,39 @@
     
-Collection%3CEntity%3E candidates
+ObjectStream%3CEntity%3E baseStream
 ;%0A  
@@ -3978,33 +3978,53 @@
         
-candidates = 
+baseStream = ObjectStreams.wrap(
 data
+)
 ;%0A      
@@ -4161,21 +4161,40 @@
     
-candidates = 
+baseStream = ObjectStreams.wrap(
 data
@@ -4202,16 +4202,17 @@
 find(f1)
+)
 ;%0A      
@@ -4217,193 +4217,106 @@
     
-%7D%0A%0A
     
-    // FIXME don't filter if we don't need to%0A        ObjectStream%3CE%3E stream =%0A                ObjectStreams.transform(ObjectStreams.filter(ObjectStreams.wrap(candidates)
+if (filters.size() %3E 1) %7B%0A                baseStream = ObjectStreams.filter(baseStream
 , query)
 ,%0A  
@@ -4311,17 +4311,17 @@
 , query)
-,
+;
 %0A       
@@ -4321,32 +4321,34 @@
             
+%7D%0A
         
             
@@ -4331,32 +4331,35 @@
   %7D%0A        
+%7D%0A%0A
         
             
@@ -4346,27 +4346,92 @@
         
-           
+ObjectStream%3CE%3E stream =%0A                ObjectStreams.transform(baseStream,
  Entitie

Fix FIXME in EntityCollectionDAO