/*
 * 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.phoenix.monitoring;

import java.util.HashMap;
import java.util.Map;
import org.HdrHistogram.Histogram;
import org.HdrHistogram.Recorder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract base class for creating percentile-based histograms that capture and analyze the
 * distribution of recorded values. This histogram tracks values up to a specified maximum and
 * provides statistical analysis including percentile distributions, min/max values, and operation
 * counts.
 * <p>
 * <b>Internal Use Only:</b> This class is for internal use only and should not be used directly by
 * users of Phoenix.
 * </p>
 * <br/>
 * Key features:
 * <ul>
 * <li>Records values efficiently using {@link org.HdrHistogram.Histogram} internally</li>
 * <li>Generates percentile distributions through concrete implementations</li>
 * <li>Supports optional metadata tags for enhanced monitoring context</li>
 * <li>Generates immutable statistical snapshots with percentile analysis</li>
 * <li>Automatically handles values exceeding the maximum trackable limit</li>
 * </ul>
 * <br/>
 * Usage workflow:
 * <ol>
 * <li>Record values using {@link #addValue(long)}</li>
 * <li>Optionally attach metadata using {@link #addTag(String, String)}</li>
 * <li>Generate distribution snapshots via {@link #getPercentileHistogramDistribution()}</li>
 * </ol>
 * <br/>
 * Concrete implementations must define {@link #generateDistributionMap(Histogram)} to specify which
 * percentiles and statistics to include in the distribution.
 */
abstract class PercentileHistogram {
  private static final Logger LOGGER = LoggerFactory.getLogger(PercentileHistogram.class);

  // Strings used to create metrics names.
  static final String NUM_OPS_METRIC_NAME = "_num_ops";
  static final String MIN_METRIC_NAME = "_min";
  static final String MAX_METRIC_NAME = "_max";
  static final String MEDIAN_METRIC_NAME = "_median";
  static final String TWENTY_FIFTH_PERCENTILE_METRIC_NAME = "_25th_percentile";
  static final String SEVENTY_FIFTH_PERCENTILE_METRIC_NAME = "_75th_percentile";
  static final String NINETIETH_PERCENTILE_METRIC_NAME = "_90th_percentile";
  static final String NINETY_FIFTH_PERCENTILE_METRIC_NAME = "_95th_percentile";

  private Histogram prevHistogram = null;
  /**
   * The recorder that records the values in a {@link Histogram}.
   */
  private final Recorder recorder;
  private final String name;
  private final long maxUtil;
  private Map<String, String> tags = null;

  PercentileHistogram(long maxUtil, String name) {
    this.name = name;
    this.maxUtil = maxUtil;
    this.recorder = new Recorder(maxUtil, 2);
  }

  /**
   * Records a value in the histogram for statistical analysis. The recorded value will be included
   * in subsequent percentile calculations and distribution snapshot generated by
   * {@link #getPercentileHistogramDistribution()}. <br/>
   * <br/>
   * Values exceeding the maximum trackable limit (specified during histogram construction) are
   * automatically rejected with a warning logged. This prevents histogram corruption while
   * maintaining recording performance.
   * @param value the value to record, must be within the histogram's trackable range
   */
  void addValue(long value) {
    if (value > maxUtil) {
      // Ignoring recording value more than maximum trackable value.
      LOGGER.warn("Histogram recording higher value than maximum. Ignoring it.");
      return;
    }
    recorder.recordValue(value);
  }

  /**
   * Generates an immutable snapshot containing the percentile distribution of values recorded since
   * the last call to this method. The snapshot includes statistical metrics such as min/max values,
   * operation count, percentile values, and any attached tags. <br/>
   * <br/>
   * This method captures an interval histogram (values recorded since the previous snapshot) and
   * delegates to concrete implementations via {@link #generateDistributionMap(Histogram)} to
   * determine which specific percentiles and metrics to include. <br/>
   * <br/>
   * The returned {@link PercentileHistogramDistribution} is thread-safe and immutable, making it
   * suitable for concurrent access by monitoring systems.
   * @return an immutable {@link HistogramDistribution} containing percentile analysis and
   *         statistics
   */
  HistogramDistribution getPercentileHistogramDistribution() {
    Histogram histogram = this.recorder.getIntervalHistogram(prevHistogram);
    HistogramDistribution distribution;
    if (tags == null) {
      distribution = new PercentileHistogramDistribution(name, histogram.getMinValue(),
        histogram.getMaxValue(), histogram.getTotalCount(), generateDistributionMap(histogram));
    } else {
      distribution =
        new PercentileHistogramDistribution(name, histogram.getMinValue(), histogram.getMaxValue(),
          histogram.getTotalCount(), generateDistributionMap(histogram), tags);
    }
    this.prevHistogram = histogram;
    return distribution;
  }

  /**
   * Attaches a metadata tag to the histogram as a key-value pair. Tags provide additional context
   * about the histogram data and are included in all distribution snapshots generated by
   * {@link #getPercentileHistogramDistribution()}. <br/>
   * <br/>
   * Tags are commonly used for dimensional monitoring, allowing metrics to be filtered and grouped
   * by tag names. Multiple tags can be added to the same histogram, and duplicate keys will
   * overwrite previous values.
   * @param key   the tag key
   * @param value the tag value
   */
  void addTag(String key, String value) {
    if (tags == null) {
      tags = new HashMap<>();
    }
    tags.put(key, value);
  }

  /**
   * Generates a map of percentile distribution where the key is the percentile name (e.g.,
   * "_90th_percentile", "_95th_percentile", "_median", etc.) and the value is the actual percentile
   * value from the histogram snapshot. This method is for internal use only and is called from
   * {@link #getPercentileHistogramDistribution()}.
   * @param snapshot the snapshot of the {@link Histogram}
   * @return a map of percentile distribution with percentile names as keys and their corresponding
   *         values
   */
  protected abstract Map<String, Long> generateDistributionMap(Histogram snapshot);
}
