package com.atlassian.bitbucket.util;

import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.IntStream;

import static org.junit.Assert.assertEquals;

@RunWith(MockitoJUnitRunner.class)
public class PagedIterableTest {

    @Test
    public void testIteratingWithoutCallingHasNext() {
        Iterator<Integer> iterator = new PagedIterable<>(createProvider(10), PageUtils.newRequest(0, 5)).iterator();

        List<Integer> actual = new ArrayList<>(10);
        try {
            //The Iterator contract does _not_ require calls to hasNext() while iterating. It's perfectly valid
            //to just call next() until NoSuchElementException is thrown, and the iterator should traverse all
            //the same elements as if hasNext() had been checked each time
            //
            //noinspection InfiniteLoopStatement
            while (true) {
                actual.add(iterator.next());
            }
        } catch (NoSuchElementException e) {
            //End iteration by catching the exception
        }

        assertIterableEquals(IntStream.range(0, 10), actual);
    }

    @Test
    public void testMultiplePagesRequiredForResult() {
        PageProvider<Integer> provider = createProvider(5);
        for (int limit = 1; limit < 5; ++limit) {
            assertIterableEquals(IntStream.range(0, 5), new PagedIterable<>(provider, PageUtils.newRequest(0, limit)));
        }
    }

    @Test
    public void testNoResults() {
        PageProvider<Integer> provider = createProvider(0);
        assertIterableEquals(IntStream.empty(), new PagedIterable<>(provider, PageUtils.newRequest(0, 2)));
    }

    @Test
    public void testSinglePageRequiredForResult() {
        PageProvider<Integer> provider = createProvider(2);
        assertIterableEquals(IntStream.range(0, 2), new PagedIterable<>(provider, PageUtils.newRequest(0, 2)));
        assertIterableEquals(IntStream.range(0, 2), new PagedIterable<>(provider, PageUtils.newRequest(0, 3)));
    }

    private void assertIterableEquals(IntStream expected, Iterable<Integer> actual) {
        Iterator<Integer> expectedIt = expected.iterator();
        Iterator<Integer> actualIt = actual.iterator();
        
        int index = 0;
        do {
            assertEquals("hasNext at index " + index + " do not match", expectedIt.hasNext(), actualIt.hasNext());
            if (expectedIt.hasNext()) {
                assertEquals("values at index " + index + " do not match", expectedIt.next(), actualIt.next());
            }
            ++index;
        } while (expectedIt.hasNext() || actualIt.hasNext());
    }
    
    private PageProvider<Integer> createProvider(final int max) {
        return request -> {
            List<Integer> results = Lists.newArrayList();
            for (int i = request.getStart(); i <= (request.getStart() + request.getLimit()) && i < max; ++i) {
                results.add(i);
            }
            return PageUtils.createPage(results, request);
        };
    }
}
