/**
 * HistogramDataAccessTest.java
 * Written by Gil Tene of Azul Systems, and released to the public domain,
 * as explained at http://creativecommons.org/publicdomain/zero/1.0/
 *
 * @author Gil Tene
 */

package org.HdrHistogram;

import org.junit.Assert;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

/**
 * JUnit test for {@link org.HdrHistogram.Histogram}
 */
public class HistogramDataAccessTest {
    static final long highestTrackableValue = 3600L * 1000 * 1000; // 1 hour in usec units
    static final int numberOfSignificantValueDigits = 3; // Maintain at least 3 decimal points of accuracy
    static final Histogram histogram;
    static final Histogram scaledHistogram;
    static final Histogram rawHistogram;
    static final Histogram scaledRawHistogram;
    static final Histogram postCorrectedHistogram;
    static final Histogram postCorrectedScaledHistogram;

    static {
        histogram = new Histogram(highestTrackableValue, numberOfSignificantValueDigits);
        scaledHistogram = new Histogram(1000, highestTrackableValue * 512, numberOfSignificantValueDigits);
        rawHistogram = new Histogram(highestTrackableValue, numberOfSignificantValueDigits);
        scaledRawHistogram = new Histogram(1000, highestTrackableValue * 512, numberOfSignificantValueDigits);
        // Log hypothetical scenario: 100 seconds of "perfect" 1msec results, sampled
        // 100 times per second (10,000 results), followed by a 100 second pause with
        // a single (100 second) recorded result. Recording is done indicating an expected
        // interval between samples of 10 msec:
        for (int i = 0; i < 10000; i++) {
            histogram.recordValueWithExpectedInterval(1000 /* 1 msec */, 10000 /* 10 msec expected interval */);
            scaledHistogram.recordValueWithExpectedInterval(1000 * 512 /* 1 msec */, 10000 * 512 /* 10 msec expected interval */);
            rawHistogram.recordValue(1000 /* 1 msec */);
            scaledRawHistogram.recordValue(1000 * 512/* 1 msec */);
        }
        histogram.recordValueWithExpectedInterval(100000000L /* 100 sec */, 10000 /* 10 msec expected interval */);
        scaledHistogram.recordValueWithExpectedInterval(100000000L * 512 /* 100 sec */, 10000 * 512 /* 10 msec expected interval */);
        rawHistogram.recordValue(100000000L /* 100 sec */);
        scaledRawHistogram.recordValue(100000000L * 512 /* 100 sec */);

        postCorrectedHistogram = rawHistogram.copyCorrectedForCoordinatedOmission(10000 /* 10 msec expected interval */);
        postCorrectedScaledHistogram = scaledRawHistogram.copyCorrectedForCoordinatedOmission(10000 * 512 /* 10 msec expected interval */);
    }

    @Test
    public void testScalingEquivalence() {
        Assert.assertEquals("averages should be equivalent",
                histogram.getMean() * 512,
                scaledHistogram.getMean(), scaledHistogram.getMean() * 0.000001);
        Assert.assertEquals("total count should be the same",
                histogram.getTotalCount(),
                scaledHistogram.getTotalCount());
        Assert.assertEquals("99%'iles should be equivalent",
                scaledHistogram.highestEquivalentValue(histogram.getValueAtPercentile(99.0) * 512),
                scaledHistogram.highestEquivalentValue(scaledHistogram.getValueAtPercentile(99.0)));
        Assert.assertEquals("Max should be equivalent",
                scaledHistogram.highestEquivalentValue(histogram.getMaxValue() * 512),
                scaledHistogram.getMaxValue());
        // Same for post-corrected:
        Assert.assertEquals("averages should be equivalent",
                histogram.getMean() * 512,
                scaledHistogram.getMean(), scaledHistogram.getMean() * 0.000001);
        Assert.assertEquals("total count should be the same",
                postCorrectedHistogram.getTotalCount(),
                postCorrectedScaledHistogram.getTotalCount());
        Assert.assertEquals("99%'iles should be equivalent",
                postCorrectedHistogram.lowestEquivalentValue(postCorrectedHistogram.getValueAtPercentile(99.0)) * 512,
                postCorrectedScaledHistogram.lowestEquivalentValue(postCorrectedScaledHistogram.getValueAtPercentile(99.0)));
        Assert.assertEquals("Max should be equivalent",
                postCorrectedScaledHistogram.highestEquivalentValue(postCorrectedHistogram.getMaxValue() * 512),
                postCorrectedScaledHistogram.getMaxValue());
    }

