// Path: src/main/java/org/killbill/billing/plugin/meter/timeline/samples/SampleOpcode.java
// public enum SampleOpcode {
//     BYTE(1, 1),
//     SHORT(2, 2),
//     INT(3, 4),
//     LONG(4, 8),
//     FLOAT(5, 4),
//     DOUBLE(6, 8),
//     STRING(7, 0),
//     NULL(8, 0, true),
//     FLOAT_FOR_DOUBLE(10, 4, DOUBLE),
//     HALF_FLOAT_FOR_DOUBLE(11, 2, DOUBLE),
//     BYTE_FOR_DOUBLE(12, 1, DOUBLE),
//     SHORT_FOR_DOUBLE(13, 2, DOUBLE),
//     BIGINT(14, 0),
//     DOUBLE_ZERO(15, 0, true),
//     INT_ZERO(16, 0, true),
//     REPEAT_BYTE(0xff, 1, true),   // A repeat operation in which the repeat count fits in an unsigned byte
//     REPEAT_SHORT(0xfe, 2, true);  // A repeat operation in which the repeat count fits in an unsigned short
// 
//     private static final Logger log = LoggerFactory.getLogger(SampleOpcode.class);
// 
//     private int opcodeIndex;
//     private final int byteSize;
//     private final boolean repeater;
//     private final boolean noArgs;
//     private final SampleOpcode replacement;
// 
//     private SampleOpcode(int opcodeIndex, int byteSize) {
//         this(opcodeIndex, byteSize, false);
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, boolean noArgs) {
//         this(opcodeIndex, byteSize, noArgs, false);
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, boolean noArgs, boolean repeater) {
//         this.opcodeIndex = opcodeIndex;
//         this.byteSize = byteSize;
//         this.noArgs = noArgs;
//         this.repeater = repeater;
//         this.replacement = this;
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, SampleOpcode replacement) {
//         this(opcodeIndex, byteSize, false, false, replacement);
//     }
// 
//     private SampleOpcode(final int opcodeIndex, final int byteSize, final boolean noArgs, final boolean repeater, final SampleOpcode replacement) {
//         this.opcodeIndex = opcodeIndex;
//         this.byteSize = byteSize;
//         this.noArgs = noArgs;
//         this.repeater = repeater;
//         this.replacement = replacement;
//     }
// 
//     public static SampleOpcode getOpcodeFromIndex(final int index) {
//         for (SampleOpcode opcode : values()) {
//             if (opcode.getOpcodeIndex() == index) {
//                 return opcode;
//             }
//         }
// 
//         final String s = String.format("In SampleOpcode.getOpcodefromIndex(), could not find opcode for index %d", index);
//         log.error(s);
//         throw new IllegalArgumentException(s);
//     }
// 
//     public int getOpcodeIndex() {
//         return opcodeIndex;
//     }
// 
//     public int getByteSize() {
//         return byteSize;
//     }
// 
//     public boolean getNoArgs() {
//         return noArgs;
//     }
// 
//     public boolean getRepeater() {
//         return repeater;
//     }
// 
//     public SampleOpcode getReplacement() {
//         return replacement;
//     }
// 
//     @Override
//     public String toString() {
//         final StringBuilder sb = new StringBuilder();
//         sb.append("SampleOpcode");
//         sb.append("{opcodeIndex=").append(opcodeIndex);
//         sb.append(", byteSize=").append(byteSize);
//         sb.append(", repeater=").append(repeater);
//         sb.append(", noArgs=").append(noArgs);
//         sb.append(", replacement=").append(replacement.name());
//         sb.append('}');
//         return sb.toString();
//     }
// }


import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
import org.killbill.billing.plugin.meter.MeterTestSuiteNoDB;
import org.killbill.billing.plugin.meter.api.DecimationMode;
import org.killbill.billing.plugin.meter.timeline.consumer.TimeRangeSampleProcessor;
import org.killbill.billing.plugin.meter.timeline.samples.SampleOpcode;
import org.skife.config.TimeSpan;
import org.testng.Assert;
import org.testng.annotations.Test;
     * @throws Exception
     */
    @Test(groups = "fast")
    public void testFilterWithNonAlignedSampleCounts() throws Exception {
        final List<Double> outputs = new ArrayList<Double>();
        final long millisStart = System.currentTimeMillis() - 2000 * 21;

        final DecimatingSampleFilter filter = new DecimatingSampleFilter(new DateTime(millisStart), new DateTime(millisStart + 2000 * 21), 6, 21, new TimeSpan("2s"), DecimationMode.PEAK_PICK,
                                                                         new TimeRangeSampleProcessor() {

                                                                             @Override
                                                                             public void processOneSample(final DateTime time, final SampleOpcode opcode, final Object value) {
                                                                                 outputs.add((double) ((Double) value));
                                                                             }
                                                                         }
        );
        for (int i = 0; i < 21; i++) {
            // Make the value go up for 6 samples; then down for 6 samples, between 10.0 and 60.0
            final int index = (i % 6) + 1;
            double value = 0;
            if (index <= 3) {
                value = 10.0 * index;
            } else {
                value = (6 - (index - 1)) * 10;
            }
            //System.out.printf("For i %d, index %d, adding value %f\n", i, index, value);
            filter.processOneSample(new DateTime(millisStart + 2000 * i), SampleOpcode.DOUBLE, value);
        }
        Assert.assertEquals(outputs.size(), 5);
        final double[] expectedValues = new double[]{30.0, 20.0, 30.0, 30.0, 10.0};
// Path: src/main/java/org/killbill/billing/plugin/meter/timeline/samples/SampleOpcode.java
// public enum SampleOpcode {
//     BYTE(1, 1),
//     SHORT(2, 2),
//     INT(3, 4),
//     LONG(4, 8),
//     FLOAT(5, 4),
//     DOUBLE(6, 8),
//     STRING(7, 0),
//     NULL(8, 0, true),
//     FLOAT_FOR_DOUBLE(10, 4, DOUBLE),
//     HALF_FLOAT_FOR_DOUBLE(11, 2, DOUBLE),
//     BYTE_FOR_DOUBLE(12, 1, DOUBLE),
//     SHORT_FOR_DOUBLE(13, 2, DOUBLE),
//     BIGINT(14, 0),
//     DOUBLE_ZERO(15, 0, true),
//     INT_ZERO(16, 0, true),
//     REPEAT_BYTE(0xff, 1, true),   // A repeat operation in which the repeat count fits in an unsigned byte
//     REPEAT_SHORT(0xfe, 2, true);  // A repeat operation in which the repeat count fits in an unsigned short
// 
//     private static final Logger log = LoggerFactory.getLogger(SampleOpcode.class);
// 
//     private int opcodeIndex;
//     private final int byteSize;
//     private final boolean repeater;
//     private final boolean noArgs;
//     private final SampleOpcode replacement;
// 
//     private SampleOpcode(int opcodeIndex, int byteSize) {
//         this(opcodeIndex, byteSize, false);
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, boolean noArgs) {
//         this(opcodeIndex, byteSize, noArgs, false);
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, boolean noArgs, boolean repeater) {
//         this.opcodeIndex = opcodeIndex;
//         this.byteSize = byteSize;
//         this.noArgs = noArgs;
//         this.repeater = repeater;
//         this.replacement = this;
//     }
// 
//     private SampleOpcode(int opcodeIndex, int byteSize, SampleOpcode replacement) {
//         this(opcodeIndex, byteSize, false, false, replacement);
//     }
// 
//     private SampleOpcode(final int opcodeIndex, final int byteSize, final boolean noArgs, final boolean repeater, final SampleOpcode replacement) {
//         this.opcodeIndex = opcodeIndex;
//         this.byteSize = byteSize;
//         this.noArgs = noArgs;
//         this.repeater = repeater;
//         this.replacement = replacement;
//     }
// 
//     public static SampleOpcode getOpcodeFromIndex(final int index) {
//         for (SampleOpcode opcode : values()) {
//             if (opcode.getOpcodeIndex() == index) {
//                 return opcode;
//             }
//         }
// 
//         final String s = String.format("In SampleOpcode.getOpcodefromIndex(), could not find opcode for index %d", index);
//         log.error(s);
//         throw new IllegalArgumentException(s);
//     }
// 
//     public int getOpcodeIndex() {
//         return opcodeIndex;
//     }
// 
//     public int getByteSize() {
//         return byteSize;
//     }
// 
//     public boolean getNoArgs() {
//         return noArgs;
//     }
// 
//     public boolean getRepeater() {
//         return repeater;
//     }
// 
//     public SampleOpcode getReplacement() {
//         return replacement;
//     }
// 
//     @Override
//     public String toString() {
//         final StringBuilder sb = new StringBuilder();
//         sb.append("SampleOpcode");
//         sb.append("{opcodeIndex=").append(opcodeIndex);
//         sb.append(", byteSize=").append(byteSize);
//         sb.append(", repeater=").append(repeater);
//         sb.append(", noArgs=").append(noArgs);
//         sb.append(", replacement=").append(replacement.name());
//         sb.append('}');
//         return sb.toString();
//     }
// }


// Path: src/test/java/org/killbill/billing/plugin/meter/timeline/consumer/filter/TestDecimatingFilter.java
import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
import org.killbill.billing.plugin.meter.MeterTestSuiteNoDB;
import org.killbill.billing.plugin.meter.api.DecimationMode;
import org.killbill.billing.plugin.meter.timeline.consumer.TimeRangeSampleProcessor;
import org.killbill.billing.plugin.meter.timeline.samples.SampleOpcode;
import org.skife.config.TimeSpan;
import org.testng.Assert;
import org.testng.annotations.Test;

     * @throws Exception
     */
    @Test(groups = "fast")
    public void testFilterWithNonAlignedSampleCounts() throws Exception {
        final List<Double> outputs = new ArrayList<Double>();
        final long millisStart = System.currentTimeMillis() - 2000 * 21;

        final DecimatingSampleFilter filter = new DecimatingSampleFilter(new DateTime(millisStart), new DateTime(millisStart + 2000 * 21), 6, 21, new TimeSpan("2s"), DecimationMode.PEAK_PICK,
                                                                         new TimeRangeSampleProcessor() {

                                                                             @Override
                                                                             public void processOneSample(final DateTime time, final SampleOpcode opcode, final Object value) {
                                                                                 outputs.add((double) ((Double) value));
                                                                             }
                                                                         }
        );
        for (int i = 0; i < 21; i++) {
            // Make the value go up for 6 samples; then down for 6 samples, between 10.0 and 60.0
            final int index = (i % 6) + 1;
            double value = 0;
            if (index <= 3) {
                value = 10.0 * index;
            } else {
                value = (6 - (index - 1)) * 10;
            }
            //System.out.printf("For i %d, index %d, adding value %f\n", i, index, value);
            filter.processOneSample(new DateTime(millisStart + 2000 * i), SampleOpcode.DOUBLE, value);
        }
        Assert.assertEquals(outputs.size(), 5);
        final double[] expectedValues = new double[]{30.0, 20.0, 30.0, 30.0, 10.0};
        for (int i = 0; i < 5; i++) {