/**
 * Copyright (C) 2013 Antonin Stefanutti (antonin.stefanutti@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.stefanutti.metrics.cdi;

import com.codahale.metrics.*;
import com.codahale.metrics.Metric;
import com.codahale.metrics.annotation.ExceptionMetered;
import com.codahale.metrics.annotation.Gauge;
import com.codahale.metrics.annotation.Metered;
import com.codahale.metrics.annotation.Timed;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.*;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class MetricsExtension implements Extension {

    private boolean hasMetricRegistry;

    private final Map<Bean<?>, AnnotatedMember<?>> metrics = new HashMap<Bean<?>, AnnotatedMember<?>>();

    // TODO: give meaningful method names
    private <X> void processAnnotatedType(@Observes @WithAnnotations({ExceptionMetered.class, Gauge.class, Metered.class, Timed.class}) ProcessAnnotatedType<X> pat) {
        boolean gauge = false;
        Set<AnnotatedMethod<? super X>> decoratedMethods = new HashSet<AnnotatedMethod<? super X>>(4);
        for (AnnotatedMethod<? super X> method : pat.getAnnotatedType().getMethods()) {
            if (method.isAnnotationPresent(ExceptionMetered.class))
                decoratedMethods.add(getAnnotatedMethodDecorator(method, ExceptionMeteredBindingLiteral.INSTANCE));
            if (method.isAnnotationPresent(Metered.class))
                decoratedMethods.add(getAnnotatedMethodDecorator(method, MeteredBindingLiteral.INSTANCE));
            if (method.isAnnotationPresent(Timed.class))
                decoratedMethods.add(getAnnotatedMethodDecorator(method, TimedBindingLiteral.INSTANCE));
            if (method.isAnnotationPresent(Gauge.class))
                gauge = true;
        }
        AnnotatedType<X> annotatedType = new AnnotatedTypeDecorator<X>(pat.getAnnotatedType(), MetricsBindingLiteral.INSTANCE);
        // FIXME: remove when OWB will be CDI 1.1 compliant
        if (gauge /*decoratedMethods.isEmpty()*/)
            pat.setAnnotatedType(annotatedType);
        else if (!decoratedMethods.isEmpty())
            pat.setAnnotatedType(new AnnotatedTypeMethodDecorator<X>(annotatedType, decoratedMethods));
    }

    // TODO: use typed observers
    private <X> void processBean(@Observes ProcessBean<X> pb) {
        if (pb.getBean().getTypes().contains(MetricRegistry.class))
            hasMetricRegistry = true;
    }

    private <X> void processProducerField(@Observes ProcessProducerField<? extends Metric, X> ppf) {
        metrics.put(ppf.getBean(), ppf.getAnnotatedProducerField());
    }

    private <X> void processProducerMethod(@Observes ProcessProducerMethod<? extends Metric, X> ppm) {
        // Skip the Metrics CDI alternatives
        if (!ppm.getBean().getBeanClass().equals(MetricProducer.class))
            metrics.put(ppm.getBean(), ppm.getAnnotatedProducerMethod());
    }

    private void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager manager) {
        if (!hasMetricRegistry)
            abd.addBean(new MetricRegistryBean(manager));
    }

    private void afterDeploymentValidation(@Observes AfterDeploymentValidation adv, BeanManager manager) {
        MetricRegistry registry = getBeanInstance(manager, MetricRegistry.class);
        MetricNameHelper helper = getBeanInstance(manager, MetricNameHelper.class);

        for (Map.Entry<Bean<?>, AnnotatedMember<?>> metric : metrics.entrySet()) {
            Metric reference = (Metric) manager.getReference(metric.getKey(), metric.getValue().getBaseType(), manager.createCreationalContext(null));
            registry.register(helper.metricName(metric.getValue()), reference);
        }

        // Let's clear the collected metric producers
        metrics.clear();
    }

    private static <X> AnnotatedMethod<X> getAnnotatedMethodDecorator(AnnotatedMethod<X> annotatedMethod, Annotation annotated) {
        return new AnnotatedMethodDecorator<X>(annotatedMethod, annotated);
    }

    @SuppressWarnings("unchecked")
    private static <T> T getBeanInstance(BeanManager manager, Class<T> clazz) {
        Bean<?> bean = manager.resolve(manager.getBeans(clazz, AnyLiteral.INSTANCE));
        return (T) manager.getReference(bean, clazz, manager.createCreationalContext(null));
    }
}
@@ -1356,50 +1356,8 @@
 );%0A%0A
-    // TODO: give meaningful method names%0A
     
@@ -1376,24 +1376,31 @@
 void process
+Metrics
 AnnotatedTyp
@@ -2809,36 +2809,131 @@
     
-// TODO: use typed observers
+private void processMetricRegistryBean(@Observes ProcessBean%3CMetricRegistry%3E pb) %7B%0A        hasMetricRegistry = true;%0A    %7D%0A
 %0A   
@@ -2932,36 +2932,32 @@
   %7D%0A%0A    private
- %3CX%3E
  void processBea
@@ -2957,40 +2957,89 @@
 cess
-Bean(@Observes ProcessBean%3CX%3E pb
+MetricRegistryProducerField(@Observes ProcessProducerField%3CMetricRegistry, ?%3E ppf
 ) %7B%0A
@@ -3050,72 +3050,152 @@
     
-if (pb.getBean().getTypes().contains(MetricRegistry.class))%0A    
+hasMetricRegistry = true;%0A    %7D%0A%0A    private void processMetricRegistryProducerMethod(@Observes ProcessProducerMethod%3CMetricRegistry, ?%3E ppm) %7B%0A
     
@@ -3234,36 +3234,32 @@
   %7D%0A%0A    private
- %3CX%3E
  void processPro
@@ -3247,32 +3247,38 @@
 ate void process
+Metric
 ProducerField(@O
@@ -3320,25 +3320,25 @@
 nds Metric, 
-X
+?
 %3E ppf) %7B%0A   
@@ -3413,36 +3413,32 @@
   %7D%0A%0A    private
- %3CX%3E
  void processPro
@@ -3430,24 +3430,30 @@
 void process
+Metric
 ProducerMeth
@@ -3505,17 +3505,17 @@
 Metric, 
-X
+?
 %3E ppm) %7B
@@ -3798,32 +3798,223 @@
 ager manager) %7B%0A
+        // TODO: should be possible from the CDI spec though Weld is returning an empty set at that stage%0A        //if (manager.getBeans(MetricRegistry.class, AnyLiteral.INSTANCE).isEmpty())%0A
         if (!has

Improve generic observers