    @Test
    public void testPreVsPostCorrectionValues()  {
        // Loop both ways (one would be enough, but good practice just for fun:

        Assert.assertEquals("pre and post corrected count totals ",
                histogram.getTotalCount(), postCorrectedHistogram.getTotalCount());

        // The following comparison loops would have worked in a perfect accuracy world, but since post
        // correction is done based on the value extracted from the bucket, and the during-recording is done
        // based on the actual (not pixelized) value, there will be subtle differences due to roundoffs:

        //        for (HistogramIterationValue v : histogram.allValues()) {
        //            long preCorrectedCount = v.getCountAtValueIteratedTo();
        //            long postCorrectedCount = postCorrectedHistogram.getCountAtValue(v.getValueIteratedTo());
        //            Assert.assertEquals("pre and post corrected count at value " + v.getValueIteratedTo(),
        //                    preCorrectedCount, postCorrectedCount);
        //        }
        //
        //        for (HistogramIterationValue v : postCorrectedHistogram.allValues()) {
        //            long preCorrectedCount = v.getCountAtValueIteratedTo();
        //            long postCorrectedCount = histogram.getCountAtValue(v.getValueIteratedTo());
        //            Assert.assertEquals("pre and post corrected count at value " + v.getValueIteratedTo(),
        //                    preCorrectedCount, postCorrectedCount);
        //        }

    }

    @Test
    public void testGetTotalCount() throws Exception {
        // The overflow value should count in the total count:
        Assert.assertEquals("Raw total count is 10,001",
                10001L, rawHistogram.getTotalCount());
        Assert.assertEquals("Total count is 20,000",
                20000L, histogram.getTotalCount());
    }

    @Test
    public void testGetMaxValue() throws Exception {
        Assert.assertTrue(
                histogram.valuesAreEquivalent(100L * 1000 * 1000,
                        histogram.getMaxValue()));
    }

    @Test
    public void testGetMinValue() throws Exception {
        Assert.assertTrue(
                histogram.valuesAreEquivalent(1000,
                        histogram.getMinValue()));
    }

    @Test
    public void testGetMean() throws Exception {
        double expectedRawMean = ((10000.0 * 1000) + (1.0 * 100000000))/10001; /* direct avg. of raw results */
        double expectedMean = (1000.0 + 50000000.0)/2; /* avg. 1 msec for half the time, and 50 sec for other half */
        // We expect to see the mean to be accurate to ~3 decimal points (~0.1%):
        Assert.assertEquals("Raw mean is " + expectedRawMean + " +/- 0.1%",
                expectedRawMean, rawHistogram.getMean(), expectedRawMean * 0.001);
        Assert.assertEquals("Mean is " + expectedMean + " +/- 0.1%",
                expectedMean, histogram.getMean(), expectedMean * 0.001);
    }

    @Test
    public void testGetStdDeviation() throws Exception {
        double expectedRawMean = ((10000.0 * 1000) + (1.0 * 100000000))/10001; /* direct avg. of raw results */
        double expectedRawStdDev =
                Math.sqrt(
                    ((10000.0 * Math.pow((1000.0 - expectedRawMean), 2)) +
                            Math.pow((100000000.0 - expectedRawMean), 2)) /
                            10001);

        double expectedMean = (1000.0 + 50000000.0)/2; /* avg. 1 msec for half the time, and 50 sec for other half */
        double expectedSquareDeviationSum = 10000 * Math.pow((1000.0 - expectedMean), 2);
        for (long value = 10000; value <= 100000000; value += 10000) {
            expectedSquareDeviationSum += Math.pow((value - expectedMean), 2);
        }
        double expectedStdDev = Math.sqrt(expectedSquareDeviationSum / 20000);

        // We expect to see the standard deviations to be accurate to ~3 decimal points (~0.1%):
        Assert.assertEquals("Raw standard deviation is " + expectedRawStdDev + " +/- 0.1%",
                expectedRawStdDev, rawHistogram.getStdDeviation(), expectedRawStdDev * 0.001);
        Assert.assertEquals("Standard deviation is " + expectedStdDev + " +/- 0.1%",
                expectedStdDev, histogram.getStdDeviation(), expectedStdDev * 0.001);
    }

