package org.usi.sq.util;

import org.junit.jupiter.api.Test;

import java.util.*;

import static org.junit.jupiter.api.Assertions.*;

class CircularFifoQueueTest {

    private void testIteratorEmpty(final Iterator<?> it) {
        assertFalse(it.hasNext());
        assertThrows(NoSuchElementException.class, it::next);
    }

    private <T> void testIteratorNext(final Iterator<T> it, final T e) {
        assertTrue(it.hasNext());
        assertEquals(e, it.next());
    }

    private <T> void testQueue(final int initQueueSize, final List<T> values, final long removeQueueCount, final List<Integer> itrRemoveIndexes) {
        final Set<Integer> removeIndexesSet = new HashSet<>(itrRemoveIndexes);

        final CircularFifoQueue<T> queue = new CircularFifoQueue<>(initQueueSize);
        for (T e : values) assertTrue(queue.offer(e));

        final int queueSizeAfterOffers = Math.min(initQueueSize, values.size());
        for (int i = 0; i < removeQueueCount; i++) {
            assertEquals(values.get(values.size() - queueSizeAfterOffers + i), queue.remove());
        }

        assertEquals(queueSizeAfterOffers - removeQueueCount, queue.size());
        assertEquals(queue.size() == initQueueSize, queue.isAtFullCapacity());

        final Iterator<T> it = queue.iterator();
        final List<T> notRemovedValues = new ArrayList<>(values.size());

        final List<T> expected = values.subList(Math.max(0, values.size() - queue.size()), values.size());

        // Test Iteration
        for (int i = 0; i < expected.size(); i++) {
            final T e = expected.get(i);
            testIteratorNext(it, e);

            if (removeIndexesSet.contains(i)) {
                it.remove();
                assertThrows(IllegalStateException.class, it::remove); // removing again causes explosion
            } else {
                notRemovedValues.add(e);
            }
        }
        testIteratorEmpty(it);

        // Test removal
        assertEquals(notRemovedValues.size(), queue.size());
        final Iterator<T> itNew = queue.iterator();
        for (T e : notRemovedValues) testIteratorNext(itNew, e);
        testIteratorEmpty(itNew);
    }

    @SafeVarargs
    public final <T> List<T> list(final T... es) {
        return Arrays.asList(es);
    }

    public final List<Integer> range(final int start, final int end) {
        if (end < start) throw new IllegalArgumentException();

        final List<Integer> toReturn = new ArrayList<>(end - start);
        for (int i = start; i < end; i++) toReturn.add(i);
        return toReturn;
    }

    @Test
    public void testIterator() {
        assertThrows(IllegalArgumentException.class, () -> new CircularFifoQueue<Void>(0));
        assertThrows(NoSuchElementException.class, () -> new CircularFifoQueue<Void>().remove());
        assertThrows(NullPointerException.class, () -> new CircularFifoQueue<Void>().offer(null));

        final List<Integer> empty = Collections.emptyList();

        testQueue(10, range(0, 3), 0, list(0, 2));
        testQueue(3, range(0, 3), 0, list(0, 2));
        testQueue(3, range(0, 3), 0, list(0, 2));
        testQueue(2, range(0, 3), 0, empty);
        testQueue(4, range(0, 9), 0, list(1, 3));
        testQueue(7, range(0, 13), 4, list(1, 3));
        testQueue(7, range(0, 13), 2, list(0, 2, 4, 5, 6));
        testQueue(5, range(0, 34), 2, list(0, 2, 4));
        testQueue(4, range(0, 5), 0, list(1, 3));
        testQueue(10, empty, 0, empty);
        testQueue(4, range(0, 5), 1, empty);
    }

}
