package net.ranides.assira.collection.query;

import net.ranides.assira.generic.Ref;
import net.ranides.test.CQueryAssert;
import net.ranides.test.mockup.query.CQueryMockup;
import org.junit.Assert;
import org.junit.Test;

import net.ranides.assira.collection.lists.VirtualList;
import net.ranides.assira.collection.query.base.CQArray;
import net.ranides.assira.collection.query.base.CQArraySupplier;
import net.ranides.assira.collection.query.base.CQIterator;
import net.ranides.assira.collection.query.base.CQListSupplier;
import net.ranides.assira.generic.CompareUtils;
import net.ranides.assira.reflection.*;
import net.ranides.assira.text.StringUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

import static org.junit.Assert.*;

public class CQueryAbstractTest {

    @Test
    public void testContent() {
        Integer[] array = {3, 4, 5, 6};
        List<Integer> expected = new ArrayList<>(Arrays.asList(array));

        CQuery<Integer> a = new CQArray<>(array);
        CQueryAssert.assertEquals(expected, a);

        CQuery<Integer> b = CQIterator.from(Arrays.asList(3, 4, 5, 6)::iterator);
        CQueryAssert.assertEquals(expected, b);

        CQuery<Integer> c = CQArraySupplier.from(true, () -> array);
        CQueryAssert.assertEquals(expected, c);
    }

    @Test
    public void testContentForSpecialFeatures() {
        Integer[] array = {3, 4, 5, 6};
        List<Integer> expected = new ArrayList<>(Arrays.asList(array));

        CQuery<Integer> a = CQueryMockup.of("iterator", array);
        CQueryAssert.assertEquals(expected, a);

        CQuery<Integer> b = CQueryMockup.of("list", array);
        CQueryAssert.assertEquals(expected, b);

        CQuery<Integer> c = CQueryMockup.of("each1", array);
        CQueryAssert.assertEquals(expected, c);

        CQuery<Integer> d = CQueryMockup.of("each2", array);
        CQueryAssert.assertEquals(expected, d);
    }

    @Test
    public void testJoin() {
        Integer[] array = {3, 4, 5, 6};
        List<Integer> expected = new ArrayList<>(Arrays.asList(array));

        CQuery<Integer> a = CQueryMockup.of("stream", array);
        CQueryAssert.assertJoin(expected, a);

        CQuery<Integer> b = CQueryMockup.of("iterator", array);
        CQueryAssert.assertJoin(expected, b);

        CQuery<Integer> c = CQueryMockup.of("each1", array);
        CQueryAssert.assertJoin(expected, c);

        CQuery<Integer> d = CQueryMockup.of("each2", array);
        CQueryAssert.assertJoin(expected, d);
    }