    @Test
    public void testGetValueAtPercentileExamples() throws Exception {
        Histogram hist = new Histogram(3600000000L, 3);
        hist.recordValue(1);
        hist.recordValue(2);

        Assert.assertEquals("50.0%'ile is 1",
                1, hist.getValueAtPercentile(50.0));
        Assert.assertEquals("50.00000000000001%'ile is 1",
                1, hist.getValueAtPercentile(50.00000000000001));
        Assert.assertEquals("50.0000000000001%'ile is 2",
                2, hist.getValueAtPercentile(50.0000000000001));

        hist.recordValue(2);
        hist.recordValue(2);
        hist.recordValue(2);

        long val = hist.getValueAtPercentile(25);

        Assert.assertEquals("25%'ile is 2",
                2, hist.getValueAtPercentile(25));
        Assert.assertEquals("30%'ile is 2",
                2, hist.getValueAtPercentile(30));
        
    }

    @Test
    public void testGetValueAtPercentile() throws Exception {
        Assert.assertEquals("raw 30%'ile is 1 msec +/- 0.1%",
                1000.0, (double) rawHistogram.getValueAtPercentile(30.0),
                1000.0 * 0.001);
        Assert.assertEquals("raw 99%'ile is 1 msec +/- 0.1%",
                1000.0, (double) rawHistogram.getValueAtPercentile(99.0),
                1000.0 * 0.001);
        Assert.assertEquals("raw 99.99%'ile is 1 msec +/- 0.1%",
                1000.0, (double) rawHistogram.getValueAtPercentile(99.99)
                , 1000.0 * 0.001);
        Assert.assertEquals("raw 99.999%'ile is 100 sec +/- 0.1%",
                100000000.0, (double) rawHistogram.getValueAtPercentile(99.999),
                100000000.0 * 0.001);
        Assert.assertEquals("raw 100%'ile is 100 sec +/- 0.1%",
                100000000.0, (double) rawHistogram.getValueAtPercentile(100.0),
                100000000.0 * 0.001);

        Assert.assertEquals("30%'ile is 1 msec +/- 0.1%",
                1000.0, (double) histogram.getValueAtPercentile(30.0),
                1000.0 * 0.001);
        Assert.assertEquals("50%'ile is 1 msec +/- 0.1%",
                1000.0, (double) histogram.getValueAtPercentile(50.0),
                1000.0 * 0.001);
        Assert.assertEquals("75%'ile is 50 sec +/- 0.1%",
                50000000.0, (double) histogram.getValueAtPercentile(75.0),
                50000000.0 * 0.001);
        Assert.assertEquals("90%'ile is 80 sec +/- 0.1%",
                80000000.0, (double) histogram.getValueAtPercentile(90.0),
                80000000.0 * 0.001);
        Assert.assertEquals("99%'ile is 98 sec +/- 0.1%",
                98000000.0, (double) histogram.getValueAtPercentile(99.0),
                98000000.0 * 0.001);
        Assert.assertEquals("99.999%'ile is 100 sec +/- 0.1%",
                100000000.0, (double) histogram.getValueAtPercentile(99.999),
                100000000.0 * 0.001);
        Assert.assertEquals("100%'ile is 100 sec +/- 0.1%",
                100000000.0, (double) histogram.getValueAtPercentile(100.0),
                100000000.0 * 0.001);
    }
    
    @Test
    public void testGetValueAtPercentileForLargeHistogram() {
        long largestValue = 1000000000000L;
        Histogram h = new Histogram(largestValue, 5);
        h.recordValue(largestValue);
         
        Assert.assertTrue(h.getValueAtPercentile(100.0) > 0);
    }


