package com.github.ruediste.salta.standard.util;

import static com.google.common.base.Preconditions.checkState;

import java.util.Map;
import java.util.function.Supplier;

import net.sf.cglib.proxy.Dispatcher;
import net.sf.cglib.proxy.Enhancer;

import com.github.ruediste.salta.core.Binding;
import com.github.ruediste.salta.core.CoreDependencyKey;
import com.github.ruediste.salta.standard.ScopeImpl.ScopeHandler;
import com.google.common.collect.Maps;

/**
 * Scopes a single execution of a block of code. In contrast to
 * {@link SimpleScopeHandler}, this scope handler creates a proxy, which will
 * always delegate to the instance in the current scope. Apply this scope with a
 * try/finally block:
 * 
 * <pre>
 * <code>
 * 
 *   scopeHandler.enter();
 *   try {
 *     // create and access scoped objects
 *   } finally {
 *     scopeHandler.exit();
 *   }
 * </code>
 * </pre>
 *
 * Register it with
 * 
 * <pre>
 * <code>
 * SimpleProxyScopeHandler scopeHandler=new SimpleProxyScopeHandler();
 * bindScope(MyCustomScopeAnnotation.class, new ScopeImpl(scopeHandler));
 * bind(SimpleProxyScopeHandler.class).named("myScope").toInstance(scopeHandler);
 * </code>
 * </pre>
 * 
 * @author Jesse Wilson
 * @author Fedor Karpelevitch
 * @author Ruedi Steinmann
 */
public class SimpleProxyScopeHandler implements ScopeHandler {

    protected final ThreadLocal<Map<Binding, Object>> values = new ThreadLocal<>();
    protected String scopeName;

    public SimpleProxyScopeHandler(String scopeName) {
        this.scopeName = scopeName;
    }

    public void enter(Map<Binding, Object> instances) {
        checkState(values.get() == null, "Scope " + scopeName
                + "  is already in progress");
        values.set(instances);
    }

    public void enter() {
        checkState(values.get() == null, "Scope " + scopeName
                + "  is already in progress");
        values.set(Maps.newHashMap());
    }

    public Map<Binding, Object> getValueMap() {
        Map<Binding, Object> scopedObjects = values.get();
        if (scopedObjects == null) {
            throw new RuntimeException(
                    "Cannot access value map outside of scope " + scopeName);
        }
        return scopedObjects;
    }

    public void exit() {
        checkState(values.get() != null, "Scope " + scopeName
                + "is not in progress");
        values.remove();
    }

    @Override
    public Supplier<Object> scope(Supplier<Object> supplier, Binding binding,
            CoreDependencyKey<?> requestedKey) {

        // create the proxy right away, such that it can be reused
        // afterwards
        Object proxy = Enhancer.create(requestedKey.getRawType(),
                new Dispatcher() {

                    @Override
                    public Object loadObject() throws Exception {
                        return getScopedObjectMap(requestedKey)
                                .computeIfAbsent(binding, b -> supplier.get());
                    }
                });

        return () -> proxy;
    }

    protected Map<Binding, Object> getScopedObjectMap(
            CoreDependencyKey<?> requestedKey) {
        Map<Binding, Object> scopedObjects = values.get();
        if (scopedObjects == null) {
            throw new RuntimeException("Cannot access " + requestedKey
                    + " outside of scope " + scopeName);
        }
        return scopedObjects;
    }

}
@@ -1535,32 +1535,217 @@
 opeName;%0A    %7D%0A%0A
+    /**%0A     * Enter the scope with a predefined map of instances. Make sure to%0A     * synchronize access to the map (no two threads using the same map at the%0A     * same time)%0A     */%0A
     public void 
@@ -1771,25 +1771,24 @@
 Object%3E 
-instances
+valueMap
 ) %7B%0A    
@@ -1911,25 +1911,24 @@
 ues.set(
-instances
+valueMap
 );%0A    %7D
@@ -2102,32 +2102,166 @@
 hMap());%0A    %7D%0A%0A
+    /**%0A     * Return the current value map.%0A     * %0A     * @throws RuntimeException%0A     *             if no scope is active%0A     */%0A
     public Map%3CB

improve javadoc