/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.tinkerpop.gremlin.process.traversal;

import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalInterruptedException;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource.traversal;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.fail;

/**
 * @author Stephen Mallette (http://stephen.genoprime.com)
 */
public class TraversalTest {

    @Test
    public void shouldTryNext() {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3);
        final Optional<Integer> optFirst = t.tryNext();
        assertEquals(1, optFirst.get().intValue());
        final Optional<Integer> optSecond = t.tryNext();
        assertEquals(2, optSecond.get().intValue());
        final Optional<Integer> optThird = t.tryNext();
        assertEquals(3, optThird.get().intValue());

        IntStream.range(0, 100).forEach(i -> {
            assertThat(t.tryNext().isPresent(), is(false));
        });
    }

    @Test
    public void shouldGetTwoAtATime() {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3, 4, 5, 6, 7);
        final List<Integer> batchOne = t.next(2);
        assertEquals(2, batchOne.size());
        assertThat(batchOne, hasItems(1 ,2));

        final List<Integer> batchTwo = t.next(2);
        assertEquals(2, batchTwo.size());
        assertThat(batchTwo, hasItems(3 ,4));

        final List<Integer> batchThree = t.next(2);
        assertEquals(2, batchThree.size());
        assertThat(batchThree, hasItems(5, 6));

        final List<Integer> batchFour = t.next(2);
        assertEquals(1, batchFour.size());
        assertThat(batchFour, hasItems(7));

        final List<Integer> batchFive = t.next(2);
        assertEquals(0, batchFive.size());
    }

    @Test
    public void shouldFillList() {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3, 4, 5, 6, 7);
        final List<Integer> listToFill = new ArrayList<>();
        final List<Integer> batch = t.fill(listToFill);
        assertEquals(7, batch.size());
        assertThat(batch, hasItems(1 ,2, 3, 4, 5, 6, 7));
        assertThat(t.hasNext(), is(false));
        assertSame(listToFill, batch);
    }

    @Test
    public void shouldStream() {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3, 4, 5, 6, 7);
        final List<Integer> batch = t.toStream().collect(Collectors.toList());
        assertEquals(7, batch.size());
        assertThat(batch, hasItems(1 ,2, 3, 4, 5, 6, 7));
        assertThat(t.hasNext(), is(false));
    }

    @Test
    public void shouldIterate() {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3, 4, 5, 6, 7);
        assertThat(t.hasNext(), is(true));
        t.iterate();
        assertThat(t.hasNext(), is(false));
    }

    @Test
    public void shouldCloseWithoutACloseableStep() throws Exception {
        final MockTraversal<Integer> t = new MockTraversal<>(1, 2, 3, 4, 5, 6, 7);
        assertThat(t.hasNext(), is(true));
        t.close();
        assertThat(t.isClosed(), is(false));
    }

    @Test
    public void shouldCloseWithCloseableStep() throws Exception {
        final MockTraversal<Integer> t = new MockTraversal<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7).iterator(), true);
        assertThat(t.hasNext(), is(true));
        t.close();
        assertThat(t.isClosed(), is(true));
    }

    @Test
    public void shouldOnlyAllowAnonymousChildren() {
        final GraphTraversalSource g = traversal().withGraph(EmptyGraph.instance());
        g.V(1).addE("self").to(__.V(1));
        try {
            g.V(1).addE("self").to(g.V(1));
            fail("Should not allow child traversals spawned from 'g'");
        } catch (IllegalStateException ignored) {}
    }

    private static class MockCloseStep<E> extends MockStep<E> implements AutoCloseable {
        private boolean closed = false;

        public MockCloseStep(final Iterator<E> itty) {
            super(itty);
        }

        boolean isClosed() {
            return closed;
        }

        @Override
        public void close() throws Exception {
            closed = true;
        }
    }

    private static class MockStep<E> implements Step<E,E> {

        private final Iterator<E> itty;

        MockStep(final Iterator<E> itty) {
            this.itty = itty;
        }

        @Override
        public void addStarts(final Iterator starts) {

        }

        @Override
        public void addStart(final Traverser.Admin start) {

        }

        @Override
        public boolean hasStarts() {
            return false;
        }

        @Override
        public void setPreviousStep(final Step step) {

        }

        @Override
        public Step getPreviousStep() {
            return null;
        }

        @Override
        public void setNextStep(final Step step) {

        }

        @Override
        public Step getNextStep() {
            return null;
        }

        @Override
        public Traversal.Admin getTraversal() {
            return null;
        }

        @Override
        public void setTraversal(final Traversal.Admin traversal) {

        }

        @Override
        public void reset() {

        }

        @Override
        public Step clone() {
            return null;
        }

        @Override
        public Set<String> getLabels() {
            return null;
        }

        @Override
        public void addLabel(final String label) {

        }

        @Override
        public void removeLabel(final String label) {

        }

        @Override
        public void setId(final String id) {

        }

        @Override
        public String getId() {
            return null;
        }

        @Override
        public boolean hasNext() {
            return itty.hasNext();
        }

        @Override
        public Traverser.Admin<E> next() {
            return new DefaultRemoteTraverser<>(itty.next(), 1L);
        }
    }

    private static class MockTraversal<T> implements Traversal.Admin<T,T> {

        private Iterator<T> itty;

        private Step mockEndStep;

        private List<Step> steps;

        MockTraversal(final T... objects) {
            this(Arrays.asList(objects));
        }

        MockTraversal(final List<T> list) {
            this(list.iterator(), false);
        }

        MockTraversal(final Iterator<T> itty, final boolean asCloseable) {
            this.itty = itty;
            mockEndStep = asCloseable ? new MockCloseStep<>(itty) : new MockStep<>(itty);
            steps = Collections.singletonList(mockEndStep);
        }

        boolean isClosed() {
            return mockEndStep instanceof MockCloseStep && ((MockCloseStep) mockEndStep).isClosed();
        }

        @Override
        public Bytecode getBytecode() {
            return new Bytecode();
        }

        @Override
        public List<Step> getSteps() {
            return steps;
        }

        @Override
        public <S2, E2> Admin<S2, E2> addStep(final int index, final Step<?, ?> step) throws IllegalStateException {
            return null;
        }

        @Override
        public <S2, E2> Admin<S2, E2> removeStep(final int index) throws IllegalStateException {
            return null;
        }

        @Override
        public void applyStrategies() throws IllegalStateException {

        }

        @Override
        public TraverserGenerator getTraverserGenerator() {
            return null;
        }

        @Override
        public Set<TraverserRequirement> getTraverserRequirements() {
            return null;
        }

        @Override
        public void setSideEffects(final TraversalSideEffects sideEffects) {

        }

        @Override
        public TraversalSideEffects getSideEffects() {
            return null;
        }

        @Override
        public void setStrategies(final TraversalStrategies strategies) {

        }

        @Override
        public TraversalStrategies getStrategies() {
            return null;
        }

        @Override
        public void setParent(final TraversalParent step) {

        }

        @Override
        public TraversalParent getParent() {
            return null;
        }

        @Override
        public Admin<T, T> clone() {
            return null;
        }

        @Override
        public boolean isLocked() {
            return false;
        }

        @Override
        public Optional<Graph> getGraph() {
            return null;
        }

        @Override
        public void setGraph(final Graph graph) {

        }

        @Override
        public boolean hasNext() {
            return itty.hasNext();
        }

        @Override
        public T next() {
            if (Thread.interrupted()) throw new TraversalInterruptedException();
            return itty.next();
        }
    }
}
