/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.query;

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.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;

import net.ranides.test.CQueryAssert;
import org.junit.Test;

import net.ranides.assira.collection.HashFunction;

import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class CQueryTest {
    
    private static final List<Character> CHARS = Arrays.asList('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z');

    private static final List<Character> CHARS3 = Arrays.asList('a','b','c');


    @Test
    public void testList() {
        CQuery<Integer> query1 = CQuery.from().collection(Arrays.asList(4, 5, 6, 7, 8, 9));
        CQueryAssert.assertEquals(Arrays.asList(4, 5, 6, 7, 8, 9), query1);

        CQuery<Integer> query2 = CQuery.from().collection(Arrays.asList(4, 5, 6, 7, 8, 9))
            .filter(v -> v % 2 == 0);
        CQueryAssert.assertEquals(Arrays.asList(4, 6, 8), query2);

        CQuery<Integer> query3 = CQuery.from().collection(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9))
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3);
        CQueryAssert.assertEquals(Arrays.asList(4, 6, 8), query3);

        CQuery<Integer> query4 = CQuery.from().collection(Arrays.asList(7,7,7,7,7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3)
            .limit(3);
        CQueryAssert.assertEquals(Arrays.asList(4, 6, 8), query4);


        CQuery<Integer> query5 = CQuery.from().collection(Arrays.asList(10, 20, 30, 40))
            .flat(a -> CQuery.from().values(a+1, a+2, a+3, a+4));
        List<Integer> expect5 = Arrays.asList(11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34, 41, 42, 43, 44);
        CQueryAssert.assertEquals(expect5, query5);

        CQuery<Integer> query6 = CQuery.from().collection(Arrays.asList(10, 20, 30, 40))
            .limit(2)
            .flat(a -> CQuery.from().values(a+1, a+2, a+3, a+4));
        List<Integer> expect6 = Arrays.asList(11, 12, 13, 14, 21, 22, 23, 24);
        CQueryAssert.assertEquals(expect6, query6);

        CQuery<Integer> query7 = CQuery.from().collection(Arrays.asList(7,7,7,7,7, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3)
            .limit(3)
            .flat(a -> CQuery.from().values(10*a+1, 10*a+2, 10*a+3, 10*a+4));
        List<Integer> expect7 = Arrays.asList(41, 42,43,44, 61,62,63,64, 81, 82, 83, 84);
        CQueryAssert.assertEquals(expect7, query7);



        CQuery<String> query8 = CQuery.from().collection(Arrays.asList(0,1,2,3,4,5,6,7,8,9,10))
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3)
            .limit(3)
            .flat(i -> CQuery.from().collection(CHARS3).map(c -> i + "." + c) );

        List<String> exp8 = Arrays.asList(
            "4.a", "4.b", "4.c",
            "6.a", "6.b", "6.c",
            "8.a", "8.b", "8.c"
        );

        CQueryAssert.assertEquals(exp8, query8);


        CQuery<String> query9 = CQuery.from().collection(Arrays.asList(2,4,6,8))
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3)
            .limit(3)
            .flat(i -> CQuery.from().collection(CHARS).limit(i / 2).map(c -> i + "." + c) );

        List<String> exp9 = Arrays.asList(
            "4.a", "4.b",
            "6.a", "6.b", "6.c",
            "8.a", "8.b", "8.c", "8.d"
        );

        CQueryAssert.assertEquals(exp9, query9);
    }

    @Test
    public void testMutableSource() {
        List<String> exp1 = Arrays.asList(
            "4.a", "4.b", 
            "6.a", "6.b", "6.c",
            "8.a", "8.b", "8.c", "8.d"
        );
        List<String> exp2 = Arrays.asList(
            "6.a", "6.b", "6.c",
            "6.a", "6.b", "6.c",
            "10.a", "10.b", "10.c", "10.d", "10.e"
        );
        
        List<Integer> src = new ArrayList<>();

        CQuery<String> query = CQuery.from().collection(src)
            .filter(v -> v % 2 == 0)
            .filter(v -> v > 3)
            .limit(3)
            .flat(i -> CQuery.from().collection(CHARS).limit(i / 2).map(c -> i + "." + c));
        
        src.addAll(Arrays.asList(0,1,2,3,4,5,6,7,8,9,10));

        CQueryAssert.assertEquals(exp1, query);

        src.clear();
        src.addAll(Arrays.asList(6,3,5,6,7,9,10));

        CQueryAssert.assertEquals(exp2, query);
    }

    @Test
    public void testCache() throws IOException {
        Path temp = File.createTempFile("cquery", ".bin").toPath();
        Files.deleteIfExists(temp);

        List<String> events = new ArrayList<>();

        Supplier<Stream<Integer>> creator = () -> {
            events.add("create");
            return Arrays.stream(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
        };

        Predicate<Integer> filter1 = v -> {
            events.add("A"+v);
            return v % 2 == 0;
        };
        Predicate<Integer> filter2 = v -> {
            events.add("B"+v);
            return v < 6;
        };

        CQuery<Integer> query = CQuery.from().stream(creator::get)
            .filter(filter1)
            .cache(temp)
            .filter(filter2);

        List<Integer> data1 = query.list();
        List<Integer> data2 = query.list();
        List<Integer> data3 = query.list();
        List<Integer> data4 = query.list();

        assertEquals(data1, data2);
        assertEquals(data1, data3);
        assertEquals(data1, data4);

        List<String> expected = Arrays.asList(
            "create",
            "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "A10",
            "B2", "B4", "B6", "B8", "B10",
            "B2", "B4", "B6", "B8", "B10",
            "B2", "B4", "B6", "B8", "B10",
            "B2", "B4", "B6", "B8", "B10"
        );
        assertEquals(expected, events);


    }

    @Test
    public void testFlat() {
        List<Integer> value1 = CQuery.from()
            .values(1, 2, 3, 4, 5, 6).flatIterable(v -> Arrays.asList(v, 10 * v, 100 * v))
                .list();
        assertEquals(Arrays.asList(1,10,100,2,20,200,3,30,300,4,40,400,5,50,500,6,60,600), value1);

        List<Integer> value2 = CQuery.from()
            .values(1, 2, 3, 4, 5, 6)
            .flatIterableIf(v -> v%2==0, v -> Arrays.asList(v, 10 * v, 100 * v))
                .list();

        assertEquals(Arrays.asList(1,2,20,200,3,4,40,400,5,6,60,600), value2);
    }

    @Test
    public void testDistinct() {
        HashFunction<Integer> hash = new HashFunction<Integer>() {
            @Override
            public int hashCode(Integer object) {
                return object % 40;
            }

            @Override
            public boolean equals(Integer a, Integer b) {
                return a % 10 == b % 40;
            }
        };

        List<Integer> value1 = CQuery.from()
            .values(1, 2, 3, 4, 1, 5, 2, 6, 41, 42, 73)
            .distinct()
            .list();

        List<Integer> value2 = CQuery.from()
            .values(1, 2, 3, 4, 1, 5, 2, 6, 41, 42, 73)
            .distinct(hash)
            .list();

        List<Integer> value3 = CQuery.from()
            .values(1, 2, 3, 4, 1, 5, 2, 6, 41, 42, 73)
            .distinct(Comparator.comparingInt(a -> a % 10))
            .list();

        assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 41, 42, 73), value1);
        assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 73), value2);
        assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), value3);
    }

    @Test
    public void sort() {
        List<Integer> input = Arrays.asList(6, 5, 4, 3, 2, 1);
        List<Integer> asc = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> dsc = Arrays.asList(6, 5, 4, 3, 2, 1);

        assertEquals(asc, CQuery.from(input).sort().list());
        assertEquals(dsc, input);
    }
}