    @Test
    public void testGetPercentileAtOrBelowValue() throws Exception {
        Assert.assertEquals("Raw percentile at or below 5 msec is 99.99% +/- 0.0001",
                99.99,
                rawHistogram.getPercentileAtOrBelowValue(5000), 0.0001);
        Assert.assertEquals("Percentile at or below 5 msec is 50% +/- 0.0001%",
                50.0,
                histogram.getPercentileAtOrBelowValue(5000), 0.0001);
        Assert.assertEquals("Percentile at or below 100 sec is 100% +/- 0.0001%",
                100.0,
                histogram.getPercentileAtOrBelowValue(100000000L), 0.0001);
    }

    @Test
    public void testGetCountBetweenValues() throws Exception {
        Assert.assertEquals("Count of raw values between 1 msec and 1 msec is 1",
                10000, rawHistogram.getCountBetweenValues(1000L, 1000L));
        Assert.assertEquals("Count of raw values between 5 msec and 150 sec is 1",
                1, rawHistogram.getCountBetweenValues(5000L, 150000000L));
        Assert.assertEquals("Count of values between 5 msec and 150 sec is 10,000",
                10000, histogram.getCountBetweenValues(5000L, 150000000L));
    }

    @Test
    public void testGetCountAtValue() throws Exception {
        Assert.assertEquals("Count of raw values at 10 msec is 0",
                0, rawHistogram.getCountBetweenValues(10000L, 10010L));
        Assert.assertEquals("Count of values at 10 msec is 0",
                1, histogram.getCountBetweenValues(10000L, 10010L));
        Assert.assertEquals("Count of raw values at 1 msec is 10,000",
                10000, rawHistogram.getCountAtValue(1000L));
        Assert.assertEquals("Count of values at 1 msec is 10,000",
                10000, histogram.getCountAtValue(1000L));
    }

