package org.airsonic.player.monitor;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jmx.JmxReporter;
import org.airsonic.player.service.ApacheCommonsConfigurationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * Created by remi on 17/01/17.
 */
@Service
public class MetricsManager {

    @Autowired
    private ApacheCommonsConfigurationService configurationService;

    // Main metrics registry
    private static final MetricRegistry metrics = new MetricRegistry();

    private static volatile Boolean metricsActivatedByConfiguration = null;
    private static Object _lock = new Object();

    // Potential metrics reporters
    private static JmxReporter reporter;

    private void configureMetricsActivation() {
        if (configurationService.containsKey("Metrics")) {
            metricsActivatedByConfiguration = Boolean.TRUE;

            // Start a Metrics JMX reporter
            reporter = JmxReporter.forRegistry(metrics)
                    .convertRatesTo(TimeUnit.SECONDS)
                    .convertDurationsTo(TimeUnit.MILLISECONDS)
                    .build();
            reporter.start();
        } else {
            metricsActivatedByConfiguration = Boolean.FALSE;
        }
    }

    private boolean metricsActivatedByConfiguration() {
        if (metricsActivatedByConfiguration == null) {
            synchronized (_lock) {
                if (metricsActivatedByConfiguration == null) {
                    configureMetricsActivation();
                }
            }
        }
        return metricsActivatedByConfiguration;
    }

    /**
     * Creates a {@link Timer} whose name is based on a class name and a
     * qualified name.
     */
    public Timer timer(Class clazz, String name) {
        if (metricsActivatedByConfiguration()) {
            return new TimerBuilder().timer(clazz, name);
        } else {
            return nullTimerSingleton;
        }
    }

    /**
     * Creates a {@link Timer} whose name is based on an object's class name and a
     * qualified name.
     */
    public Timer timer(Object ref, String name) {
        return timer(ref.getClass(),name);
    }

    /**
     * Initiate a {@link TimerBuilder} using a condition.
     * If the condition is false, a void {@link Timer} will finally be built thus
     * no timer will be registered in the Metrics registry.
     */
    public TimerBuilder condition(boolean ifTrue) {
        if (metricsActivatedByConfiguration()) {
            if (!ifTrue) {
                return conditionFalseTimerBuilderSingleton;
            }
            return new TimerBuilder();
        } else {
            return nullTimerBuilderSingleton;
        }
    }

    public void setConfigurationService(ApacheCommonsConfigurationService configurationService) {
        this.configurationService = configurationService;
    }

    /**
     * A class that builds a {@link Timer}
     */
    public static class TimerBuilder {

        public Timer timer(Class clazz, String name) {
            com.codahale.metrics.Timer t = metrics.timer(MetricRegistry.name(clazz,name));
            com.codahale.metrics.Timer.Context tContext = t.time();
            return new Timer(tContext);
        }

        public Timer timer(Object ref, String name) {
            return timer(ref.getClass(),name);
        }

    }

    /**
     * A class that holds a Metrics timer context implementing {@link AutoCloseable}
     * thus it can be used in a try-with-resources statement.
     */
    public static class Timer implements AutoCloseable {

        private com.codahale.metrics.Timer.Context timerContext;

        protected Timer(com.codahale.metrics.Timer.Context timerContext) {
            this.timerContext = timerContext;
        }

        @Override
        public void close() {
            timerContext.stop();
        }

    }


    // -----------------------------------------------------------------
    // Convenient singletons to avoid creating useless objects instances
    // -----------------------------------------------------------------
    private static final NullTimer nullTimerSingleton = new NullTimer(null);
    private static final NullTimerBuilder conditionFalseTimerBuilderSingleton = new NullTimerBuilder();
    private static final NullTimerBuilder nullTimerBuilderSingleton = new NullTimerBuilder();

    private static class NullTimer extends Timer {

        protected NullTimer(com.codahale.metrics.Timer.Context timerContext) {
            super(timerContext);
        }

        @Override
        public void close() {
            // Does nothing
        }
    }

    private static class NullTimerBuilder extends TimerBuilder {
        @Override
        public Timer timer(Class clazz, String name) {
            return nullTimerSingleton;
        }
    }

}
