package fj.data.properties;
import fj.*;
import fj.data.Either;
import fj.data.Stream;
import fj.test.Gen;
import fj.test.Property;
import fj.test.reflect.CheckParams;
import fj.test.runner.PropertyTestRunner;
import org.junit.runner.RunWith;
import static fj.Equal.intEqual;
import static fj.Equal.p1Equal;
import static fj.Equal.streamEqual;
import static fj.Function.compose;
import static fj.Function.identity;
import static fj.Ord.intOrd;
import static fj.data.Stream.nil;
import static fj.data.Stream.single;
import static fj.test.Arbitrary.*;
import static fj.test.Property.implies;
import static fj.test.Property.prop;
import static fj.test.Property.property;
import static java.lang.Math.abs;
@RunWith(PropertyTestRunner.class)
@CheckParams(maxSize = 10000)
public class StreamProperties {
private static final Equal<Stream<Integer>> eq = streamEqual(intEqual);
public Property isEmpty() {
return property(arbStream(arbInteger), stream -> prop(stream.isEmpty() != stream.isNotEmpty()));
}
public Property isNotEmpty() {
return property(arbStream(arbInteger), stream ->
prop(stream.length() > 0 == stream.isNotEmpty()));
}
public Property orHead() {
return property(arbStream(arbInteger), arbInteger, (stream, n) ->
implies(stream.isNotEmpty(), () -> prop(intEqual.eq(stream.orHead(() -> n), stream.head()))));
}
public Property orTail() {
final Equal<P1<Stream<Integer>>> eq = p1Equal(streamEqual(intEqual));
return property(arbStream(arbInteger), arbP1(arbStream(arbInteger)), (stream, stream2) ->
implies(stream.isNotEmpty(), () -> prop(eq.eq(stream.orTail(stream2), stream.tail()))));
}
public Property bindStackOverflow() {
return property(arbInteger, n -> {
final Stream<Integer> stream = Stream.range(1, abs(n));
final Stream<Integer> bound = stream.bind(Stream::single);
return prop(stream.zip(bound).forall(p2 -> intEqual.eq(p2._1(), p2._2())));
});
}
public Property toOption() {
return property(arbStream(arbInteger), stream ->
prop(stream.toOption().isNone() || intEqual.eq(stream.toOption().some(), stream.head())));
}
public Property toEither() {
return property(arbStream(arbInteger), arbP1(arbInteger), (stream, n) -> {
final Either<Integer, Integer> e = stream.toEither(n);
return prop(e.isLeft() && intEqual.eq(e.left().value(), n._1()) ||
intEqual.eq(e.right().value(), stream.head()));
});
}
public Property consHead() {
return property(arbStream(arbInteger), arbInteger, (stream, n) ->
prop(intEqual.eq(stream.cons(n).head(), n)));
}
public Property consLength() {
return property(arbStream(arbInteger), arbInteger, (stream, n) ->
prop(stream.cons(n).length() == stream.length() + 1));
}
public Property mapId() {
return property(arbStream(arbInteger), stream -> prop(eq.eq(stream.map(identity()), stream)));
}
public Property mapCompose() {
final F<Integer, Integer> f = x -> x + 3;
final F<Integer, Integer> g = x -> x * 4;
return property(arbStream(arbInteger), stream ->
prop(eq.eq(stream.map(compose(f, g)), stream.map(g).map(f))));
}
public Property foreachDoEffect() {
return property(arbStream(arbInteger), stream -> {
int[] acc = {0};
stream.foreachDoEffect(x -> acc[0] += x);
int acc2 = 0;
for (int x : stream) { acc2 += x; }
return prop(intEqual.eq(acc[0], acc2));
});
}
public Property filter() {
final F<Integer, Boolean> predicate = (x -> x % 2 == 0);
return property(arbStream(arbInteger), stream -> prop(stream.filter(predicate).forall(predicate)));
}
public Property filterLength() {
final F<Integer, Boolean> predicate = (x -> x % 2 == 0);
return property(arbStream(arbInteger), stream -> prop(stream.filter(predicate).length() <= stream.length()));
}
public Property bindLeftIdentity() {
final F<Integer, Stream<Integer>> f = (i -> single(-i));
return property(arbStream(arbInteger), arbInteger, (stream, i) ->
prop(eq.eq(single(i).bind(f), f.f(i))));
}
public Property bindRightIdentity() {
return property(arbStream(arbInteger), stream -> prop(eq.eq(stream.bind(Stream::single), stream)));
}
public Property bindAssociativity() {
final F<Integer, Stream<Integer>> f = x -> single(x + 3);
final F<Integer, Stream<Integer>> g = x -> single(x * 4);
return property(arbStream(arbInteger), stream ->
prop(eq.eq(stream.bind(f).bind(g), stream.bind(i -> f.f(i).bind(g)))));
}
@CheckParams(maxSize = 100)
public Property sequence() {
return property(arbStream(arbInteger), arbStream(arbInteger), (stream1, stream2) ->
prop(eq.eq(stream1.sequence(stream2), stream1.bind(__ -> stream2))));
}
public Property append() {
return property(arbStream(arbInteger), arbInteger, (stream, i) ->
prop(eq.eq(single(i).append(stream), stream.cons(i))));
}
public Property foldRight() {
return property(arbStream(arbInteger), stream ->
prop(eq.eq(stream.foldRight((i, s) -> single(i).append(s), nil()), stream)));
}
public Property foldLeft() {
return property(arbStream(arbInteger), stream ->
prop(eq.eq(stream.foldLeft((s, i) -> single(i).append(s), nil()),
stream.reverse().foldRight((i, s) -> single(i).append(s), nil()))));
}
public Property tailLength() {
return property(arbStream(arbInteger), stream ->
implies(stream.isNotEmpty(), () -> prop(stream.tail()._1().length() == stream.length() - 1)));
}
public Property reverseIdentity() {
return property(arbStream(arbInteger), stream ->
prop(eq.eq(stream.reverse().reverse(), stream)));
}
public Property reverse() {
return property(arbStream(arbInteger), arbStream(arbInteger), (stream1, stream2) ->
prop(eq.eq(stream1.append(stream2).reverse(), stream2.reverse().append(stream1.reverse()))));
}
@CheckParams(minSize = 2, maxSize = 10000)
public Property indexTail() {
final Gen<P2<Stream<Integer>, Integer>> gen = arbStream(arbInteger)
.filter(stream -> stream.length() > 1)
.bind(stream -> Gen.choose(1, stream.length() - 1).map(i -> P.p(stream, i)));
return property(gen, pair -> {
final Stream<Integer> stream = pair._1();
final int i = pair._2();
return prop(intEqual.eq(stream.index(i), stream.tail()._1().index(i - 1)));
});
}
public Property forallExists() {
return property(arbStream(arbInteger), stream ->
prop(stream.forall(x -> x % 2 == 0) == !stream.exists(x -> x % 2 != 0)));
}
public Property find() {
return property(arbStream(arbInteger), stream -> prop(stream.find(x -> x % 2 == 0).forall(x -> x % 2 == 0)));
}
@CheckParams(maxSize = 500)
public Property join() {
return property(arbStream(arbStream(arbInteger)), (Stream<Stream<Integer>> stream) ->
prop(eq.eq(stream.foldRight((Stream<Integer> i, P1<Stream<Integer>> s) -> i.append(s._1()), nil()),
Stream.join(stream))));
}
@CheckParams(maxSize = 1000)
public Property sort() {
return property(arbStream(arbInteger), (Stream<Integer> stream) ->
prop(eq.eq(stream.sort(intOrd), stream.toList().sort(intOrd).toStream())));
}
}