    @Test
    public void testPercentiles() throws Exception {
        for (HistogramIterationValue v : histogram.percentiles(5 /* ticks per half */)) {
            Assert.assertEquals("Value at Iterated-to Percentile is the same as the matching getValueAtPercentile():\n" +
                    "getPercentileLevelIteratedTo = " + v.getPercentileLevelIteratedTo() +
                    "\ngetValueIteratedTo = " + v.getValueIteratedTo() +
                    "\ngetValueIteratedFrom = " + v.getValueIteratedFrom() +
                    "\ngetValueAtPercentile(getPercentileLevelIteratedTo()) = " +
                    histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo()) +
                    "\ngetPercentile = " + v.getPercentile() +
                    "\ngetValueAtPercentile(getPercentile())" +
                    histogram.getValueAtPercentile(v.getPercentile()) +
                    "\nequivalent at getValueAtPercentile(v.getPercentileLevelIteratedTo()) = " +
                    histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo())) +
                    "\nequivalent at getValueAtPercentile(v.getPercentile()) = " +
                    histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentile())) +
                    "\nindex at v.getValueIteratedTo() = " +
                    histogram.countsArrayIndex(v.getValueIteratedTo()) +
                    "\nindex at getValueAtPercentile(v.getPercentileLevelIteratedTo()) = " +
                    histogram.countsArrayIndex(histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo())) +
                    "\nindex at getValueAtPercentile(v.getPercentile()) = " +
                    histogram.countsArrayIndex(histogram.getValueAtPercentile(v.getPercentile())) +
                    "\nindex at getValueAtPercentile(v.getPercentile() - 0.0000000001) = " +
                    histogram.countsArrayIndex(histogram.getValueAtPercentile(v.getPercentile() - 0.0000000001)) +
                    "\ncount for (long)(((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) = " +
                            (long)(((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) +
                    "\n math for ((v.getPercentile() / 100.0) * histogram.getTotalCount()) = " +
                            ((v.getPercentile() / 100.0) * histogram.getTotalCount()) +
                    "\n math for (((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) = " +
                            (((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) +
                    "\n math for (long)(((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) = " +
                            (long)(((v.getPercentile() / 100.0) * histogram.getTotalCount()) + 0.5) +
                    "\ncount for (long)(Math.ceil((v.getPercentile() / 100.0) * histogram.getTotalCount())) = " +
                            (long)(Math.ceil((v.getPercentile() / 100.0) * histogram.getTotalCount())) +
                    "\n math for Math.ceil((v.getPercentile() / 100.0) * histogram.getTotalCount()) = " +
                            Math.ceil((v.getPercentile() / 100.0) * histogram.getTotalCount()) +
                    "\ntotalCountToThisValue = " +
                    v.getTotalCountToThisValue() +
                    "\ncountAtValueIteratedTo = " +
                    v.getCountAtValueIteratedTo() +
                    "\ncount at index at getValueAtPercentile(v.getPercentileLevelIteratedTo()) = " +
                    histogram.getCountAtIndex(histogram.countsArrayIndex(histogram.getValueAtPercentile(v.getPercentileLevelIteratedTo()))) +
                    "\ncount at index at getValueAtPercentile(v.getPercentile()) = " +
                    histogram.getCountAtIndex(histogram.countsArrayIndex(histogram.getValueAtPercentile(v.getPercentile()))) +
                    "\n"
                    ,
                    v.getValueIteratedTo(),
                    histogram.highestEquivalentValue(histogram.getValueAtPercentile(v.getPercentile())));
        }
    }

    @Test
    public void testLinearBucketValues() throws Exception {
        int index = 0;
        // Note that using linear buckets should work "as expected" as long as the number of linear buckets
        // is lower than the resolution level determined by largestValueWithSingleUnitResolution
        // (2000 in this case). Above that count, some of the linear buckets can end up rounded up in size
        // (to the nearest local resolution unit level), which can result in a smaller number of buckets that
        // expected covering the range.

        // Iterate raw data using linear buckets of 100 msec each.
        for (HistogramIterationValue v : rawHistogram.linearBucketValues(100000)) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Raw Linear 100 msec bucket # 0 added a count of 10000",
                        10000, countAddedInThisBucket);
            } else if (index == 999) {
                Assert.assertEquals("Raw Linear 100 msec bucket # 999 added a count of 1",
                        1, countAddedInThisBucket);
            } else {
                Assert.assertEquals("Raw Linear 100 msec bucket # " + index + " added a count of 0",
                        0 , countAddedInThisBucket);
            }
            index++;
        }
        Assert.assertEquals(1000, index);

        index = 0;
        long totalAddedCounts = 0;
        // Iterate data using linear buckets of 10 msec each.
        for (HistogramIterationValue v : histogram.linearBucketValues(10000)) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Linear 1 sec bucket # 0 [" +
                        v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
                        "] added a count of 10000",
                        10000, countAddedInThisBucket);
            }
            // Because value resolution is low enough (3 digits) that multiple linear buckets will end up
            // residing in a single value-equivalent range, some linear buckets will have counts of 2 or
            // more, and some will have 0 (when the first bucket in the equivalent range was the one that
            // got the total count bump).
            // However, we can still verify the sum of counts added in all the buckets...
            totalAddedCounts += v.getCountAddedInThisIterationStep();
            index++;
        }
        Assert.assertEquals("There should be 10000 linear buckets of size 10000 usec between 0 and 100 sec.",
                10000, index);
        Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);

        index = 0;
        totalAddedCounts = 0;
        // Iterate data using linear buckets of 1 msec each.
        for (HistogramIterationValue v : histogram.linearBucketValues(1000)) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 1) {
                Assert.assertEquals("Linear 1 sec bucket # 0 [" +
                        v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
                        "] added a count of 10000",
                        10000, countAddedInThisBucket);
            }
            // Because value resolution is low enough (3 digits) that multiple linear buckets will end up
            // residing in a single value-equivalent range, some linear buckets will have counts of 2 or
            // more, and some will have 0 (when the first bucket in the equivalent range was the one that
            // got the total count bump).
            // However, we can still verify the sum of counts added in all the buckets...
            totalAddedCounts += v.getCountAddedInThisIterationStep();
            index++;
        }
        // You may ask "why 100007 and not 100000?" for the value below? The answer is that at this fine
        // a linear stepping resolution, the final populated sub-bucket (at 100 seconds with 3 decimal
        // point resolution) is larger than our liner stepping, and holds more than one linear 1 msec
        // step in it.
        // Since we only know we're done with linear iteration when the next iteration step will step
        // out of the last populated bucket, there is not way to tell if the iteration should stop at
        // 100000 or 100007 steps. The proper thing to do is to run to the end of the sub-bucket quanta...
        Assert.assertEquals("There should be 100007 linear buckets of size 1000 usec between 0 and 100 sec.",
                100007, index);
        Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);


    }

    @Test
    public void testLogarithmicBucketValues() throws Exception {
        int index = 0;
        // Iterate raw data using logarithmic buckets starting at 10 msec.
        for (HistogramIterationValue v : rawHistogram.logarithmicBucketValues(10000, 2)) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Raw Logarithmic 10 msec bucket # 0 added a count of 10000",
                        10000, countAddedInThisBucket);
            } else if (index == 14) {
                Assert.assertEquals("Raw Logarithmic 10 msec bucket # 14 added a count of 1",
                        1, countAddedInThisBucket);
            } else {
                Assert.assertEquals("Raw Logarithmic 100 msec bucket # " + index + " added a count of 0",
                        0, countAddedInThisBucket);
            }
            index++;
        }
        Assert.assertEquals(14, index - 1);

        index = 0;
        long totalAddedCounts = 0;
        for (HistogramIterationValue v : histogram.logarithmicBucketValues(10000, 2)) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Logarithmic 10 msec bucket # 0 [" +
                        v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
                        "] added a count of 10000",
                        10000, countAddedInThisBucket);
            }
            totalAddedCounts += v.getCountAddedInThisIterationStep();
            index++;
        }
        Assert.assertEquals("There should be 14 Logarithmic buckets of size 10000 usec between 0 and 100 sec.",
                14, index - 1);
        Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
    }

    @Test
    public void testRecordedValues() throws Exception {
        int index = 0;
        // Iterate raw data by stepping through every value that has a count recorded:
        for (HistogramIterationValue v : rawHistogram.recordedValues()) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Raw recorded value bucket # 0 added a count of 10000",
                        10000, countAddedInThisBucket);
            } else {
                Assert.assertEquals("Raw recorded value bucket # " + index + " added a count of 1",
                        1, countAddedInThisBucket);
            }
            index++;
        }
        Assert.assertEquals(2, index);

        index = 0;
        long totalAddedCounts = 0;
        for (HistogramIterationValue v : histogram.recordedValues()) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 0) {
                Assert.assertEquals("Recorded bucket # 0 [" +
                        v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
                        "] added a count of 10000",
                        10000, countAddedInThisBucket);
            }
            Assert.assertTrue("The count in recorded bucket #" + index + " is not 0",
                    v.getCountAtValueIteratedTo() != 0);
            Assert.assertEquals("The count in recorded bucket #" + index +
                    " is exactly the amount added since the last iteration ",
                    v.getCountAtValueIteratedTo(), v.getCountAddedInThisIterationStep());
            totalAddedCounts += v.getCountAddedInThisIterationStep();
            index++;
        }
        Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
    }

    @Test
    public void testAllValues() throws Exception {
        int index = 0;
        long latestValueAtIndex = 0;
        long totalCountToThisPoint = 0;
        long totalValueToThisPoint = 0;
        // Iterate raw data by stepping through every value that has a count recorded:
        for (HistogramIterationValue v : rawHistogram.allValues()) {
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 1000) {
                Assert.assertEquals("Raw allValues bucket # 0 added a count of 10000",
                        10000, countAddedInThisBucket);
            } else if (histogram.valuesAreEquivalent(v.getValueIteratedTo(), 100000000)) {
                Assert.assertEquals("Raw allValues value bucket # " + index + " added a count of 1",
                        1, countAddedInThisBucket);
            } else {
                Assert.assertEquals("Raw allValues value bucket # " + index + " added a count of 0",
                        0, countAddedInThisBucket);
            }
            latestValueAtIndex = v.getValueIteratedTo();
            totalCountToThisPoint += v.getCountAtValueIteratedTo();
            Assert.assertEquals("total Count should match", totalCountToThisPoint, v.getTotalCountToThisValue());
            totalValueToThisPoint += v.getCountAtValueIteratedTo() * latestValueAtIndex;
            Assert.assertEquals("total Value should match", totalValueToThisPoint, v.getTotalValueToThisValue());
            index++;
        }
        Assert.assertEquals("index should be equal to countsArrayLength", histogram.countsArrayLength, index);

        index = 0;
        long totalAddedCounts = 0;
        HistogramIterationValue v1 = null;
        for (HistogramIterationValue v : histogram.allValues()) {
            v1 = v;
            long countAddedInThisBucket = v.getCountAddedInThisIterationStep();
            if (index == 1000) {
                Assert.assertEquals("AllValues bucket # 0 [" +
                        v.getValueIteratedFrom() + ".." + v.getValueIteratedTo() +
                        "] added a count of 10000",
                        10000, countAddedInThisBucket);
            }
            Assert.assertEquals("The count in AllValues bucket #" + index +
                    " is exactly the amount added since the last iteration ",
                    v.getCountAtValueIteratedTo(), v.getCountAddedInThisIterationStep());
            totalAddedCounts += v.getCountAddedInThisIterationStep();
            Assert.assertTrue("valueFromIndex(index) should be equal to getValueIteratedTo()",
                    histogram.valuesAreEquivalent(histogram.valueFromIndex(index), v.getValueIteratedTo()));
            index++;
        }
        Assert.assertEquals("index should be equal to countsArrayLength", histogram.countsArrayLength, index);
        Assert.assertEquals("Total added counts should be 20000", 20000, totalAddedCounts);
    }

    @Test
    public void testLinearIteratorSteps() {
        IntCountsHistogram histogram = new IntCountsHistogram(2);
        histogram.recordValue(193);
        histogram.recordValue(0);
        histogram.recordValue(1);
        histogram.recordValue(64);
        histogram.recordValue(128);
        int step = 64;
        int stepCount = 0;
        for (HistogramIterationValue itValue : histogram.linearBucketValues(step)) {
            stepCount++;
            itValue.getCountAddedInThisIterationStep();
        }
        Assert.assertEquals("should see 4 steps", 4, stepCount);
    }

    private static void recordOneValueAndDisplayLinearBuckets(int value, long step) {
        IntCountsHistogram histogram = new IntCountsHistogram(2);
        histogram.recordValue(value);
        for (HistogramIterationValue itValue : histogram.linearBucketValues(step)) {
            itValue.getCountAddedInThisIterationStep();
        }
    }

    @Test
    public void scanLinearIteratorForAIOOB() {
        List<int[]> broken = new ArrayList<int[]>();
        // scan iterators through a range of step sizes and recorded values, looking for AIOOB:
        for (int step = 1; step < 100; step++) {
            for (int value = 1; value < 1000; value++) {
                try {
                    recordOneValueAndDisplayLinearBuckets(value, step);
                } catch (ArrayIndexOutOfBoundsException e) {
                    broken.add(new int[] { value, step });
                }
            }
        }
        Assert.assertEquals("should not have any AIOOB iterations", 0, broken.size());
        for (int[] brk : broken) {
            System.out.println("broken: value=" + brk[0] + " step=" + brk[1]);
        }
    }

    @Test
    public void testVerifyManualAllValuesDuplication() {
        Histogram histogram1 = histogram.copy();

        AbstractHistogram.AllValues values = histogram1.allValues();
        ArrayList<Long> ranges = new ArrayList<Long>();
        ArrayList<Long> counts = new ArrayList<Long>();
        int index = 0;
        for (HistogramIterationValue value : values) {
            if (value.getCountAddedInThisIterationStep() > 0) {
                ranges.add(value.getValueIteratedTo());
                counts.add(value.getCountAddedInThisIterationStep());
            }
            index++;
        }
        Assert.assertEquals("index should be equal to countsArrayLength", histogram1.countsArrayLength, index);

        AbstractHistogram histogram2 = new Histogram(highestTrackableValue, numberOfSignificantValueDigits);
        for (int i = 0; i < ranges.size(); ++i) {
            histogram2.recordValueWithCount(ranges.get(i), counts.get(i));
        }

        Assert.assertTrue("Histograms should be equal", histogram1.equals(histogram2));
    }
    
    @Test
    public void testLinearIteratorVisitsBucketsWiderThanStepSizeMultipleTimes() {
        Histogram h = new Histogram(1, Long.MAX_VALUE, 3);

        h.recordValue(1);
        h.recordValue(2047);
        // bucket size 2
        h.recordValue(2048);
        h.recordValue(2049);
        h.recordValue(4095);
        // bucket size 4
        h.recordValue(4096);
        h.recordValue(4097);
        h.recordValue(4098);
        h.recordValue(4099);
        // 2nd bucket in size 4
        h.recordValue(4100);

        // sadly verbose helper class to hang on to iteration information for later comparison
        class IteratorValueSnapshot {
            private final long value;
            private final long count;

            private IteratorValueSnapshot(HistogramIterationValue iv) {
                this.value = iv.getValueIteratedTo();
                this.count = iv.getCountAddedInThisIterationStep();
            }
            
            private IteratorValueSnapshot(long value, long count) {
                this.value = value;
                this.count = count;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) { return true; }
                if (o == null || getClass() != o.getClass()) { return false; }

                IteratorValueSnapshot that = (IteratorValueSnapshot) o;

                if (value != that.value) { return false; }
                return count == that.count;
            }

            @Override
            public int hashCode() {
                int result = (int) (value ^ (value >>> 32));
                result = 31 * result + (int) (count ^ (count >>> 32));
                return result;
            }

            @Override
            public String toString() {
                return "IteratorValueSnapshot{" +
                        "value=" + value +
                        ", count=" + count +
                        '}';
            }
        }

        List<IteratorValueSnapshot> snapshots = new ArrayList<IteratorValueSnapshot>();

        for (HistogramIterationValue iv : h.linearBucketValues(1)) {
            snapshots.add(new IteratorValueSnapshot(iv));
        }

        // bucket size 1
        Assert.assertEquals(new IteratorValueSnapshot(0, 0), snapshots.get(0));
        Assert.assertEquals(new IteratorValueSnapshot(1, 1), snapshots.get(1));
        Assert.assertEquals(new IteratorValueSnapshot(2046, 0), snapshots.get(2046));
        Assert.assertEquals(new IteratorValueSnapshot(2047, 1), snapshots.get(2047));
        // bucket size 2
        Assert.assertEquals(new IteratorValueSnapshot(2048, 2), snapshots.get(2048));
        Assert.assertEquals(new IteratorValueSnapshot(2049, 0), snapshots.get(2049));
        Assert.assertEquals(new IteratorValueSnapshot(2050, 0), snapshots.get(2050));
        Assert.assertEquals(new IteratorValueSnapshot(2051, 0), snapshots.get(2051));
        Assert.assertEquals(new IteratorValueSnapshot(4094, 1), snapshots.get(4094));
        Assert.assertEquals(new IteratorValueSnapshot(4095, 0), snapshots.get(4095));
        // bucket size 4
        Assert.assertEquals(new IteratorValueSnapshot(4096, 4), snapshots.get(4096));
        Assert.assertEquals(new IteratorValueSnapshot(4097, 0), snapshots.get(4097));
        Assert.assertEquals(new IteratorValueSnapshot(4098, 0), snapshots.get(4098));
        Assert.assertEquals(new IteratorValueSnapshot(4099, 0), snapshots.get(4099));
        // also size 4, last bucket
        Assert.assertEquals(new IteratorValueSnapshot(4100, 1), snapshots.get(4100));
        Assert.assertEquals(new IteratorValueSnapshot(4101, 0), snapshots.get(4101));
        Assert.assertEquals(new IteratorValueSnapshot(4102, 0), snapshots.get(4102));
        Assert.assertEquals(new IteratorValueSnapshot(4103, 0), snapshots.get(4103));

        Assert.assertEquals(4104, snapshots.size());
    }
}
