package org.zalando.nakadi.metrics;

import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.annotations.VisibleForTesting;

import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;

import static org.zalando.nakadi.metrics.MetricUtils.metricNameFor;

public class EventTypeMetrics {

    private final String eventTypeName;
    private final MetricRegistry metricRegistry;

    private final Histogram eventsPerBatchHistogram;
    private final Timer publishingTimer;
    private final Meter eventCountMeter;
    private final Histogram averageEventSizeInBytesHistogram;
    private final ConcurrentMap<Integer, Meter> statusCodeMeter = new ConcurrentHashMap<>();

    public EventTypeMetrics(final String eventTypeName, final MetricRegistry metricRegistry) {
        this.eventTypeName = eventTypeName;
        this.metricRegistry = metricRegistry;
        eventCountMeter = metricRegistry.meter(metricNameFor(eventTypeName, "publishing.events"));
        eventsPerBatchHistogram = metricRegistry.histogram(metricNameFor(eventTypeName, "publishing.eventsPerBatch"));
        averageEventSizeInBytesHistogram = metricRegistry.histogram(
                metricNameFor(eventTypeName, "publishing.averageEventSizeInBytes"));
        publishingTimer = metricRegistry.timer(metricNameFor(eventTypeName, "publishing"));
    }

    public void reportSizing(final int eventsPerBatch, final int totalEventSize) {
        eventsPerBatchHistogram.update(eventsPerBatch);
        eventCountMeter.mark(eventsPerBatch);
        averageEventSizeInBytesHistogram.update(eventsPerBatch == 0 ? 0 : totalEventSize / eventsPerBatch);
    }

    public void incrementResponseCount(final int code) {
        statusCodeMeter.computeIfAbsent(code,
                key -> metricRegistry.meter(metricNameFor(eventTypeName, "publishing." + code)))
                .mark();
    }

    public void updateTiming(final long startingNanos, final long currentNanos) {
        publishingTimer.update(currentNanos - startingNanos, TimeUnit.NANOSECONDS);
    }

    @VisibleForTesting
    public long getResponseCount(final int code) {
        return Optional.ofNullable(statusCodeMeter.get(code)).map(Meter::getCount).orElse(-1L);
    }
}