    @Test
    public void testFetch() {
        AtomicInteger counter = new AtomicInteger(0);
        AtomicInteger state = new AtomicInteger(777);
        List<Integer> list = VirtualList.of(5, i -> {
            counter.incrementAndGet();
            return state.get()+i;
        });

        CQuery<Integer> a = CQListSupplier.from(true, () -> list);

        state.set(10);
        CQueryAssert.assertEquals(Arrays.asList(10,11,12,13,14), a);
        state.set(30);
        CQueryAssert.assertEquals(Arrays.asList(30,31,32,33,34), a);
        state.set(70);
        CQueryAssert.assertEquals(Arrays.asList(70,71,72,73,74), a);

        state.set(80);
        CQuery<Integer> b = a.fetch();

        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), a);
        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), b);

        state.set(90);
        CQueryAssert.assertEquals(Arrays.asList(90,91,92,93,94), a);
        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), b);

        int base = counter.get();

        assertEquals(Arrays.asList(90,91,92,93,94), a.list());
        assertEquals(base+5, counter.get());
        assertEquals(Arrays.asList(80,81,82,83,84), b.list());
        assertEquals(base+5, counter.get());

        CQuery<Integer> c = a.fetch();
        assertEquals(base+10, counter.get());
        assertEquals(Arrays.asList(90,91,92,93,94), c.list());
        assertEquals(base+10, counter.get());
        CQuery<Integer> d = b.fetch();
        assertEquals(base+10, counter.get());
        assertEquals(Arrays.asList(80,81,82,83,84), d.list());
        assertEquals(base+10, counter.get());

    }

    @Test
    public void testPrefetch() {
        AtomicInteger counter = new AtomicInteger(0);
        AtomicInteger state = new AtomicInteger(777);
        List<Integer> list = VirtualList.of(5, i -> {
            counter.incrementAndGet();
            return state.get()+i;
        });

        CQuery<Integer> a = CQListSupplier.from(true, () -> list);

        state.set(10);
        CQueryAssert.assertEquals(Arrays.asList(10,11,12,13,14), a);
        state.set(30);
        CQueryAssert.assertEquals(Arrays.asList(30,31,32,33,34), a);
        state.set(70);
        CQueryAssert.assertEquals(Arrays.asList(70,71,72,73,74), a);

        state.set(80);
        CQuery<Integer> b = a.prefetch();

        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), a);
        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), b);

        state.set(90);
        CQueryAssert.assertEquals(Arrays.asList(90,91,92,93,94), a);
        CQueryAssert.assertEquals(Arrays.asList(80,81,82,83,84), b);

        int base = counter.get();

        assertEquals(Arrays.asList(90,91,92,93,94), a.list());
        assertEquals(base+5, counter.get());
        assertEquals(Arrays.asList(80,81,82,83,84), b.list());
        assertEquals(base+5, counter.get());

        CQuery<Integer> c = a.prefetch();
        assertEquals(base+5, counter.get());
        assertEquals(Arrays.asList(90,91,92,93,94), c.list());
        assertEquals(base+10, counter.get());
        CQuery<Integer> d = b.prefetch();
        assertEquals(base+10, counter.get());
        assertEquals(Arrays.asList(80,81,82,83,84), d.list());
        assertEquals(base+10, counter.get());
    }

    @Test
    public void testCache() throws IOException {
        AtomicInteger counter = new AtomicInteger(0);
        AtomicInteger state = new AtomicInteger(777);
        List<Integer> list = VirtualList.of(5, i -> {
            counter.incrementAndGet();
            return state.get()+i;
        });

        Path file = File.createTempFile("cquery2", ".bin").toPath();
        Files.deleteIfExists(file);

        state.set(10);
        CQuery<Integer> a = CQListSupplier.from(true, () -> list).cache(file);
        assertEquals(5, counter.get());

        state.set(20);
        CQueryAssert.assertEquals(Arrays.asList(10,11,12,13,14), a);
        assertEquals(5, counter.get());

        state.set(30);
        CQueryAssert.assertEquals(Arrays.asList(10,11,12,13,14), a);
        assertEquals(5, counter.get());

        CQuery<Integer> b = CQListSupplier.from(true, () -> list).cache(file);
        CQuery<Integer> c = CQListSupplier.from(true, () -> list);

        state.set(40);
        CQueryAssert.assertEquals(Arrays.asList(10,11,12,13,14), b);
        assertEquals(5, counter.get());
        CQueryAssert.assertEquals(Arrays.asList(40,41,42,43,44), c);
        Assert.assertTrue(counter.get() > 5);
    }

    @Test
    public void testGroup() {
        Integer[] array = {4, 5, 6, 7, 8, 9};
        List<Integer> expected = new ArrayList<>(Arrays.asList(array));

        CQueryAssert.assertGroup(expected, CQueryMockup.of("each1", array));
        CQueryAssert.assertGroup(expected, CQueryMockup.of("each2", array));
        CQueryAssert.assertGroup(expected, CQueryMockup.of("stream", array));
        CQueryAssert.assertGroup(expected, CQueryMockup.of("iterator", array));
    }

    @Test
    public void testFilter() {
        CQuery<Character> query2 = CQuery.from().array("ab12cdRTefghPQijklmAZno3456pq".toCharArray())
            .filterEach((i, v) -> i % 2 == 0)
            .filter(Character.class)
            .filter(IClass.CHAR)
            .filter(char.class)
            .discard(v -> Character.isUpperCase(v))
            .discard(v -> Character.isDigit(v))
            .discardEach((i, v) -> i % 4 == 3)
            .discard(Integer.class)
            .discard(IClass.INT);

        CQueryAssert.assertEquals(Arrays.asList('a','c','e','i','k','m','q'), query2);
    }

    @Test
    public void testFilterType() {
        Object[] input = {'a', "b", 'c', "d", StringUtils.repeat(1, 'e'), 1, 2L, 3.0f, 4.0, 5.0f};
        CQuery<Object> query = CQuery.from().values(input);

        CQueryAssert.assertEquals(Arrays.asList(1, 2, 3, 4, 5), query.filter(Number.class).<Number>map(Number::intValue));
        CQueryAssert.assertEquals(Arrays.asList(1, 2, 3, 4, 5), query.filter(IClass.NUMBER).<Number>map(Number::intValue));
        CQueryAssert.assertEquals(Arrays.asList(3,5), query.filter(Float.class).<Number>map(Number::intValue));
        CQueryAssert.assertEquals(Arrays.asList(3,5), query.filter(float.class).<Number>map(Number::intValue));

        CQuery<String> query2 = query.filter(String.class);
        CQueryAssert.assertEquals(Arrays.asList("b", "d"), query2);

        CQuery<String> query3 = query.filter(CharSequence.class).map(CharSequence::toString);
        CQueryAssert.assertEquals(Arrays.asList("b", "d", "e"), query3);
    }

    @Test
    public void testMap() {
        Integer[] input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
        String[] out1 = {"#1", "#2", "#3", "#4", "#5", "#6", "#7", "#8", "#9", "#10", "#11", "#12", "#13", "#14", "#15", "#16", "#17", "#18", "#19", "#20"};
        String[] out2 = {"#2", "#4", "#6", "#8", "#10", "#12", "#14", "#16", "#18", "#20"};
        String[] out3 = {"0#1", "1#2", "2#3", "3#4", "4#5", "5#6", "6#7", "7#8", "8#9", "9#10", "10#11", "11#12", "12#13", "13#14", "14#15", "15#16", "16#17", "17#18", "18#19", "19#20"};
        String[] out4 = {"0#2", "1#4", "2#6", "3#8", "4#10", "5#12", "6#14", "7#16", "8#18", "9#20"};

        Integer[] out5 = {1, 702, 3, 704, 5, 706, 7, 708, 9, 710, 11, 712, 13, 714, 15, 716, 17, 718, 19, 720};
        Integer[] out6 = {1, 702, 3,      5,      7, 708, 9,      11,      13, 714, 15,      17,      19, 720};
        Integer[] out8 = {2, 10004, 6, 30008, 10, 50012, 14, 70016, 18, 90020};

        CQueryAssert.assertEquals(out1, CQuery.from().values(input).map(v -> "#"+v));
        CQueryAssert.assertEquals(out2, CQuery.from().values(input).mapOptional(v -> Ref.of("#"+v).filter(x -> 0==v%2) ));
        CQueryAssert.assertEquals(out3, CQuery.from().values(input).mapEach((i,v) -> i+ "#"+v));
        CQueryAssert.assertEquals(out4, CQuery.from().values(input).filter(v -> v%2==0).mapEach((i,v) -> i+ "#"+v));

        CQueryAssert.assertEquals(out5, CQuery.from().values(input).mapIf(v -> v%2==0, v -> 700+v));
        CQueryAssert.assertEquals(out8, CQuery.from().values(input).filter(v -> v%2==0).mapEachIf((i,v) -> i%2==1, (i,v) -> 10000*i + v));
        CQueryAssert.assertEquals(out8, CQuery.from().values(input).filter(v -> v%2==0).mapEachIf((i,v) -> v%4==0, (i,v) -> 10000*i + v));

        CQueryAssert.assertEquals(out5, CQuery.from().values(input).mapOptionalIf(v -> v%2==0, (v) -> Ref.of(700+v)));
        CQueryAssert.assertEquals(out6, CQuery.from().values(input).mapOptionalIf(v -> v%2==0, (v) -> Ref.of(700+v).filter(z -> z%3 == 0) ));
    }

    @Test
    public void testFlat() {
        Integer[] input = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        Integer[] out1 = {101, 201, 102, 202, 103, 203, 104, 204, 105, 205, 106, 206, 107, 207, 108, 208, 109, 209, 110, 210};
        Integer[] out2 = {1, 102, 202, 3, 104, 204, 5, 106, 206, 7, 108, 208, 9, 110, 210};

        CQueryAssert.assertEquals(out1, CQuery.from().values(input).flat(v -> CQuery.from().values(v+100, v+200)));
        CQueryAssert.assertEquals(out1, CQuery.from().values(input).flatIterable(v -> Arrays.asList(v+100, v+200)));
        CQueryAssert.assertEquals(out1, CQuery.from().values(input).flatArray(v -> new Integer[]{v+100, v+200}));

        CQueryAssert.assertEquals(out2, CQuery.from().values(input).flatIf(v -> v%2==0, v -> CQuery.from().values(v+100, v+200)));
        CQueryAssert.assertEquals(out2, CQuery.from().values(input).flatIterableIf(v -> v%2==0, v -> Arrays.asList(v+100, v+200)));
        CQueryAssert.assertEquals(out2, CQuery.from().values(input).flatArrayIf(v -> v%2==0, v -> new Integer[]{v+100, v+200}));
    }

    @Test
    public void testReduce() {
        assertReduce("each1");
        assertReduce("each2");
        assertReduce("stream");
        assertReduce("iterator");
    }

    private void assertReduce(String features) {
        Integer[] input = {1, 2, 3, 4, 5, 6, 7, 8, 9};

        assertEquals(1045, CQueryMockup.of(features, input).reduce(1000, (a,b) -> a + b).intValue());
        assertEquals(45, CQueryMockup.of(features, input).reduce((a,b) -> a + b).intValue());

        assertEquals(2123456789, CQueryMockup.of(features, input).reduce(2, (a,b) -> 10*a + b).intValue());
        assertEquals(20123456789L, CQueryMockup.of(features, input).map(v -> (long)v).reduce(20L, (a,b) -> 10*a + b).longValue());

        assertEquals(10203040506070809L, CQueryMockup.of(features, input).map(Long::valueOf).reduce((a,b) -> 100L*a + b).longValue());
    }

    @Test
    public void testMin() {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};

        assertEquals((Integer)1, CQuery.from().values(input).min(Integer::compareTo));
        assertEquals((Integer)9, CQuery.from().values(input).max(Integer::compareTo));
    }

    @Test
    public void testSingle() {
        assertEquals((Integer)1, CQuery.from().values(1).single().get());
        assertEquals((Integer)1, CQuery.from().values(1,2,3).limit(1).single().get());

        assertFalse(CQuery.from().empty().single().isPresent());

        assertFalse(CQuery.from().values(1, 2).single().isPresent());
    }

    @Test
    public void testSingleAmbiguous() {
        Supplier<RuntimeException> oh = () -> new RuntimeException("oh");

        assertEquals((Integer)1, CQuery.from().values(1).single(oh).get());
        assertEquals((Integer)1, CQuery.from().values(1,2,3).limit(1).single(oh).get());

        assertFalse(CQuery.from().empty().single(oh).isPresent());

        assertThrows(RuntimeException.class, () -> {
            assertFalse(CQuery.from().values(1, 2).single(oh).isPresent());
        });

    }

    @Test
    public void testSort() {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out1 =  {1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 7, 8, 9};
        Integer[] out2 =  {9, 8, 7, 7, 6, 6, 5, 4, 4, 3, 2, 2, 1};

        CQueryAssert.assertEquals(out1, CQuery.from().values(input).sort());
        CQueryAssert.assertEquals(out1, CQueryMockup.of("", input).sort());

        Comparator<Object> cmp = CompareUtils.comparator().reversed();
        CQueryAssert.assertEquals(out2, CQuery.from().values(input).sort(cmp));
        CQueryAssert.assertEquals(out2, CQueryMockup.of("", input).sort(cmp));
    }

    @Test
    public void testReverse() {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out1 =  {4, 8, 7, 6, 1, 5, 2, 9, 4, 3, 2, 6, 7};

        CQueryAssert.assertEquals(out1, CQuery.from().values(input).reverse());
        CQueryAssert.assertEquals(out1, CQueryMockup.of("", input).reverse());
        CQueryAssert.assertEquals(out1, CQueryMockup.of("stream", input).reverse());
        CQueryAssert.assertEquals(out1, CQueryMockup.of("each1", input).reverse());
    }

    @Test
    public void testLimit() {
        assertLimitLength("");
        assertLimitLength("list");
        assertLimitLength("stream");

        assertLimitPredicate("");
        assertLimitPredicate("list");
        assertLimitPredicate("stream");
    }

    private void assertLimitLength(String features) {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out1 =  {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out2 =  {7, 6, 2, 3, 4};
        Integer[] out3 =  {7};
        Integer[] out4 =  {};

        CQueryAssert.assertEquals(out1, CQueryMockup.of(features, input).limit(1000));
        CQueryAssert.assertEquals(out2, CQueryMockup.of(features, input).limit(5));
        CQueryAssert.assertEquals(out3, CQueryMockup.of(features, input).limit(1));
        CQueryAssert.assertEquals(out4, CQueryMockup.of(features, input).limit(0));
    }

    private void assertLimitPredicate(String features) {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out1 =  {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};
        Integer[] out2 =  {7, 6, 2, 3, 4};
        Integer[] out3 =  {7};
        Integer[] out4 =  {};

        CQueryAssert.assertEquals(out1, CQueryMockup.of(features, input).limit(v -> v!=0));
        CQueryAssert.assertEquals(out2, CQueryMockup.of(features, input).limit(v -> v!=9));
        CQueryAssert.assertEquals(out3, CQueryMockup.of(features, input).limit(v -> v!=6));
        CQueryAssert.assertEquals(out4, CQueryMockup.of(features, input).limit(v -> false));
    }

    @Test
    public void testOnEach() {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};

        List<Integer> out1 = new ArrayList<>();
        assertEquals(64, CQuery.from().values(input).onEach(v -> out1.add(v)).reduce(Integer::sum).intValue());
        assertEquals(Arrays.asList(7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4), out1);

        List<Integer> out2 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input).onEach(v -> out2.add(v)).limit(4).reduce(Integer::sum).intValue());
        assertEquals(Arrays.asList(7,6,2,3), out2);

        List<Integer> out3 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input).limit(4).onEach(v -> out3.add(v)).reduce(Integer::sum).intValue());
        assertEquals(Arrays.asList(7,6,2,3), out3);


        List<String> out4 = new ArrayList<>();
        assertEquals(64, CQuery.from().values(input)
            .onEach((i,v) -> out4.add(i+"#"+v))
            .reduce(Integer::sum).intValue()
        );
        assertEquals(Arrays.asList("0#7", "1#6", "2#2", "3#3", "4#4", "5#9", "6#2", "7#5", "8#1", "9#6", "10#7", "11#8", "12#4"), out4);


        List<String> out5 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input)
            .limit(4).onEach((i, v) -> out5.add(i + "#" + v))
            .reduce(Integer::sum).intValue()
        );
        assertEquals(Arrays.asList("0#7", "1#6", "2#2", "3#3"), out5);

        List<String> out6 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input)
            .onEach((i, v) -> out6.add(i + "#" + v)).limit(4)
            .reduce(Integer::sum).intValue()
        );

        assertEquals(Arrays.asList("0#7", "1#6", "2#2", "3#3"), out6);

    }

    @Test
    public void testOnEachSkip() {
        Integer[] input = {7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4};

        List<Integer> out3 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input)
            .skip(2)
            .limit(4)
            .onEach(v -> out3.add(v))
            .reduce(Integer::sum).intValue()
        );
        assertEquals(Arrays.asList(2,3,4,9), out3);

        List<String> out5 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input)
            .limit(6)
            .skip(2)
            .onEach((i, v) -> {
                out5.add(i + "#" + v);
            })
            .reduce(Integer::sum).intValue()
        );
        assertEquals(Arrays.asList("0#2", "1#3", "2#4", "3#9"), out5);

        List<String> out6 = new ArrayList<>();
        assertEquals(18, CQuery.from().values(input)
            .slice(2, 6)
            .onEach((i, v) -> {
                out6.add(i + "#" + v);
            })
            .reduce(Integer::sum).intValue()
        );
        assertEquals(Arrays.asList("0#2", "1#3", "2#4", "3#9"), out6);
    }

    @Test
    public void testInto() {
        List<Integer> input = Arrays.asList(7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4);

        List<Integer> out1 = new ArrayList<>();
        CQueryMockup.of("stream", input).into(out1);
        assertEquals(input, out1);

        List<Integer> out2 = new ArrayList<>();
        CQueryMockup.of("each1", input).into(out2);
        assertEquals(input, out2);

        List<Integer> out3 = new ArrayList<>();
        CQueryMockup.of("iterator", input).into(out3);
        assertEquals(input, out3);

        List<Integer> out4 = new ArrayList<>();
        CQueryMockup.of("list", input).into(out4);
        assertEquals(input, out4);
    }

    @Test
    public void testApply() {
        CQuery<Integer> query = CQuery.from().values(1, 2, 3);

        CQueryFunction<Integer, Integer> cqf = a -> a.map(v -> 2 * v);

        assertTrue(query.test(q -> q.size() == 3));
        CQueryAssert.assertEquals(Arrays.asList(2,4,6), query.apply(cqf));
    }

    @Test
    public void testMatch() {
        List<Integer> input = Arrays.asList(7, 6, 2, 3, 4, 9, 2, 5, 1, 6, 7, 8, 4);

        assertTrue(CQueryMockup.of("stream", input).matchAny(v -> v==9));
        assertTrue(CQueryMockup.of("iterator", input).matchAny(v -> v==9));
        assertTrue(CQueryMockup.of("each1", input).matchAny(v -> v==9));

        assertFalse(CQueryMockup.of("stream", input).matchAny(v -> v==0));
        assertFalse(CQueryMockup.of("iterator", input).matchAny(v -> v==0));
        assertFalse(CQueryMockup.of("each1", input).matchAny(v -> v==0));

        assertTrue(CQueryMockup.of("stream", input).matchAll(v -> v<10));
        assertTrue(CQueryMockup.of("iterator", input).matchAll(v -> v<10));
        assertTrue(CQueryMockup.of("each1", input).matchAll(v -> v<10));

        assertFalse(CQueryMockup.of("stream", input).matchAll(v -> v==6));
        assertFalse(CQueryMockup.of("iterator", input).matchAll(v -> v==6));
        assertFalse(CQueryMockup.of("each1", input).matchAll(v -> v==6));

        assertTrue(CQueryMockup.of("stream", input).matchNone(v -> v==0));
        assertTrue(CQueryMockup.of("iterator", input).matchNone(v -> v==0));
        assertTrue(CQueryMockup.of("each1", input).matchNone(v -> v==0));

        assertFalse(CQueryMockup.of("stream", input).matchNone(v -> v==6));
        assertFalse(CQueryMockup.of("iterator", input).matchNone(v -> v==6));
        assertFalse(CQueryMockup.of("each1", input).matchNone(v -> v==6));
    }

    @Test
    public void testList() {
        // please note, that IQuery does not protect against modifications of source data

        // it could, but it would require copy at creation time
        // it costs a lot, so we shouldn't do this implicitly
        // in fact, that type of "protection" would be unwanted most of the time, because
        // "live queries" against source collection is quite useful and imposssible to achieve
        // if we disable this behaviour now.

        // from the other side: protection against modification is trivial:
        // just copy list before creating query, if you really need

        List<Integer> list0 = Arrays.asList(1,2,3,4,5);

        CQuery<Integer> a = CQListSupplier.from(true, () -> list0);
        List<Integer> list1 = a.list();
        List<Integer> list2 = a.list();
        List<Integer> list3 = a.list();
        Assert.assertNotSame(list1, list0);
        Assert.assertNotSame(list1, list2);
        Assert.assertNotSame(list1, list3);

        assertEquals(list0, list1);
        assertEquals(list1, list2);
        assertEquals(list1, list3);

        // at this point, change in source collection is visible
        list0.set(0, 9);
        assertEquals(Arrays.asList(9,2,3,4,5), list1);
        assertEquals(Arrays.asList(9,2,3,4,5), list1);
        assertEquals(Arrays.asList(9,2,3,4,5), list1);

        // returned lists can't be used to change source collection
        // every modification will detach list
        list1.set(1, 17);
        list2.set(2, 18);
        list3.set(3, 19);

        // at this point, change in source collection is invisible in detached lists
        list0.set(4, 20);
        assertEquals(Arrays.asList(9,17,3,4,5), list1);
        assertEquals(Arrays.asList(9,2,18,4,5), list2);
        assertEquals(Arrays.asList(9,2,3,19,5), list3);
    }

    @Test
    public void indexOf() {
        List<Integer> input = Arrays.asList(7, 6, 2, 5, 4, 9, 2, 5, 1, 6, 3, 7, 8, 4);

        assertIndexOf(3, input, 5);
        assertLastIndexOf(7, input, 5);

        assertIndexOf(-1, input, 15);
        assertLastIndexOf(-1, input, 15);
    }

    private <T> void assertIndexOf(int expected, List<T> input, T value) {
        assertEquals(expected, CQueryMockup.of("list", input).indexOf(value));
        assertEquals(expected, CQueryMockup.of("each1", input).indexOf(value));
        assertEquals(expected, CQueryMockup.of("iterator", input).indexOf(value));
        assertEquals(expected, CQueryMockup.of("stream", input).indexOf(value));
    }

    private <T> void assertLastIndexOf(int expected, List<T> input, T value) {
        assertEquals(expected, CQueryMockup.of("list", input).lastIndexOf(value));
        assertEquals(expected, CQueryMockup.of("each1", input).lastIndexOf(value));
        assertEquals(expected, CQueryMockup.of("iterator", input).lastIndexOf(value));
        assertEquals(expected, CQueryMockup.of("stream", input).lastIndexOf(value));
    }
}