package com.knecon.fforesight.service.layoutparser.processor.docstrum.model;

public class Histogram {

    private static final double EPSILON = 1.0e-6;
    private final double min;
    private final double resolution;
    private double[] frequencies;


    public Histogram(double minValue, double maxValue, double resolution) {

        this.min = minValue - EPSILON;
        double delta = maxValue - minValue + 2 * EPSILON;
        int size = Math.max(1, (int) Math.round((maxValue - minValue) / resolution));
        this.resolution = delta / size;
        this.frequencies = new double[size];
    }


    public void kernelSmooth(double[] kernel) {

        double[] newFrequencies = new double[frequencies.length];
        int shift = (kernel.length - 1) / 2;
        for (int i = 0; i < kernel.length; i++) {
            int jStart = Math.max(0, i - shift);
            int jEnd = Math.min(frequencies.length, frequencies.length + i - shift);
            for (int j = jStart; j < jEnd; j++) {
                newFrequencies[j - i + shift] += kernel[i] * frequencies[j];
            }
        }
        frequencies = newFrequencies;
    }


    public double[] createGaussianKernel(double length, double stdDeviation) {

        int r = (int) Math.round(length / resolution) / 2;

        int size = 2 * r + 1;
        double[] kernel = new double[size];
        double sum = 0;
        double b = 2 * (stdDeviation / resolution) * (stdDeviation / resolution);
        double a = 1 / Math.sqrt(Math.PI * b);
        for (int i = 0; i < size; i++) {
            kernel[i] = a * Math.exp(-(i - r) * (i - r) / b);
            sum += kernel[i];
        }
        for (int i = 0; i < size; i++) {
            kernel[i] /= sum;
        }
        return kernel;
    }


    public void gaussianSmooth(double windowLength, double stdDeviation) {

        kernelSmooth(createGaussianKernel(windowLength, stdDeviation));
    }


    public void add(double value) {

        frequencies[(int) ((value - min) / resolution)] += 1.0;
    }


    public int getSize() {

        return frequencies.length;
    }


    public double getPeakValue() {

        int peakIndex = 0;
        for (int i = 1; i < frequencies.length; i++) {
            if (frequencies[i] > frequencies[peakIndex]) {
                peakIndex = i;
            }
        }
        int peakEndIndex = peakIndex + 1;
        final double EPS = 0.0001;
        while (peakEndIndex < frequencies.length && Math.abs(frequencies[peakEndIndex] - frequencies[peakIndex]) < EPS) {
            peakEndIndex++;
        }
        return ((double) peakIndex + peakEndIndex) / 2 * resolution + min;
    }

}
