package com.wideplay.warp.persist;

import com.google.inject.AbstractModule;
import com.google.inject.cglib.proxy.Proxy;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matcher;
import static com.google.inject.matcher.Matchers.annotatedWith;
import static com.google.inject.matcher.Matchers.any;
import com.wideplay.warp.persist.dao.Finder;
import org.aopalliance.intercept.MethodInterceptor;

import java.lang.reflect.AnnotatedElement;

/**
 * Base module for persistence strategies that holds a bunch
 * of utility methods for easier configuration.
 * @author Robbie Vanbrabant
 */
public abstract class AbstractPersistenceModule extends AbstractModule {
    protected abstract void configure();

    /**
     * Bind with an optional binding annotation instance or type, depending on the configuration.
     * A binding annotation needs to be specified when using two Hibernate configurations within the
     * same Injector.
     */
    protected <T> com.google.inject.binder.LinkedBindingBuilder<T> bindSpecial(PersistenceConfiguration config, java.lang.Class<T> tClass) {
        if (config.hasBindingAnnotation()) {
            return super.bind(tClass).annotatedWith(config.getBindingAnnotationClass());
        } else {
            return super.bind(tClass);
        }
    }
    
    protected void bindDynamicAccessors(PersistenceConfiguration config, MethodInterceptor finderInterceptor) {
        for (Class accessor : config.getAccessors()) {
            if (accessor.isInterface()) {
                bindSpecial(config, accessor).toInstance(Proxy.newProxyInstance(accessor.getClassLoader(),
                        new Class<?>[] { accessor }, new AopAllianceJdkProxyAdapter(finderInterceptor)));
            } else {
                //use cglib adapter to subclass the accessor (this lets us intercept abstract classes)
                bindSpecial(config, accessor).toInstance(com.google.inject.cglib.proxy.Enhancer.create(accessor,
                        new AopAllianceCglibAdapter(finderInterceptor)));
            }
        }
    }

    /**
     * Binds a finder interceptor with support for multiple modules. When the user specifies
     * an annotation to bind the module to, we match on the combination {@code @UserAnnotation @Finder}.
     */
    protected void bindFinderInterceptor(PersistenceConfiguration config, MethodInterceptor finderInterceptor) {
        Matcher<AnnotatedElement> annotatedWithFinder = annotatedWith(Finder.class);
        if (config.hasBindingAnnotation()) {
            bindInterceptor(any(), and(annotatedWithFinder, annotatedWith(config.getBindingAnnotationClass())), finderInterceptor);
        } else {
            bindInterceptor(any(), annotatedWithFinder, finderInterceptor);
        }
    }

    // TODO use
    private <T extends AnnotatedElement> Matcher<T> finderWithUnitIdenticalTo(final Class<T> annotation) {
        return new AbstractMatcher<T>() {
            public boolean matches(T t) {
                return annotatedWith(Finder.class).matches(t) && ((Finder)t).unit() == annotation;
            }
        };
    }

    /**
     * Matches on AND for the given matchers.
     */
    protected <T> Matcher<T> and(final Matcher<T> one, final Matcher<T> two) {
        return one.and(two);
    }
}

@@ -447,32 +447,22 @@
 reflect.
-AnnotatedElement
+Method
 ;%0A%0A/**%0A 
@@ -2401,93 +2401,8 @@
 ) %7B%0A
-        Matcher%3CAnnotatedElement%3E annotatedWithFinder = annotatedWith(Finder.class);%0A
     
@@ -2481,46 +2481,33 @@
 (), 
-and(annotatedWithFinder, annotatedWith
+finderWithUnitIdenticalTo
 (con
@@ -2538,17 +2538,16 @@
 Class())
-)
 , finder
@@ -2625,22 +2625,30 @@
 atedWith
+(
 Finder
+.class)
 , finder
@@ -2686,60 +2686,15 @@
     
-// TODO use%0A    private %3CT extends AnnotatedElement%3E
+private
  Mat
@@ -2698,17 +2698,22 @@
 Matcher%3C
-T
+Method
 %3E finder
@@ -2744,17 +2744,17 @@
 l Class%3C
-T
+?
 %3E annota
@@ -2796,17 +2796,22 @@
 Matcher%3C
-T
+Method
 %3E() %7B%0A  
@@ -2847,11 +2847,21 @@
 hes(
-T t
+Method method
 ) %7B%0A
@@ -2923,47 +2923,18 @@
 hes(
-t) && ((Finder)t).unit() == annotation;
+method) &&
 %0A   
@@ -2942,26 +2942,24 @@
         
-%7D%0A
         
 %7D;%0A    %7D
@@ -2954,186 +2954,91 @@
     
-%7D;%0A    %7D%0A%0A    /**%0A     * Matches on AND for the given matchers.%0A     */%0A    protected %3CT%3E Matcher%3CT%3E and(final Matcher%3CT%3E one, final Matcher%3CT%3E two) %7B%0A        return one.and(two)
+   method.getAnnotation(Finder.class).unit() == annotation;%0A            %7D%0A        %7D
 ;%0A  

Support for @Finder(unit=Blah.class), which we will only support for concrete types.