/*
 * The MIT License
 *
 * Copyright (c) 2012 The Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package htsjdk.samtools.util;

import htsjdk.HtsjdkTest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import org.testng.Assert;
import org.testng.annotations.Test;

public class MergingIteratorTest extends HtsjdkTest {

    private static class QueueBackedIterator<T> implements CloseableIterator<T> {

        private final Iterator<T> backing;

        QueueBackedIterator(final Queue<T> queue) {
            this.backing = queue.iterator();
        }

        @Override
        public void close() {
            // no-op
        }

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

        @Override
        public T next() {
            return backing.next();
        }

        @Override
        public void remove() {
            backing.remove();
        }
    }

    private static final Comparator<Integer> INTEGER_COMPARATOR = new Comparator<Integer>() {
        @Override
        public int compare(Integer integer, Integer integer2) {
            return integer - integer2;
        }
    };

    @Test
    public void testOrderingAndCompleteness() {
        final Queue<Integer> queueOne = new LinkedList<Integer>();
        queueOne.add(1);
        queueOne.add(3);
        queueOne.add(5);

        final Queue<Integer> queueTwo = new LinkedList<Integer>();
        queueTwo.add(2);
        queueTwo.add(4);
        queueTwo.add(6);

        final Queue<Integer> queueThree = new LinkedList<Integer>();
        queueThree.add(0);
        queueThree.add(1);

        final Collection<CloseableIterator<Integer>> iterators = new ArrayList<CloseableIterator<Integer>>(3);
        Collections.addAll(
                iterators,
                new QueueBackedIterator<Integer>(queueOne),
                new QueueBackedIterator<Integer>(queueTwo),
                new QueueBackedIterator<Integer>(queueThree));

        final MergingIterator<Integer> mergingIterator = new MergingIterator<Integer>(INTEGER_COMPARATOR, iterators);

        int count = 0;
        int last = -1;
        while (mergingIterator.hasNext()) {
            final Integer integer = mergingIterator.next();
            count++;
            if (integer == 1) Assert.assertTrue(integer >= last);
            else Assert.assertTrue(integer > last);
            last = integer;
        }

        Assert.assertEquals(queueOne.size() + queueTwo.size() + queueThree.size(), count);
    }

    @Test
    public void testIteratorsOfUnevenLength() {
        final Queue<Integer> queueOne = new LinkedList<Integer>();
        queueOne.add(1);
        queueOne.add(3);
        queueOne.add(5);
        queueOne.add(7);
        queueOne.add(9);
        queueOne.add(11);
        queueOne.add(13);

        final Queue<Integer> queueTwo = new LinkedList<Integer>();
        queueTwo.add(2);

        final Collection<CloseableIterator<Integer>> iterators = new ArrayList<CloseableIterator<Integer>>(3);
        Collections.addAll(
                iterators, new QueueBackedIterator<Integer>(queueOne), new QueueBackedIterator<Integer>(queueTwo));

        final MergingIterator<Integer> mergingIterator = new MergingIterator<Integer>(INTEGER_COMPARATOR, iterators);

        int count = 0;
        int last = -1;
        while (mergingIterator.hasNext()) {
            final Integer integer = mergingIterator.next();
            count++;
            Assert.assertTrue(integer > last);
            last = integer;
        }

        Assert.assertEquals(queueOne.size() + queueTwo.size(), count);
    }

    @Test(expectedExceptions = IllegalStateException.class)
    public void testOutOfOrderIterators() {
        final Queue<Integer> queueOne = new LinkedList<Integer>();
        queueOne.add(1);
        queueOne.add(3);

        final Queue<Integer> queueTwo = new LinkedList<Integer>();
        queueTwo.add(4);
        queueTwo.add(2);

        final Collection<CloseableIterator<Integer>> iterators = new ArrayList<CloseableIterator<Integer>>(3);
        Collections.addAll(
                iterators, new QueueBackedIterator<Integer>(queueOne), new QueueBackedIterator<Integer>(queueTwo));

        final MergingIterator<Integer> mergingIterator = new MergingIterator<Integer>(INTEGER_COMPARATOR, iterators);

        Assert.assertEquals(mergingIterator.next().intValue(), 1);
        Assert.assertEquals(mergingIterator.next().intValue(), 3);
        Assert.assertEquals(mergingIterator.next().intValue(), 4);
        mergingIterator.next(); // fails, because the next element would be "2"
    }

    @Test()
    public void testCloseMultipleIteratorsMidIteration() {
        final Queue<Integer> queueOne = new LinkedList<Integer>();
        queueOne.add(1);
        queueOne.add(4);
        queueOne.add(7);

        final Queue<Integer> queueTwo = new LinkedList<Integer>();
        queueTwo.add(2);
        queueTwo.add(5);
        queueTwo.add(8);

        final Queue<Integer> queueThree = new LinkedList<Integer>();
        queueThree.add(3);
        queueThree.add(6);
        queueThree.add(9);

        final Collection<CloseableIterator<Integer>> iterators = new ArrayList<CloseableIterator<Integer>>(3);
        Collections.addAll(
                iterators,
                new QueueBackedIterator<>(queueOne),
                new QueueBackedIterator<>(queueTwo),
                new QueueBackedIterator<>(queueThree));

        final MergingIterator<Integer> mergingIterator = new MergingIterator<Integer>(INTEGER_COMPARATOR, iterators);

        Assert.assertEquals(mergingIterator.next().intValue(), 1);
        Assert.assertEquals(mergingIterator.next().intValue(), 2);
        Assert.assertEquals(mergingIterator.next().intValue(), 3);

        mergingIterator.close();
        Assert.assertFalse(mergingIterator.hasNext());
    }
}
