package org.rocket.network.guice;

import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.multibindings.Multibinder;
import org.rocket.guice.RocketModule;
import org.rocket.network.*;

import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;

import static java.util.Objects.requireNonNull;

public abstract class ControllerModule extends RocketModule {

    private final Class<? extends Annotation> controllerAnnotation;

    private Multibinder<Object> controllerMultibinder;
    private final Set<Key<?>> boundPropKeys = new HashSet<>();

    protected ControllerModule(Class<? extends Annotation> controllerAnnotation) {
        this.controllerAnnotation = requireNonNull(controllerAnnotation, "controllerAnnotation");
    }

    protected ControllerModule() {
        this(Controller.class);
    }

    @Override
    protected void before() {
        controllerMultibinder = Multibinder.newSetBinder(binder(), Object.class, controllerAnnotation);
    }

    @Override
    protected void after() {
        controllerMultibinder = null;
    }

    public interface Builder {
        void to(Class<?> klass);
    }
    protected Builder newController() {
        return this::newController;
    }

    protected void newController(Class<?> klass) {
        controllerMultibinder.addBinding().to(klass);

        for (Field field : klass.getFields()) {
            if (!field.isAnnotationPresent(Inject.class) && !field.isAnnotationPresent(com.google.inject.Inject.class)) {
                continue;
            }

            if (!Prop.class.isAssignableFrom(field.getType())) {
                continue;
            }

            newProp(keyFor(field));
        }
    }

    @SuppressWarnings("unchecked")
    protected void newProp(Key<?> key) {
        if (boundPropKeys.contains(key)) {
            return;
        }
        boundPropKeys.add(key);

        Provider provider = new PropProvider(getProvider(NetworkClient.class), asPropId(key));
        bind(wrap(key, Prop.class)).toProvider(provider);
        bind(wrap(key, MutProp.class)).toProvider(provider);
    }

    private Key<?> keyFor(Field field) {
        // find the first eligible annotation other than @Inject
        // com.google.inject.Key only supports a single marker annotation
        // a contrario of PropId which can handle any number of annotations
        Annotation acc = null;
        for (Annotation ann : field.getAnnotations()) {
            if (Inject.class.isAssignableFrom(ann.annotationType())) {
                continue;
            }
            if (com.google.inject.Inject.class.isAssignableFrom(ann.annotationType())) {
                continue;
            }

            acc = ann;
            break;
        }

        if (acc != null) {
            return Key.get(field.getGenericType(), acc);
        }

        return Key.get(field.getGenericType());
    }

    private Key<?> wrap(Key<?> key, Class<?> wrapper) {
        throw new Error("todo");
    }

    private PropId asPropId(Key<?> key) {
        throw new Error("todo");
    }

    private static class PropProvider implements Provider {
        private final Provider<NetworkClient> client;
        private final PropId pid;

        private PropProvider(Provider<NetworkClient> client, PropId pid) {
            this.client = client;
            this.pid = pid;
        }

        @Override
        public Object get() {
            return client.get().getProp(pid);
        }
    }
}

@@ -141,24 +141,61 @@
 ultibinder;%0A
+import com.google.inject.util.Types;%0A
 import org.r
@@ -253,36 +253,49 @@
 .*;%0A
-%0A
 import 
-javax.inject.Inject;
+org.rocket.network.props.PropIds;%0A
 %0Aimp
@@ -356,21 +356,20 @@
 reflect.
-Field
+Type
 ;%0Aimport
@@ -1477,372 +1477,8 @@
 ss);
-%0A%0A        for (Field field : klass.getFields()) %7B%0A            if (!field.isAnnotationPresent(Inject.class) && !field.isAnnotationPresent(com.google.inject.Inject.class)) %7B%0A                continue;%0A            %7D%0A%0A            if (!Prop.class.isAssignableFrom(field.getType())) %7B%0A                continue;%0A            %7D%0A%0A            newProp(keyFor(field));%0A        %7D
 %0A   
@@ -1894,328 +1894,252 @@
   pr
-ivate Key%3C?%3E keyFor(Field field) %7B%0A        // find the first eligible annotation other than @Inject
+otected void newProp(Class%3C?%3E klass) %7B%0A        newProp(Key.get(klass));
 %0A    
+%7D%0A%0A
     
-// com.google.inject.Key only supports a single marker annotation%0A        // a contrario of PropId which can handle any number of annotations%0A        Annotation acc = null
+protected void newProp(Class%3C?%3E klass, Annotation ann) %7B%0A        newProp(Key.get(klass, ann))
 ;%0A    
+%7D%0A%0A
     
-for (Annotation ann : field.get
+protected void newProp(Class%3C?%3E klass, Class%3C? extends 
 Anno
@@ -2148,11 +2148,13 @@
 tion
-s()
+%3E ann
 ) %7B%0A
@@ -2165,375 +2165,347 @@
     
- 
-   if (Inject.class.isAssignableFrom(ann.annotationType())) %7B%0A                continue;%0A            %7D%0A            if (com.google.inject.Inject.class.isAssignableFrom(ann.annotationType())) %7B%0A                continue;%0A            %7D%0A%0A            acc = ann;%0A            break;%0A        %7D%0A%0A        if (acc != null) %7B%0A            return Key.get(field.getGeneric
+newProp(Key.get(klass, ann));%0A    %7D%0A%0A    private Key%3C?%3E wrap(Key%3C?%3E key, Type wrapper) %7B%0A        return key.ofType(Types.newParameterizedType(wrapper, key.getTypeLiteral().getType()));%0A    %7D%0A%0A    private PropId asPropId(Key%3C?%3E key) %7B%0A        if (key.getAnnotation() == null) %7B%0A            return PropIds.type(key.getTypeLiteral().get
 Type()
-, acc
 );%0A 
@@ -2505,33 +2505,32 @@
 e());%0A        %7D%0A
-%0A
         return K
@@ -2532,216 +2532,138 @@
 urn 
-Key.get(field.getGenericType());
+PropIds.of(
 %0A    
-%7D%0A%0A
+ 
     
-private Key%3C?%3E wrap(Key%3C?%3E key, Class%3C?%3E wrapper) %7B%0A        throw new Error(%22todo%22);
+   PropIds.type(key.getTypeLiteral().getType()),
 %0A    
-%7D%0A%0A
     
-private PropId asPropId(Key%3C?%3E key) %7B%0A        throw new Error(%22todo%22
+    PropIds.annotation(key.getAnnotation())%0A        
 );%0A 

Bring back the Prop, dammit!