package net.ranides.test;

import net.ranides.test.mockup.query.CQueryMockup;
import org.junit.Assert;

import net.ranides.assira.collection.lists.IntRange;
import net.ranides.assira.collection.lists.ListUtils;
import net.ranides.assira.collection.maps.ArrayMap;
import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.collection.query.base.CQCollection;
import net.ranides.assira.collection.query.support.BaseArray;
import net.ranides.assira.collection.query.support.BaseEach;
import net.ranides.assira.collection.query.support.BaseIterable;
import net.ranides.assira.collection.query.support.BaseStream;
import net.ranides.assira.text.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class CQueryAssert {

    public static <T> void assertGroup(List<T> values, CQuery<T> query) {
        Map<String, T> expected1 = new HashMap<>();
        Map<String, String> expected2 = new HashMap<>();

        for(T v : values) {
            expected1.put("#"+v, v);
            expected2.put("#K"+v, "#V"+v);
        }
        Assert.assertEquals(expected1, query.group(i -> "#" + i));
        Assert.assertEquals(expected2, query.group(i -> "#K" + i, i -> "#V"+i));
    }

    public static <T> void assertEquals(T[] expected, CQuery<T> query) {
        assertEquals(Arrays.asList(expected), query);
    }

    public static <T> void assertEquals(List<T> expected, CQuery<T> query) {
        assertList(expected, query);
        assertSet(expected, query);
        assertArray(expected, query);
        assertCollect(expected, query);

        assertSpliterator(expected, query);
        assertEach(expected, query);
        assertAt(expected, query);
        assertSize(expected, query);

        assertJoin(expected, query);
    }

    public static <T> void assertEqualsNull(List<T> expected, CQuery<T> query) {
        assertList(expected, query);
        assertSet(expected, query);
        assertArray(expected, query);
        assertCollect(expected, query);

        assertSpliterator(expected, query);
        assertEach(expected, query);
        assertSize(expected, query);
    }

    public static <T> void assertEmpty(CQuery<T> query) {
        assertEquals(Collections.emptyList(), query);
    }

    public static <T> void assertList(List<T> expected, CQuery<T> query) {
        // direct 1st check
        Assert.assertEquals(expected, query.list());

        // direct 2nd check
        Assert.assertEquals(expected, query.list());

        // check other list implementations
        Assert.assertEquals(expected, BaseIterable.list(query));
        Assert.assertEquals(expected, BaseStream.list(query));
        Assert.assertEquals(expected, BaseEach.list(query));
        Assert.assertEquals(expected, BaseArray.list(query));
    }

    public static <T> void assertSet(List<T> list, CQuery<T> query) {
        Set<T> expected = new HashSet<>(list);
        // direct 1st check
        Assert.assertEquals(expected, query.set());

        // direct 2nd check
        Assert.assertEquals(expected, query.set());

        // check other list implementations
        Assert.assertEquals(expected, BaseIterable.set(query));
        Assert.assertEquals(expected, BaseStream.set(query));
        Assert.assertEquals(expected, BaseEach.set(query));
        Assert.assertEquals(expected, BaseArray.set(query));
    }

    public static <T> void assertEach(List<T> expected, CQuery<T> query) {
        // check all #while implementations
        List<T> a = new ArrayList<>();
        query.whileEach(v -> { a.add(v); return true; });
        Assert.assertEquals(expected, a);

        List<T> b = new ArrayList<>();
        query.whileEach((i,v) -> { b.add(v); return true; });
        Assert.assertEquals(expected, b);

        List<T> c = new ArrayList<>();
        query.forEach((v) -> c.add(v));
        Assert.assertEquals(expected, c);

        List<T> d = new ArrayList<>();
        query.forEach((i,v) -> d.add(v));
        Assert.assertEquals(expected, d);
    }

    public static <T> void assertSpliterator(List<T> expected, CQuery<T> query) {
        List<T> out = new ArrayList<>();
        query.spliterator().forEachRemaining(out::add);
        Assert.assertEquals(expected, out);
    }

    public static <T> void assertAt(List<T> expected, CQuery<T> query) {
        Assert.assertEquals(ListUtils.first(expected), query.first());
        Assert.assertEquals(ListUtils.last(expected), query.last());

        int n = expected.size();
        Assert.assertFalse(query.at(n).isPresent());
        Assert.assertFalse(query.at(n+1).isPresent());
        Assert.assertFalse(query.at(n+17).isPresent());

        for(int i=0; i<n; i++) {
            Assert.assertEquals(ListUtils.at(expected, i), query.at(i));
        }
    }

    public static <T> void assertArray(List<T> expected, CQuery<T> query) {
        Assert.assertArrayEquals(expected.toArray(), query.array());
        Assert.assertArrayEquals(expected.toArray(), query.array(Object[]::new));
    }

    public static <T> void assertCollect(List<T> expected, CQuery<T> query) {
        Assert.assertEquals(expected, query.collect(Collectors.toList()));
    }

    public static <T> void assertSize(List<T> expected, CQuery<T> query) {
        Assert.assertEquals(expected.isEmpty(), query.isEmpty());
        Assert.assertEquals(expected.size(), query.size());
    }

    public static <T> void assertJoin(List<T> items, CQuery<T> query) {
        String expected1 = StringUtils.join(items, "");
        String expected2 = StringUtils.join(items, "+");

        Assert.assertEquals(expected1, query.join());
        Assert.assertEquals(expected2, query.join("+"));

        // check other join implementations
        Assert.assertEquals(expected1, BaseIterable.join(query));
        Assert.assertEquals(expected1, BaseEach.join(query));

        Assert.assertEquals(expected2, BaseIterable.join(query, "+"));
        Assert.assertEquals(expected2, BaseEach.join(query, "+"));
    }

    public static void assertOrderedParallel(CQuery<?> query) {
        Map<String, String> expect1 = new ConcurrentHashMap<>();
        query.sequential().forEach(v -> {
            expect1.put(v + "k", v + "v");
        });
        Map<String, String> expect2 = new ConcurrentHashMap<>();
        query.sequential().forEach((i,v) -> {
            expect2.put(v + "k", v + "v"+i);
        });

        Map<String, String> forEach1 = new ConcurrentHashMap<>();
        query.parallel().forEach(v -> {
            forEach1.put(v + "k", v + "v");
        });

        Map<String, String> forEach2 = new ConcurrentHashMap<>();
        query.parallel().forEach((i,v) -> {
            forEach2.put(v + "k", v + "v"+i);
        });

        Map<String, String> whileEach1 = new ConcurrentHashMap<>();
        query.parallel().whileEach(v -> {
            whileEach1.put(v + "k", v + "v");
            return true;
        });

        Map<String, String> whileEach2 = new ConcurrentHashMap<>();
        query.parallel().whileEach((i,v) -> {
            whileEach2.put(v + "k", v + "v"+i);
            return true;
        });

        Assert.assertEquals(expect1, forEach1);
        Assert.assertEquals(expect2, forEach2);

        Assert.assertEquals(expect1, whileEach1);
        Assert.assertEquals(expect2, whileEach2);

        Assert.assertEquals(query.sequential().size(), query.parallel().size());
    }

    public static void assertUnorderedParallel(CQuery<?> query) {
        Map<String, String> expectValues = new ConcurrentHashMap<>();
        query.sequential().forEach(v -> {
            expectValues.put(v + "k", v + "v");
        });
        Map<Integer, Integer> expectIndexes = new ConcurrentHashMap<>();
        query.sequential().forEach((i,v) -> {
            expectIndexes.put(i,i);
        });

        Map<String, String> forEach1 = new ConcurrentHashMap<>();
        query.parallel().forEach(v -> {
            forEach1.put(v + "k", v + "v");
        });

        Map<String, String> forEach2 = new ConcurrentHashMap<>();
        Map<Integer, Integer> forEachIndex = new ConcurrentHashMap<>();
        query.parallel().forEach((i,v) -> {
            forEach2.put(v + "k", v + "v");
            forEachIndex.put(i,i);
        });

        Map<String, String> whileEach1 = new ConcurrentHashMap<>();
        query.parallel().whileEach(v -> {
            whileEach1.put(v + "k", v + "v");
            return true;
        });

        Map<String, String> whileEach2 = new ConcurrentHashMap<>();
        Map<Integer, Integer> whileEachIndex = new ConcurrentHashMap<>();
        query.parallel().whileEach((i,v) -> {
            whileEach2.put(v + "k", v + "v");
            whileEachIndex.put(i,i);
            return true;
        });

        Assert.assertEquals(expectValues, forEach1);
        Assert.assertEquals(expectValues, forEach2);
        Assert.assertEquals(expectIndexes, forEachIndex);

        Assert.assertEquals(expectValues, whileEach1);
        Assert.assertEquals(expectValues, whileEach2);
        Assert.assertEquals(expectIndexes, whileEachIndex);

        Assert.assertEquals(query.sequential().size(), query.parallel().size());
    }


}
