/*
 * Copyright 2016 Square Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package flowless;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;

@RunWith(RobolectricTestRunner.class) // Necessary for functional SparseArray
@Config(manifest = Config.NONE) //
public class HistoryTest {
    private static final TestKey ABLE = new TestKey("able");
    private static final TestKey BAKER = new TestKey("baker");
    private static final TestKey CHARLIE = new TestKey("charlie");

    @Test public void builderCanPushPeekAndPopObjects() {
        History.Builder builder = History.emptyBuilder();

        List<TestKey> objects = asList(ABLE, BAKER, CHARLIE);
        for (Object object : objects) {
            builder.push(object);
        }

        for (int i = objects.size() - 1; i >= 0; i--) {
            Object object = objects.get(i);
            assertThat(builder.peek()).isSameAs(object);
            assertThat(builder.pop()).isSameAs(object);
        }
    }

    @Test public void builderCanPopTo() {
        History.Builder builder = History.emptyBuilder();
        builder.push(ABLE);
        builder.push(BAKER);
        builder.push(CHARLIE);
        builder.popTo(ABLE);
        assertThat(builder.peek()).isSameAs(ABLE);
    }

    @Test public void builderPopToExplodesOnMissingState() {
        History.Builder builder = History.emptyBuilder();
        builder.push(ABLE);
        builder.push(BAKER);
        builder.push(CHARLIE);
        try {
            builder.popTo(new Object());
            fail("Missing state object, should have thrown");
        } catch (IllegalArgumentException ignored) {
            // Correct!
        }
    }

    @Test public void builderCanPopCount() {
        History.Builder builder = History.emptyBuilder();
        builder.push(ABLE);
        builder.push(BAKER);
        builder.push(CHARLIE);
        builder.pop(1);
        assertThat(builder.peek()).isSameAs(BAKER);
        builder.pop(2);
        assertThat(builder.isEmpty());
    }

    @Test public void builderPopExplodesIfCountIsTooLarge() {
        History.Builder builder = History.emptyBuilder();
        builder.push(ABLE);
        builder.push(BAKER);
        builder.push(CHARLIE);
        try {
            builder.pop(4);
            fail("Count is too large, should have thrown");
        } catch (IllegalArgumentException ignored) {
            // Success!
        }
    }

    @Test public void forwardIterator() {
        List<Object> paths = new ArrayList<>(Arrays.<Object>asList(ABLE, BAKER, CHARLIE));
        History history = History.emptyBuilder().pushAll(paths).build();
        for (Object o : history) {
            assertThat(o).isSameAs(paths.remove(paths.size() - 1));
        }
    }

    @Test public void reverseIterator() {
        List<Object> paths = new ArrayList<>(Arrays.<Object>asList(ABLE, BAKER, CHARLIE));
        History history = History.emptyBuilder().pushAll(paths).build();
        Iterator<Object> i = history.reverseIterator();
        while (i.hasNext()) {
            assertThat(i.next()).isSameAs(paths.remove(0));
        }
    }

    @Test public void emptyBuilderPeekIsNullable() {
        assertThat(History.emptyBuilder().peek()).isNull();
    }

    @Test public void emptyBuilderPopThrows() {
        try {
            History.emptyBuilder().pop();
            fail("Should throw");
        } catch (IllegalStateException e) {
            // pass
        }
    }

    @Test public void isEmpty() {
        final History.Builder builder = History.emptyBuilder();
        assertThat(builder.isEmpty()).isTrue();
        builder.push("foo");
        assertThat(builder.isEmpty()).isFalse();
        builder.pop();
        assertThat(builder.isEmpty()).isTrue();
    }

    @Test public void historyIndexAccess() {
        History history = History.emptyBuilder().pushAll(asList(ABLE, BAKER, CHARLIE)).build();
        assertThat(history.peek(0)).isEqualTo(CHARLIE);
        assertThat(history.peek(1)).isEqualTo(BAKER);
        assertThat(history.peek(2)).isEqualTo(ABLE);
    }
}
