package skadistats.clarity.two.runner;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import skadistats.clarity.two.framework.EventProvider;
import skadistats.clarity.two.framework.EventProviders;
import skadistats.clarity.two.framework.annotation.InvocationPointMarker;
import skadistats.clarity.two.framework.invocation.EventListener;
import skadistats.clarity.two.framework.invocation.InvocationPoint;
import skadistats.clarity.two.framework.invocation.InvocationPointType;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

public class Context {

    private static final Logger log = LoggerFactory.getLogger(Context.class);

    private Map<Class<?>, Object> processors = new HashMap<>();
    private Map<Class<? extends Annotation>, Set<EventListener>> processedEvents = new HashMap<>();

    public void addProcessor(Object processor) {
        requireProcessorClass(processor.getClass());
        processors.put(processor.getClass(), processor);
    }

    private void requireProcessorClass(Class<?> processorClass) {
        if (!processors.containsKey(processorClass)) {
            log.info("require processor {}", processorClass.getName());
            processors.put(processorClass, null);
            List<InvocationPoint> invocationPoints = findInvocationPoints(processorClass);
            for (InvocationPoint ip : invocationPoints) {
                if (ip instanceof EventListener) {
                    requireEventListener((EventListener) ip);
                }
            }
        }
    }

    private void requireEventListener(EventListener eventListener) {
        log.info("require event listener {}", eventListener.getEventClass());
        Set<EventListener> eventListeners = processedEvents.get(eventListener.getEventClass());
        if (eventListeners == null) {
            eventListeners = new HashSet<>();
            processedEvents.put(eventListener.getEventClass(), eventListeners);
        }
        eventListeners.add(eventListener);
        EventProvider provider = EventProviders.getEventProviderFor(eventListener.getEventClass());
        if (provider == null) {
            throw new RuntimeException("oops. no provider found for required listener");
        }
        requireProcessorClass(provider.getProviderClass());
    }

    private List<InvocationPoint> findInvocationPoints(Class<?> searchedClass) {
        List<InvocationPoint> eventListeners = new ArrayList<>();
        for (Method method : searchedClass.getMethods()) {
            for (Annotation methodAnnotation : method.getAnnotations()) {
                if (methodAnnotation.annotationType().isAnnotationPresent(InvocationPointMarker.class)) {
                    InvocationPointMarker marker = methodAnnotation.annotationType().getAnnotation(InvocationPointMarker.class);
                    InvocationPointType ipt = marker.value();
                    ipt.newInstance(methodAnnotation, searchedClass, method);
                }
            }
        }
        return eventListeners;
    }

    private void instantiateMissingProcessors() {
        for (Map.Entry<Class<?>, Object> entry : processors.entrySet()) {
            if (entry.getValue() == null) {
                try {
                    entry.setValue(entry.getKey().newInstance());
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public void initialize() {
        instantiateMissingProcessors();
        // callInitializers();
    }

    public void raise(Class<? extends Annotation> eventType, Object... params) {
    }

}

@@ -337,24 +337,94 @@
 ntListener;%0A
+import skadistats.clarity.two.framework.invocation.InitializerMethod;%0A
 import skadi
@@ -479,16 +479,16 @@
 nPoint;%0A
-
 import s
@@ -912,16 +912,112 @@
 Map%3C%3E();
+%0A    private Map%3CClass%3C? extends Annotation%3E, InitializerMethod%3E initializers = new HashMap%3C%3E();
 %0A%0A    pu
@@ -1681,16 +1681,143 @@
 r) ip);%0A
+                %7D else if (ip instanceof InitializerMethod) %7B%0A                    registerInitializer((InitializerMethod) ip);%0A
         
@@ -2607,32 +2607,614 @@
 lass());%0A    %7D%0A%0A
+    private void registerInitializer(InitializerMethod initializer) %7B%0A        log.info(%22register initializer %7B%7D%22, initializer.getEventClass());%0A        if (initializers.containsKey(initializer.getEventClass())) %7B%0A            log.warn(%22ignoring duplicate initializer for event %7B%7D found in %7B%7D, already provided by %7B%7D%22, initializer.getEventClass().getName(), initializer.getProcessorClass().getName(), initializers.get(initializer.getEventClass()).getProcessorClass().getName());%0A            return;%0A        %7D%0A        initializers.put(initializer.getEventClass(), initializer);%0A    %7D%0A%0A
     private List
@@ -3308,29 +3308,31 @@
 nPoint%3E 
-eventListener
+invocationPoint
 s = new 
@@ -3796,16 +3796,37 @@
         
+invocationPoints.add(
 ipt.newI
@@ -3873,16 +3873,17 @@
  method)
+)
 ;%0A      
@@ -3933,29 +3933,31 @@
  return 
-eventListener
+invocationPoint
 s;%0A    %7D

add initializer detection