package org.grouplens.lenskit.core;

import org.grouplens.grapht.Injector;
import org.grouplens.grapht.graph.Edge;
import org.grouplens.grapht.graph.Graph;
import org.grouplens.grapht.graph.Node;
import org.grouplens.grapht.spi.CachedSatisfaction;
import org.grouplens.grapht.spi.Desire;
import org.grouplens.grapht.spi.InjectSPI;
import org.grouplens.grapht.spi.ProviderSource;

import javax.inject.Provider;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

/**
 * A Grapht injector that uses a precomputed graph.
 * @author Michael Ekstrand
 */
public class StaticInjector implements Injector {
    private InjectSPI spi;
    private Graph graph;
    private Node root;
    private Map<Node, Object> cache;

    /**
     * Create a new static injector. The node labelled with
     * {@code null} is the root node.
     * @param spi The inject SPI.
     * @param g The object graph.
     */
    public StaticInjector(InjectSPI spi, Graph g) {
        this(spi, g, g.getNode(null));
    }
    /**
     * Create a new static injector with a specified root node.
     * @param spi The inject SPI.
     * @param g The object graph.
     * @param rt The root node.
     */
    public StaticInjector(InjectSPI spi, Graph g, Node rt) {
        this.spi = spi;
        graph = g;
        root = rt;
        cache = new HashMap<Node, Object>();
    }

    @Override
    public <T> T getInstance(Class<T> type) {
        Desire d = spi.desire(null, type, true);
        Edge e = graph.getOutgoingEdge(root, d);

        if (e != null) {
            return type.cast(instantiate(e.getTail()));
        } else {
            // FIXME support retrieving deep nodes
            return null;
        }
    }

    /**
     * Instantiate a particular node in the graph.
     * @param node The node to instantiate.
     * @return The instantiation of the node.
     */
    public Object instantiate(Node node) {
        // FIXME Respect cache policies
        // FIXME Use memoizing providers
        Object instance = cache.get(node);
        if (instance == null) {
            CachedSatisfaction lbl = node.getLabel();
            assert lbl != null;
            Provider<?> provider = lbl.getSatisfaction().makeProvider(new DepSrc(node));
            instance = provider.get();
            cache.put(node, instance);
        }
        return instance;
    }

    @Override
    public <T> T getInstance(Annotation qualifier, Class<T> type) {
        throw new UnsupportedOperationException();
    }

    private class DepSrc implements ProviderSource {
        private Node node;

        private DepSrc(Node n) {
            this.node = n;
        }

        @Override
        public Provider<?> apply(Desire desire) {
            final Node dep = graph.getOutgoingEdge(node, desire).getTail();
            return new Provider() {
                @Override
                public Object get() {
                    return instantiate(dep);
                }
            };
        }
    }
}

@@ -464,37 +464,9 @@
 til.
-HashMap;%0Aimport java.util.Map
+*
 ;%0A%0A/
@@ -1622,81 +1622,1301 @@
     
-// FIXME support retrieving deep nodes%0A            return null;%0A        %7D
+Node node = findSatisfyingNode(type);%0A            if (node != null) %7B%0A                return type.cast(instantiate(node));%0A            %7D else %7B%0A                return null;%0A            %7D%0A        %7D%0A    %7D%0A%0A    /**%0A     * Find a node with a satisfaction for a specified type. Does a breadth-first%0A     * search to find the closest matching one.%0A     * @param type The type to look for.%0A     * @return A node whose satisfaction is compatible with %7B@code type%7D.%0A     * @review Decide how to handle qualifiers and contexts%0A     */%0A    private Node findSatisfyingNode(Class%3C?%3E type) %7B%0A        Queue%3CNode%3E work = new LinkedList%3CNode%3E();%0A        Set%3CNode%3E seen = new HashSet%3CNode%3E();%0A        work.add(root);%0A        seen.add(root);%0A        while (!work.isEmpty()) %7B%0A            Node node = work.remove();%0A            CachedSatisfaction lbl = node.getLabel();%0A            if (lbl != null && type.isAssignableFrom(lbl.getSatisfaction().getErasedType())) %7B%0A                return node;%0A            %7D%0A            for (Edge e: graph.getOutgoingEdges(node)) %7B%0A                Node nbr = e.getTail();%0A                if (!seen.contains(nbr)) %7B%0A                    seen.add(nbr);%0A                    work.add(nbr);%0A                %7D%0A            %7D%0A        %7D%0A        // got this far, no node%0A        return null;
 %0A   

Support deep extraction of types from injector (refs #183)