package com.googlecode.totallylazy;

import com.googlecode.totallylazy.functions.Function1;
import com.googlecode.totallylazy.predicates.Predicates;
import org.hamcrest.Matchers;
import org.junit.Test;

import java.util.Enumeration;
import java.util.Vector;

import static com.googlecode.totallylazy.functions.Callables.returns;
import static com.googlecode.totallylazy.Option.none;
import static com.googlecode.totallylazy.Option.some;
import static com.googlecode.totallylazy.Pair.pair;
import static com.googlecode.totallylazy.Quadruple.quadruple;
import static com.googlecode.totallylazy.Quintuple.quintuple;
import static com.googlecode.totallylazy.Sequences.characters;
import static com.googlecode.totallylazy.Sequences.empty;
import static com.googlecode.totallylazy.Sequences.iterate;
import static com.googlecode.totallylazy.Sequences.join;
import static com.googlecode.totallylazy.Sequences.repeat;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Sequences.transpose;
import static com.googlecode.totallylazy.Triple.triple;
import static com.googlecode.totallylazy.matchers.IterableMatcher.hasExactly;
import static com.googlecode.totallylazy.matchers.IterableMatcher.startsWith;
import static com.googlecode.totallylazy.numbers.Numbers.decrement;
import static com.googlecode.totallylazy.numbers.Numbers.even;
import static com.googlecode.totallylazy.numbers.Numbers.increment;
import static com.googlecode.totallylazy.numbers.Numbers.numbers;
import static com.googlecode.totallylazy.numbers.Numbers.odd;
import static com.googlecode.totallylazy.numbers.Numbers.range;
import static java.util.Arrays.asList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class SequencesTest {
    @Test
    public void shouldCreateEmptySequenceWhenIterableIsNull() throws Exception {
        assertThat(sequence((Iterable<Object>) null), is(empty()));
        assertThat(sequence((Object[]) null), is(empty(Object.class)));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void supportsTranspose() {
        Sequence<Sequence<Integer>> transposed = transpose(sequence(1, 2), sequence(3, 4), sequence(5, 6));
        assertThat(transposed, hasExactly(sequence(1, 3, 5), sequence(2, 4, 6)));
        assertThat(transpose(transposed), hasExactly(sequence(1, 2), sequence(3, 4), sequence(5, 6)));
        assertThat(sequence(1, 2).transpose(sequence(3, 4)), hasExactly(sequence(1, 3), sequence(2, 4)));
    }

    @Test
    public void supportsUnzip() {
        Pair<Sequence<Integer>, Sequence<Integer>> pair = Sequences.unzip(sequence(pair(1, 2), pair(3, 4), pair(5, 6)));
        assertThat(pair.first(), hasExactly(1, 3, 5));
        assertThat(pair.second(), hasExactly(2, 4, 6));
    }

    @Test
    public void supportsUnzippingTriples() {
        Triple<Sequence<Integer>, Sequence<Integer>, Sequence<String>> triple = Sequences.unzip3(sequence(triple(1, 2, "car"), triple(3, 4, "cat")));
        assertThat(triple.first(), hasExactly(1, 3));
        assertThat(triple.second(), hasExactly(2, 4));
        assertThat(triple.third(), hasExactly("car", "cat"));
    }

    @Test
    public void supportsUnzippingQuadruples() {
        Quadruple<Sequence<Integer>, Sequence<Integer>, Sequence<String>, Sequence<Character>> quadruple = Sequences.unzip4(sequence(quadruple(1, 2, "car", 'C'), quadruple(3, 4, "cat", 'D')));
        assertThat(quadruple.first(), hasExactly(1, 3));
        assertThat(quadruple.second(), hasExactly(2, 4));
        assertThat(quadruple.third(), hasExactly("car", "cat"));
        assertThat(quadruple.fourth(), hasExactly('C', 'D'));
    }

    @Test
    public void supportsUnzippingQuintuples() {
        Quintuple<Sequence<Integer>, Sequence<Integer>, Sequence<String>, Sequence<Character>, Sequence<Integer>> quintuple =
                Sequences.unzip5(sequence(quintuple(1, 2, "car", 'C', 1), quintuple(3, 4, "cat", 'D', 2)));
        assertThat(quintuple.first(), hasExactly(1, 3));
        assertThat(quintuple.second(), hasExactly(2, 4));
        assertThat(quintuple.third(), hasExactly("car", "cat"));
        assertThat(quintuple.fourth(), hasExactly('C', 'D'));
        assertThat(quintuple.fifth(), hasExactly(1, 2));
    }

    @Test
    public void supportsCycle() throws Exception {
        assertThat(range(1, 3).cycle(), startsWith((Number) 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3));
    }

    @Test
    public void supportsAddingToAnEmptyList() throws Exception {
        assertThat(sequence().append(1).append(2).append(3), hasExactly((Object) 1, 2, 3));
    }

    @Test
    public void joinWorksEvenWhenFirstIterableIsEmpty() throws Exception {
        final Sequence<Integer> empty = Sequences.<Integer>empty();
        assertThat(empty.append(1).join(sequence(2, 3)).append(4), hasExactly(1, 2, 3, 4));
        assertThat(join(empty, sequence(1, 2, 3), empty, asList(4, 5, 6)), hasExactly(1, 2, 3, 4, 5, 6));
    }

    @Test
    public void supportsJoiningSubTypes() throws Exception {
        final Sequence<Number> numbers = numbers(2, 3.0D);
        Sequence<Integer> integers = sequence(2, 3);
        Sequence<Long> longs = sequence(2L, 3L);
        assertThat(numbers.join(integers).join(longs), hasExactly((Number) 2, 3.0D, 2, 3, 2L, 3L));
        assertThat(join(sequence(1L, 2.0D, 3), numbers, asList(4, 5, 6), integers), hasExactly((Number) 1L, 2.0D, 3, 2, 3.0D, 4, 5, 6, 2, 3));
    }

    @Test
    public void supportsEnumeration() throws Exception {
        Vector<String> vector = new Vector<String>();
        vector.add("foo");
        Enumeration enumeration = vector.elements();
        Sequence<String> forwardOnly = Sequences.forwardOnly(enumeration, String.class);
        assertThat(forwardOnly.headOption().isEmpty(), is(false));
        assertThat(forwardOnly.headOption().isEmpty(), is(true));
        Sequence<String> forwardOnlyWithType = Sequences.forwardOnly(vector.elements());
        assertThat(forwardOnlyWithType.headOption().isEmpty(), is(false));
        assertThat(forwardOnlyWithType.headOption().isEmpty(), is(true));
    }

    @Test
    public void supportsMemorisingAnEnumeration() throws Exception {
        Vector<String> vector = new Vector<String>();
        vector.add("foo");
        Enumeration enumeration = vector.elements();
        Sequence<String> memorise = Sequences.memorise(enumeration, String.class);
        assertThat(memorise.headOption().isEmpty(), is(false));
        assertThat(memorise.headOption().isEmpty(), is(false));
        Sequence<String> memorisedWithType = Sequences.memorise(vector.elements());
        assertThat(memorisedWithType.headOption().isEmpty(), is(false));
        assertThat(memorisedWithType.headOption().isEmpty(), is(false));
    }

    @Test
    public void supportRepeat() throws Exception {
        assertThat(repeat(10), startsWith(10, 10, 10, 10, 10));
        assertThat(repeat(returns(10)), startsWith(10, 10, 10, 10, 10));
    }

    @Test
    public void supportsCharacters() throws Exception {
        assertThat(characters("text"), hasExactly('t', 'e', 'x', 't'));
        assertThat(characters("text".toCharArray()), hasExactly('t', 'e', 'x', 't'));
        assertThat(characters("text").drop(2).toString(""), is("xt"));
    }

    @Test
    public void supportsIterate() throws Exception {
        assertThat(iterate(increment, 1), startsWith((Number) 1, 2, 3, 4, 5));
    }

    @Test
    public void supportsIteratingEvenWhenCallableReturnNull() throws Exception {
        final Sequence<Integer> sequence = iterate(integer -> {
            assertThat("Should never see a null value", integer, is(Matchers.notNullValue()));
            return null;
        }, 1).takeWhile(Predicates.notNullValue());
        assertThat(sequence, hasExactly(1));
    }


    @Test
    public void canCombineIterateWithOtherOperations() throws Exception {
        final Sequence<Number> numbers = iterate(increment, 1);
        assertThat(numbers.filter(even()), startsWith((Number) 2, 4, 6));
        assertThat(numbers.filter(odd()), startsWith((Number) 1, 3, 5, 7, 9));
    }

    @Test
    public void supportsUnfoldRight() throws Exception {
        Sequence<Number> result = Sequences.unfoldRight(new Function1<Number, Option<Pair<Number, Number>>>() {
            @Override
            public Option<Pair<Number, Number>> call(Number number) throws Exception {
                if (number.equals(0)) return none();
                return some(pair(number, decrement(number)));
            }
        }, 10);
        assertThat(result, is(range(10, 1)));
    }
}
