/*
 * Java Genetic Algorithm Library (@__identifier__@).
 * Copyright (c) @__year__@ Franz Wilhelmstötter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Author:
 *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
 */
package io.jenetics.util;

import static io.jenetics.internal.math.Randoms.indexes;

import java.util.PrimitiveIterator.OfInt;
import java.util.Random;
import java.util.random.RandomGenerator;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import io.jenetics.internal.math.Probabilities;
import io.jenetics.internal.util.IntRef;
import io.jenetics.stat.Histogram;
import io.jenetics.stat.LongMomentStatistics;

/**
 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
 */
public class RandomIndexStreamTest {

	@Test
	public void compatibility() {
		final TestData data = TestData.of("/io/jenetics/util/IndexStream.Random");

		for (String[] line : data) {
			final Random random = new Random(0);
			final double p = Double.parseDouble(line[0]);

			final OfInt it = indexes(random, 500, p).iterator();
			for (int i = 1; i < line.length; ++i) {
				final int index = Integer.parseInt(line[i]);
				Assert.assertEquals(it.nextInt(), index);
			}

			Assert.assertFalse(it.hasNext());
		}
	}

	@Test
	public void reference() {
		final int size = 5000;
		final double p = 0.5;

		final Random random1 = new Random(0);
		final Random random2 = new Random(0);

		for (int j = 0; j < 1; ++j) {
			final OfInt it = indexes(random1, size, p).iterator();
			final IndexStream stream2 = ReferenceRandomStream(
				size, p, random2
			);

			while (it.hasNext()) {
				Assert.assertEquals(it.nextInt(), stream2.next());
			}

			Assert.assertFalse(it.hasNext());
			Assert.assertEquals(stream2.next(), -1);
		}
	}

	@Test(dataProvider = "probabilities")
	public void distribution(final Integer n, final Double p) {
		final double mean = n*p;
		final double var = n*p*(1 - p);

		final Random random = new Random();
		final Range<Long> domain = new Range<>(0L, n.longValue());

		final Histogram<Long> histogram = Histogram.ofLong(domain.getMin(), domain.getMax(), 10);
		final LongMomentStatistics variance = new LongMomentStatistics();
		for (int i = 0; i < 2500; ++i) {
			final long k = k(n, p, random);

			histogram.accept(k);
			variance.accept(k);
		}

		// Normal distribution as approximation for binomial distribution.
		// TODO: Implement test
		//assertDistribution(histogram, new NormalDistribution<>(domain, mean, var));
	}

	double var(final double p, final long N) {
		return N*p*(1.0 - p);
	}

	double mean(final double p, final long N) {
		return N*p;
	}

	long k(final int n, final double p, final RandomGenerator random) {
		final IntRef kt = new IntRef(0);
		indexes(random, n, p).forEach(i -> ++kt.value);

		return kt.value;
	}

	@DataProvider(name = "probabilities")
	public Object[][] probabilities() {
		return new Object[][] {
			// n,      p
			{1115,  0.015},
			{1150,  0.015},
			{1160,  0.015},
			{1170,  0.015},
			{11100, 0.015},
			{11200, 0.015},
			{11500, 0.015},

			{1115,  0.15},
			{1150,  0.15},
			{1160,  0.15},
			{1170,  0.15},
			{11100, 0.15},
			{11200, 0.15},
			{11500, 0.15},

			{515,   0.5},
			{1115,  0.5},
			{1150,  0.5},
			{1160,  0.5},
			{1170,  0.5},
			{11100, 0.5},
			{11200, 0.5},
			{11500, 0.5},

			{515,   0.85},
			{1115,  0.85},
			{1150,  0.85},
			{1160,  0.85},
			{1170,  0.85},
			{11100, 0.85},
			{11200, 0.85},
			{11500, 0.85},

			{515,   0.99},
			{1115,  0.99},
			{1150,  0.99},
			{1160,  0.99},
			{1170,  0.99},
			{11100, 0.99},
			{11200, 0.99},
			{11500, 0.99}
		};
	}


	public static IndexStream ReferenceRandomStream(
		final int n,
		final double p,
		final RandomGenerator random
	) {
		return new IndexStream() {
			private final int P = Probabilities.toInt(p);
			private int _pos = -1;
			@Override public int next() {
				while (_pos < n && random.nextInt() >= P) {
					++_pos;
				}
				return (_pos < n - 1) ? ++_pos : -1;
			}

		};
	}

	interface IndexStream {
		int next();
	}


	public static void main(final String[] args) {
		final int delta = 500;

		for (int i = 0; i <= delta; ++i) {
			final double p = (double)(i)/(double)delta;
			final Random random = new Random(0);
			final IndexStream stream = ReferenceRandomStream(delta, p, random);

			System.out.print(p);
			System.out.print(",");
			for (int j = stream.next(); j != -1; j = stream.next()) {
				System.out.print(j);
				System.out.print(",");
			}
			System.out.println();
		}
	}

}
