/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.example.cdi.metrics;

import java.util.concurrent.TimeUnit;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.RatioGauge;
import com.codahale.metrics.Slf4jReporter;
import com.codahale.metrics.annotation.Metric;
import io.astefanutti.metrics.cdi.MetricsConfiguration;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.cdi.ContextName;
import org.apache.camel.component.metrics.MetricsConstants;
import org.apache.camel.management.event.CamelContextStartedEvent;

/**
 * This example registers the following meters into the Metrics registry:
 * <ul>
 *     <li>{@code attempt}</code>: meters the number of calls
 *     made to the service to process incoming events</li>
 *     <li>{@code error}</code>: meters the number of errors
 *     corresponding to the number of events that haven't been processed</li>
 *     <li>{@code generated}</code>: meters the number of events to be processed</li>
 *     <li>{@code redelivery}</code>: meters the number of retries
 *     made to process the events</li>
 *     <li>{@code success}</code>: meters the number of events successfully processed</li>
 * </ul>
 * The invariant being: {@code attempt = redelivery - success - error}.
 * <p> In addition, a ratio gauge {@code success-ratio = success / generated} is registered.
 *
 */
class Application {

    @ContextName("camel-example-metrics-cdi")
    static class MetricsRoute extends RouteBuilder {

        @Override
        public void configure() {
            onException()
                .handled(true)
                .maximumRedeliveries(2)
                .logStackTrace(false)
                .logExhausted(false)
                .log(LoggingLevel.ERROR, "Failed processing ${body}")
                .to("metrics:meter:redelivery?mark=2")
                // The 'error' meter
                .to("metrics:meter:error");

            from("timer:stream?period=1000")
                .routeId("unreliable-service")
                .setBody(header(Exchange.TIMER_COUNTER).prepend("event #"))
                .log("Processing ${body}...")
                // The 'generated' meter
                .to("metrics:meter:generated")
                // The 'attempt' meter via @Metered interceptor
                .bean(UnreliableService.class)
                .filter(header(Exchange.REDELIVERED))
                    .log(LoggingLevel.WARN, "Processed ${body} after ${header.CamelRedeliveryCounter} retries")
                    .setHeader(MetricsConstants.HEADER_METER_MARK, header(Exchange.REDELIVERY_COUNTER))
                    // The 'redelivery' meter
                    .to("metrics:meter:redelivery")
                .end()
                .log("Successfully processed ${body}")
                // The 'success' meter
                .to("metrics:meter:success");
        }
    }

    @Produces
    @Metric(name = "success-ratio")
    // Register a custom gauge that's the ratio of the 'success' meter on the 'generated' meter
    // TODO: use a lambda expression and parameter names when Java 8 is a pre-requisite
    Gauge<Double> successRatio(@Metric(name = "success") final Meter success,
                               @Metric(name = "generated") final Meter generated) {
        return new RatioGauge() {
            @Override
            protected Ratio getRatio() {
                return Ratio.of(success.getOneMinuteRate(), generated.getOneMinuteRate());
            }
        };
    }

    @Produces
    @Singleton
    Slf4jReporter reporter(MetricRegistry registry) {
        return Slf4jReporter.forRegistry(registry)
            .convertRatesTo(TimeUnit.SECONDS)
            .convertDurationsTo(TimeUnit.MILLISECONDS)
            .build();
    }

    void onStart(@Observes CamelContextStartedEvent event, Slf4jReporter reporter) {
        reporter.start(10L, TimeUnit.SECONDS);
    }

    void onStop(@Disposes Slf4jReporter reporter) {
        reporter.stop();
    }

    void configure(@Observes MetricsConfiguration config) {
        // Force Metrics CDI to use absolute names
        config.useAbsoluteName(true);
    }
}
