package ru.ifmo.nds.tests;

import java.util.Arrays;
import java.util.Random;
import java.util.function.Function;

import org.junit.Assert;
import org.junit.Test;

import ru.ifmo.nds.util.ArrayHelper;
import ru.ifmo.nds.util.median.DestructiveMedianAlgorithm;
import ru.ifmo.nds.util.median.DestructiveMedianFactory;

public abstract class MedianTestsBase {
    protected abstract DestructiveMedianFactory getFactory();
    private double destructiveMedian(double[] array, int until) {
        return getFactory().createInstance(until).solve(array, 0, until);
    }

    private void checkMedian(Function<Random, Double> generator) {
        Random random = new Random();
        double[] medianSwap = new double[100];

        for (int times = 0; times < 100; ++times) {
            for (int size = 1; size <= 100; ++size) {
                double[] points = new double[size];
                int[] indices = new int[size];
                for (int i = 0; i < size; ++i) {
                    points[i] = generator.apply(random);
                    indices[i] = i;
                }
                for (int i = 0; i < size; ++i) {
                    ArrayHelper.swap(indices, i, random.nextInt(i + 1));
                }

                ArrayHelper.transplant(points, indices, 0, size, medianSwap, 0);
                double median = destructiveMedian(medianSwap, size);
                double[] originalPoints = points.clone();
                Arrays.sort(points);
                Assert.assertEquals(Arrays.toString(originalPoints), points[size / 2], median, 1e-16);
            }
        }
    }

    @Test
    public void maximumSizeIsCorrect() {
        DestructiveMedianFactory factory = getFactory();
        int maximumSize = 1000;
        DestructiveMedianAlgorithm algorithm = factory.createInstance(maximumSize);
        Assert.assertTrue("The maximum size is smaller than expected", algorithm.maximumSize() >= maximumSize);
    }

    @Test
    public void findsMedianInRandomSequencesOK() {
        checkMedian(Random::nextDouble);
    }

    @Test
    public void findsMedianInBinarySequencesOK() {
        checkMedian(random -> random.nextBoolean() ? 0.0 : 1.0);
    }

    @Test
    public void findsMedianInSmallDomainSequencesOK() {
        checkMedian(random -> (double) random.nextInt(10));
    }
}
