/**
 * Copyright 2017 Pivotal Software, Inc.
 * <p>
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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 io.micrometer.core.instrument.binder.logging;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.turbo.TurboFilter;
import ch.qos.logback.core.spi.FilterReply;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.lang.NonNullApi;
import io.micrometer.core.lang.NonNullFields;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

import static java.util.Collections.emptyList;

/**
 * @author Jon Schneider
 */
@NonNullApi
@NonNullFields
public class LogbackMetrics implements MeterBinder {
    static ThreadLocal<Boolean> ignoreMetrics = new ThreadLocal<>();

    private final Iterable<Tag> tags;

    public LogbackMetrics() {
        this(emptyList());
    }

    public LogbackMetrics(Iterable<Tag> tags) {
        this.tags = tags;
    }

    @Override
    public void bindTo(MeterRegistry registry) {
        LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
        context.addTurboFilter(new MetricsTurboFilter(registry, tags));
    }

    /**
     * Used by {@link Counter#increment()} implementations that may cause a logback logging event to occur.
     * Attempting to instrument that implementation would cause a {@link StackOverflowError}.
     *
     * @param r Don't record metrics on logging statements that occur inside of this runnable.
     */
    public static void ignoreMetrics(Runnable r) {
        ignoreMetrics.set(true);
        r.run();
        ignoreMetrics.remove();
    }
}

@NonNullApi
@NonNullFields
class MetricsTurboFilter extends TurboFilter {
    private final Counter errorCounter;
    private final Counter warnCounter;
    private final Counter infoCounter;
    private final Counter debugCounter;
    private final Counter traceCounter;

    MetricsTurboFilter(MeterRegistry registry, Iterable<Tag> tags) {
        errorCounter = Counter.builder("logback.events")
            .tags(tags).tags("level", "error")
            .description("Number of error level events that made it to the logs")
            .register(registry);

        warnCounter = Counter.builder("logback.events")
            .tags(tags).tags("level", "warn")
            .description("Number of warn level events that made it to the logs")
            .register(registry);

        infoCounter = Counter.builder("logback.events")
            .tags(tags).tags("level", "info")
            .description("Number of info level events that made it to the logs")
            .register(registry);

        debugCounter = Counter.builder("logback.events")
            .tags(tags).tags("level", "debug")
            .description("Number of debug level events that made it to the logs")
            .register(registry);

        traceCounter = Counter.builder("logback.events")
            .tags(tags).tags("level", "trace")
            .description("Number of trace level events that made it to the logs")
            .register(registry);
    }

    @Override
    public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
        Boolean ignored = LogbackMetrics.ignoreMetrics.get();
        if(ignored != null && ignored) {
            return FilterReply.NEUTRAL;
        }

        // cannot use logger.isEnabledFor(level), as it would cause a StackOverflowError by calling this filter again!
        if (level.isGreaterOrEqual(logger.getEffectiveLevel()) && format != null) {
            switch (level.toInt()) {
                case Level.ERROR_INT:
                    errorCounter.increment();
                    break;
                case Level.WARN_INT:
                    warnCounter.increment();
                    break;
                case Level.INFO_INT:
                    infoCounter.increment();
                    break;
                case Level.DEBUG_INT:
                    debugCounter.increment();
                    break;
                case Level.TRACE_INT:
                    traceCounter.increment();
                    break;
            }
        }

        return FilterReply.NEUTRAL;
    }
}

@@ -1220,16 +1220,65 @@
 arker;%0A%0A
+import java.util.HashMap;%0Aimport java.util.Map;%0A%0A
 import s
@@ -1316,16 +1316,16 @@
 tyList;%0A
-
 %0A/**%0A * 
@@ -1537,16 +1537,112 @@
 g%3E tags;
+%0A    private final Map%3CMeterRegistry, MetricsTurboFilter%3E metricsTurboFilters = new HashMap%3C%3E();
 %0A%0A    pu
@@ -1938,19 +1938,15 @@
     
-context.add
+Metrics
 Turb
@@ -1952,17 +1952,26 @@
 boFilter
-(
+ filter = 
 new Metr
@@ -2000,16 +2000,106 @@
 y, tags)
+;%0A        metricsTurboFilters.put(registry, filter);%0A        context.addTurboFilter(filter
 );%0A    %7D
@@ -2517,24 +2517,24 @@
    r.run();%0A
-
         igno
@@ -2558,16 +2558,136 @@
 );%0A    %7D
+%0A%0A    public Map%3CMeterRegistry, MetricsTurboFilter%3E getMetricsTurboFilters() %7B%0A        return metricsTurboFilters;%0A    %7D
 %0A%7D%0A%0A@Non

Add mechanism to retrieve TurboFilter from LogbackMetrics so it can be removed from LoggerContext (issue #421)