package ru.yandex.qe.cache.telemetry;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

import com.google.common.base.Stopwatch;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.constructs.EhcacheDecoratorAdapter;

import ru.yandex.monlib.metrics.histogram.Histograms;
import ru.yandex.monlib.metrics.labels.Labels;
import ru.yandex.monlib.metrics.primitives.Histogram;
import ru.yandex.monlib.metrics.primitives.Rate;
import ru.yandex.monlib.metrics.registry.MetricRegistry;

/**
 * An instrumented {@link Ehcache} instance.
 */
public class MetricsInstrumentedCache extends EhcacheDecoratorAdapter {

    private final Rate hitRate;
    private final Rate missRate;
    private final Histogram getDuration;
    private final Histogram putDuration;

    public MetricsInstrumentedCache(final Ehcache cache, MetricRegistry registry) {
        super(cache);
        registry.lazyGaugeInt64("ehcache.size", Labels.of("cache_name", cache.getName()),
                () -> cache.getStatistics().getSize());
        hitRate = registry.rate("ehcache.hit_rate", Labels.of("cache_name", cache.getName()));
        missRate = registry.rate("ehcache.miss_rate", Labels.of("cache_name", cache.getName()));
        getDuration = registry.histogramRate("ehcache.get_duration_millis", Labels.of("cache_name", cache.getName()),
                Histograms.exponential(22, 2.0d, 1.0d));
        putDuration = registry.histogramRate("ehcache.put_duration_millis", Labels.of("cache_name", cache.getName()),
                Histograms.exponential(22, 2.0d, 1.0d));
    }

    @Override
    public Element get(Object key) throws IllegalStateException, CacheException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            final Element element = underlyingCache.get(key);
            if (element != null) {
                hitRate.inc();
                underlyingCache.getStatistics().cacheMissExpiredOperation().rate();
            } else {
                missRate.inc();
            }
            return element;
        } finally {
            getDuration.record(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }

    @Override
    public Element get(Serializable key) throws IllegalStateException, CacheException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            final Element element = underlyingCache.get(key);
            if (element != null) {
                hitRate.inc();
            } else {
                missRate.inc();
            }
            return element;
        } finally {
            getDuration.record(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }

    @Override
    public void put(Element element) throws IllegalArgumentException, IllegalStateException, CacheException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            underlyingCache.put(element);
        } finally {
            putDuration.record(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }

    @Override
    public void put(Element element, boolean doNotNotifyCacheReplicators) throws IllegalArgumentException, IllegalStateException, CacheException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            underlyingCache.put(element, doNotNotifyCacheReplicators);
        } finally {
            putDuration.record(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }

    @Override
    public Element putIfAbsent(Element element) throws NullPointerException {
        Stopwatch stopwatch = Stopwatch.createStarted();
        try {
            return underlyingCache.putIfAbsent(element);
        } finally {
            putDuration.record(stopwatch.elapsed(TimeUnit.MILLISECONDS));
        }
    }
}
