package us.hebi.histogram.visualizer.parser;

import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import org.HdrHistogram.*;

import java.util.ArrayList;
import java.util.List;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleFunction;
import java.util.function.DoubleUnaryOperator;

import static com.google.common.base.Preconditions.*;
import static us.hebi.histogram.visualizer.parser.HistogramTag.convertPercentileToX;

/**
 * Class that sums multiple histograms for creating percentile data.
 *
 * @author Florian Enner < florian @ hebirobotics.com >
 * @since 19 Sep 2016
 */
abstract class HistogramAccumulator {

    static HistogramAccumulator forTypeOf(EncodableHistogram expectedHistogram) {
        checkNotNull(expectedHistogram);
        if (expectedHistogram instanceof DoubleHistogram) {
            return new DoubleHistogramAccumulator((DoubleHistogram) expectedHistogram);
        }
        if (expectedHistogram instanceof AbstractHistogram) {
            return new AbstractHistogramAccumulator((AbstractHistogram) expectedHistogram);
        }
        throw new IllegalArgumentException("Unknown histogram type: " + expectedHistogram.getClass().getSimpleName());
    }

    abstract void addHistogram(EncodableHistogram histogram);

    abstract List<Data<Number, Number>> getPercentileData(
            int percentileOutputTicksPerHalf,
            DoubleUnaryOperator xConversion,
            DoubleUnaryOperator yConversion);

    /**
     * Accumulator for DoubleHistogram intervals
     */
    private static class DoubleHistogramAccumulator extends HistogramAccumulator {

        private DoubleHistogramAccumulator(DoubleHistogram interval) {
            this.accumulatedHistogram = interval.copy();
            accumulatedHistogram.reset();
            interval.setAutoResize(true);
        }

        @Override
        protected void addHistogram(EncodableHistogram histogram) {
            checkArgument(histogram instanceof DoubleHistogram, "Unexpected type");
            accumulatedHistogram.add((DoubleHistogram) histogram);
        }

        @Override
        List<Data<Number, Number>> getPercentileData(int percentileOutputTicksPerHalf,
                                                     DoubleUnaryOperator xConversion,
                                                     DoubleUnaryOperator yConversion) {

            DoublePercentileIterator percentileIterator = new DoublePercentileIterator(
                    accumulatedHistogram,
                    percentileOutputTicksPerHalf);

            List<Data<Number, Number>> percentileData = new ArrayList<>(512);

            while (percentileIterator.hasNext()) {
                DoubleHistogramIterationValue value = percentileIterator.next();

                final double x = xConversion.applyAsDouble(value.getPercentileLevelIteratedTo());
                final double y = yConversion.applyAsDouble(value.getValueIteratedTo());

                if (Double.isInfinite(x))
                    break;

                percentileData.add(new Data<>(x, y));
            }

            return percentileData;
        }

        final DoubleHistogram accumulatedHistogram;

    }

    /**
     * Accumulator for histograms derived from AbstractHistogram, e.g.,
     * - Histogram
     * - IntCountsHistogram
     * - ShortCountsHistogram
     */
    private static class AbstractHistogramAccumulator extends HistogramAccumulator {

        private AbstractHistogramAccumulator(AbstractHistogram interval) {
            accumulatedHistogram = interval.copy();
            accumulatedHistogram.reset();
            accumulatedHistogram.setAutoResize(true);
        }

        @Override
        protected void addHistogram(EncodableHistogram histogram) {
            // NOTE: can a short histogram be added to e.g. an int histogram? Should there be type equality check?
            checkArgument(histogram instanceof AbstractHistogram, "Unexpected type");
            accumulatedHistogram.add((AbstractHistogram) histogram);
        }

        @Override
        List<Data<Number, Number>> getPercentileData(int percentileOutputTicksPerHalf,
                                                     DoubleUnaryOperator xConversion,
                                                     DoubleUnaryOperator yConversion) {
            PercentileIterator percentileIterator = new PercentileIterator(
                    accumulatedHistogram,
                    percentileOutputTicksPerHalf);

            List<Data<Number, Number>> percentileData = new ArrayList<>(512);

            while (percentileIterator.hasNext()) {
                HistogramIterationValue value = percentileIterator.next();

                final double x = xConversion.applyAsDouble(value.getPercentileLevelIteratedTo());
                final double y = yConversion.applyAsDouble(value.getValueIteratedTo());

                if (Double.isInfinite(x))
                    break;

                percentileData.add(new Data<>(x, y));
            }

            return percentileData;
        }

        final AbstractHistogram accumulatedHistogram;

    }


}