/**
 * 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.component.micrometer;

import java.time.Duration;
import java.util.function.Predicate;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import static org.apache.camel.component.micrometer.MicrometerConstants.ALWAYS;
import static org.apache.camel.component.micrometer.MicrometerConstants.CAMEL_METERS;

/**
 * Filter for adding distribution statistics to Timers and Distribution Summaries.
 * Configure and add this to the {@link io.micrometer.core.instrument.MeterRegistry}
 * if desired:
 *
 * <pre>
 *     DistributionStatisticConfigFilter filter = new DistributionStatisticConfigFilter()
 *     // filter.set...
 *     meterRegistry.config().meterFilter(filter)
 * </pre>
 */
public class DistributionStatisticConfigFilter implements MeterFilter {

    private Predicate<Meter.Id> appliesTo = ALWAYS;
    private Long maximumExpectedValue;
    private Long minimumExpectedValue;
    private Boolean publishPercentileHistogram = true;
    private Integer percentilePrecision;
    private Integer bufferLength;
    private Duration expiry;
    private double[] percentiles;
    private long[] slas;

    @Override
    public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
        if (CAMEL_METERS.and(appliesTo).test(id)) {
            return DistributionStatisticConfig.builder()
                    .percentilesHistogram(publishPercentileHistogram)
                    .percentiles(percentiles)
                    .percentilePrecision(percentilePrecision)
                    .maximumExpectedValue(maximumExpectedValue)
                    .minimumExpectedValue(minimumExpectedValue)
                    .sla(slas)
                    .bufferLength(bufferLength)
                    .expiry(expiry)
                    .build()
                    .merge(config);
        }
        return config;
    }

    /**
     * Restrict a condition under which this config applies to a Camel meter
     *
     * @param appliesTo predicate that must return true so that this config applies
     */
    public DistributionStatisticConfigFilter andAppliesTo(Predicate<Meter.Id> appliesTo) {
        this.appliesTo = this.appliesTo.and(appliesTo);
        return this;
    }

    /**
     * Add a condition under which this config applies to a Camel meter
     *
     * @param appliesTo predicate that must return true so that this config applies
     */
    public DistributionStatisticConfigFilter orAppliesTo(Predicate<Meter.Id> appliesTo) {
        this.appliesTo = this.appliesTo.or(appliesTo);
        return this;
    }

    /**
     * Sets the maximum expected value for a distribution summary value.
     * Controls the number of buckets shipped by publishPercentileHistogram as well as controlling the
     * accuracy and memory footprint of the underlying HdrHistogram structure.
     *
     * @param maximumExpectedValue the maximum expected value for a distribution summary value
     */
    public DistributionStatisticConfigFilter setMaximumExpectedValue(Long maximumExpectedValue) {
        this.maximumExpectedValue = maximumExpectedValue;
        return this;
    }

    /**
     * Sets the minimum expected value for a distribution summary value.
     * Controls the number of buckets shipped by publishPercentileHistogram as well as controlling the
     * accuracy and memory footprint of the underlying HdrHistogram structure.
     *
     * @param minimumExpectedValue the minimum expected value for a distribution summary value
     */
    public DistributionStatisticConfigFilter setMinimumExpectedValue(Long minimumExpectedValue) {
        this.minimumExpectedValue = minimumExpectedValue;
        return this;
    }

    /**
     * Sets the maximum expected duration for a timer value
     * Controls the number of buckets shipped by publishPercentileHistogram as well as controlling the
     * accuracy and memory footprint of the underlying HdrHistogram structure.
     *
     * @param maximumExpectedDuration the maximum expected duration for a timer value
     */
    public DistributionStatisticConfigFilter setMaximumExpectedDuration(Duration maximumExpectedDuration) {
        this.maximumExpectedValue = maximumExpectedDuration.toNanos();
        return this;
    }

    /**
     * Sets the minimum expected duration for a timer value
     * Controls the number of buckets shipped by publishPercentileHistogram as well as controlling the
     * accuracy and memory footprint of the underlying HdrHistogram structure.
     *
     * @param minimumExpectedDuration the minimum expected duration for a timer value
     */
    public DistributionStatisticConfigFilter setMinimumExpectedDuration(Duration minimumExpectedDuration) {
        this.minimumExpectedValue = minimumExpectedDuration.toNanos();
        return this;
    }

    /**
     * Whether to publish aggregatable percentile approximations for Prometheus or Atlas.
     * Has no effect on systems that do not support aggregatable percentile approximations.
     * This defaults to true.
     *
     * @param publishPercentileHistogram Whether to publish aggregatable percentile approximations.
     */
    public DistributionStatisticConfigFilter setPublishPercentileHistogram(Boolean publishPercentileHistogram) {
        this.publishPercentileHistogram = publishPercentileHistogram;
        return this;
    }

    public DistributionStatisticConfigFilter setBufferLength(Integer bufferLength) {
        this.bufferLength = bufferLength;
        return this;
    }

    public DistributionStatisticConfigFilter setExpiry(Duration expiry) {
        this.expiry = expiry;
        return this;
    }

    /**
     * Calculate and publish percentile values. These values are non-aggregatable across dimensions.
     *
     * @param percentiles array of percentiles to be published
     */
    public DistributionStatisticConfigFilter setPercentiles(double[] percentiles) {
        this.percentiles = percentiles;
        return this;
    }

    public DistributionStatisticConfigFilter setPercentilePrecision(Integer percentilePrecision) {
        this.percentilePrecision = percentilePrecision;
        return this;
    }

    /**
     * Publish a cumulative histogram with buckets defined by your SLAs. Used together with publishPercentileHistogram
     * on a monitoring system that supports aggregatable percentiles, this setting adds additional buckets to the published histogram.
     * Used on a system that does not support aggregatable percentiles, this setting causes a histogram to be published with only these buckets.
     *
     * @param slas array of percentiles to be published
     */
    public DistributionStatisticConfigFilter setSlas(long[] slas) {
        this.slas = slas;
        return this;
    }
}
