
package scala.collection

import org.junit.Assert._
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

import scala.tools.testing.AssertUtil._

import Seq.empty

@RunWith(classOf[JUnit4])
class IteratorTest {

  @Test def groupedIteratorShouldNotAskForUnneededElement(): Unit = {
    var counter = 0
    val it = new Iterator[Int] { var i = 0 ; def hasNext = { counter = i; true } ; def next = { i += 1; i } }
    val slidingIt = it sliding 2
    slidingIt.next
    assertEquals("Counter should be one, that means we didn't look further than needed", 1, counter)
  }

  @Test def groupedIteratorIsLazyWhenPadded(): Unit = {
    var counter = 0
    def it = new Iterator[Int] { var i = 0 ; def hasNext = { counter = i; true } ; def next = { i += 1; i } }
    val slidingIt = it sliding 2 withPadding -1
    slidingIt.next
    assertEquals("Counter should be one, that means we didn't look further than needed", 1, counter)
  }

  @Test def dropDoesNotGrowStack(): Unit = {
    def it = new Iterator[Throwable] { def hasNext = true ; def next = new Throwable }

    assertEquals(it.drop(1).next.getStackTrace.length, it.drop(1).drop(1).next.getStackTrace.length)
  }

  @Test def dropIsChainable(): Unit = {
    assertSameElements(1 to 4, Iterator from 0 take 5 drop 1)
    assertSameElements(3 to 4, Iterator from 0 take 5 drop 3)
    assertSameElements(empty,  Iterator from 0 take 5 drop 5)
    assertSameElements(empty,  Iterator from 0 take 5 drop 10)
    assertSameElements(0 to 4, Iterator from 0 take 5 drop 0)
    assertSameElements(0 to 4, Iterator from 0 take 5 drop -1)
    assertSameElements(2 to 8 by 2, Iterator from 0 take 5 drop 1 map (2 * _))
    assertSameElements(2 to 8 by 2, Iterator from 0 take 5 map (2 * _) drop 1)
    assertSameElements(3 to 4, Iterator from 0 take 5 drop 1 drop 2)
    assertSameElements(3 to 4, Iterator from 0 take 5 drop 2 drop 1)
  }

  @Test def sliceIsChainable(): Unit = {
    assertSameElements(3 to 6, Iterator from 0 slice (3, 7))
    assertSameElements(empty,  Iterator from 0 slice (3, 3))
    assertSameElements(0 to 2, Iterator from 0 slice (-1, 3))
    assertSameElements(empty,  Iterator from 0 slice (3, -1))
    assertSameElements(6 to 12 by 2, Iterator from 0 slice (3, 7) map (2 * _))
    assertSameElements(6 to 12 by 2, Iterator from 0 map (2 * _) slice (3, 7))
    assertSameElements(4 to 6, Iterator from 0 slice (3, 7) drop 1)
    assertSameElements(4 to 7, Iterator from 0 drop 1 slice (3, 7))
    assertSameElements(4 to 5, Iterator from 0 slice (3, 7) slice (1, 3))
    assertSameElements(4 to 6, Iterator from 0 slice (3, 7) slice (1, 10))
  }

  // test/files/run/iterator-concat.scala
  @Test def concatIsStackFriendly(): Unit = {
    // Create `size` Function0s, each of which evaluates to an Iterator
    // which produces 1. Then fold them over ++ to get a single iterator,
    // which should sum to "size".
    def mk(size: Int): Iterator[Int] = {
      //val closures = (1 to size).toList.map(x => (() => Iterator(1)))
      //closures.foldLeft(Iterator.empty: Iterator[Int])((res, f) => res ++ f())
      List.fill(size)(() => Iterator(1)).foldLeft(Iterator.empty: Iterator[Int])((res, f) => res ++ f())
    }
    assertEquals(100,    mk(100).sum)
    assertEquals(1000,   mk(1000).sum)
    assertEquals(10000,  mk(10000).sum)
    assertEquals(100000, mk(100000).sum)
  }

  @Test def from(): Unit = {
    val it1 = Iterator.from(-1)
    val it2 = Iterator.from(0, -1)
    assertEquals(-1, it1.next())
    assertEquals(0,  it2.next())
  }
  @Test def range(): Unit = {
    assertEquals(5, Iterator.range(0, 10, 2).size)
    assertEquals(0, Iterator.range(0, 10, -2).size)
    assertEquals(5, Iterator.range(10, 0, -2).size)
    assertEquals(0, Iterator.range(10, 0, 2).size)
    assertEquals(1, Iterator.range(0, 10, 11).size)
    assertEquals(10, Iterator.range(0, 10, 1).size)
    assertEquals(10, Iterator.range(10, 0, -1).size)
  }
  @Test def range3(): Unit = {
    val r1 = Iterator.range(0, 10)
    assertTrue(r1 contains 5)
    assertTrue(r1 contains 6)
    assertFalse(r1 contains 4)
    val r2a = Iterator.range(0, 10, 2)
    assertFalse(r2a contains 5)
    val r2b = Iterator.range(0, 10, 2)
    assertTrue(r2b contains 6)
    val r3 = Iterator.range(0, 10, 11)
    assertFalse(r3 contains 5)
    assertTrue(r3.isEmpty)
  }
  @Test def take(): Unit = {
    assertEquals(10, (Iterator from 0 take 10).size)
  }
  @Test def foreach(): Unit = {
    val it1 = Iterator.from(0) take 20
    var n = 0
    it1 foreach { n += _ }
    assertEquals(190, n)
  }
  // ticket #429
  @Test def fromArray(): Unit = {
    val a = List(1, 2, 3, 4).toArray
    var xs0 = a.iterator.toList;
    var xs1 = a.slice(0, 1).iterator
    var xs2 = a.slice(0, 2).iterator
    var xs3 = a.slice(0, 3).iterator
    var xs4 = a.slice(0, 4).iterator
    assertEquals(14, xs0.size + xs1.size + xs2.size + xs3.size + xs4.size)
  }
  @Test def toSeq(): Unit = {
    assertEquals("1x2x3x4x5", List(1, 2, 3, 4, 5).iterator.mkString("x"))
  }
  @Test def indexOf(): Unit = {
    assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexOf(4))
    assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexOf(16))
  }
  @Test def indexWhere(): Unit = {
    assertEquals(3, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 4 })
    assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere { x: Int => x >= 16 })
  }
  @Test def indexOfFrom(): Unit = {
    assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 0))
    assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 1))
    assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexOf(2, 2))
    assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexOf(1, 1))
    assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexOf(2, 1))
  }
  @Test def indexWhereFrom(): Unit = {
    assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 0))
    assertEquals(1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 1))
    assertEquals(-1, List(1, 2, 3, 4, 5).iterator.indexWhere(_ == 2, 2))
    assertEquals(4, List(1, 2, 3, 2, 1).iterator.indexWhere(_ < 2, 1))
    assertEquals(1, List(1, 2, 3, 2, 1).iterator.indexWhere(_ <= 2, 1))
  }
  // iterator-iterate-lazy.scala
  // was java.lang.UnsupportedOperationException: tail of empty list
  @Test def iterateIsSufficientlyLazy(): Unit = {
    //Iterator.iterate((1 to 5).toList)(_.tail).takeWhile(_.nonEmpty).toList  // suffices
    Iterator.iterate((1 to 5).toList)(_.tail).takeWhile(_.nonEmpty).map(_.head).toList
  }
  // SI-3516
  @Test def toStreamIsSufficientlyLazy(): Unit = {
    val results = collection.mutable.ListBuffer.empty[Int]
    def mkIterator = (1 to 5).iterator map (x => { results += x ; x })
    def mkInfinite = Iterator continually { results += 1 ; 1 }

    // Stream is strict in its head so we should see 1 from each of them.
    val s1 = mkIterator.toStream
    val s2 = mkInfinite.toStream
    // back and forth without slipping into nontermination.
    results += (Stream from 1).toIterator.drop(10).toStream.drop(10).toIterator.next()
    assertSameElements(List(1,1,21), results)
  }
  // SI-8552
  @Test def indexOfShouldWorkForTwoParams(): Unit = {
    assertEquals(1, List(1, 2, 3).iterator.indexOf(2, 0))
    assertEquals(-1, List(5 -> 0).iterator.indexOf(5, 0))
    assertEquals(0, List(5 -> 0).iterator.indexOf((5, 0)))
    assertEquals(-1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9, 2))
    assertEquals(1, List(5 -> 0, 9 -> 2, 0 -> 3).iterator.indexOf(9 -> 2))
  }
  // SI-9332
  @Test def spanExhaustsLeadingIterator(): Unit = {
    def it = Iterator.iterate(0)(_ + 1).take(6)
    val (x, y) = it.span(_ != 1)
    val z = x.toList
    assertEquals(1, z.size)
    assertFalse(x.hasNext)
    assertEquals(1, y.next)
    assertFalse(x.hasNext)   // was true, after advancing underlying iterator
  }
  // SI-9913
  @Test def `span leading iterator finishes at state -1`(): Unit = {
    val (yes, no) = Iterator(1, 2, 3).span(_ => true)
    assertFalse(no.hasNext)
    assertTrue(yes.hasNext)
  }
  // SI-9623
  @Test def noExcessiveHasNextInJoinIterator: Unit = {
    var counter = 0
    val exp = List(1,2,3,1,2,3)
    def it: Iterator[Int] = new Iterator[Int] {
      val parent = List(1,2,3).iterator
      def next(): Int = parent.next
      def hasNext: Boolean = { counter += 1; parent.hasNext }
    }
    // Iterate separately
    val res = new mutable.ArrayBuffer[Int]
    it.foreach(res += _)
    it.foreach(res += _)
    assertSameElements(exp, res)
    assertEquals(8, counter)
    // JoinIterator
    counter = 0
    res.clear
    (it ++ it).foreach(res += _)
    assertSameElements(exp, res)
    assertEquals(8, counter) // was 17
    // ConcatIterator
    counter = 0
    res.clear
    (Iterator.empty ++ it ++ it).foreach(res += _)
    assertSameElements(exp, res)
    assertEquals(8, counter) // was 14
  }
  // SI-9691
  @Test def bufferedHeadOptionReturnsValueWithHeadOrNone(): Unit = {
    // Checks BufferedIterator returns Some(value) when there is a value
    val validHeadOption = List(1,2,3).iterator.buffered.headOption
    assertEquals(Some(1), validHeadOption)
    // Checks BufferedIterator returns None when there is no value
    val invalidHeadOption = List(1,2,3).iterator.drop(10).buffered.headOption
    assertEquals(None: Option[Int], invalidHeadOption)
    // Checks BufferedIterator returns Some(value) in the last position with a value
    val validHeadOptionAtTail = List(1,2,3).iterator.drop(2).buffered.headOption
    assertEquals(Some(3), validHeadOptionAtTail)
    // Checks BufferedIterator returns None at the first position without a value
    val invalidHeadOptionOnePastTail = List(1,2,3).iterator.drop(3).buffered.headOption
    assertEquals(None, invalidHeadOptionOnePastTail)
    // Checks BufferedIterator returns Some(null) if the next value is null.
    val nullHandingList = List(null, "yellow").iterator.buffered.headOption
    assertEquals(Some(null), nullHandingList)
    // Checks that BufferedIterator is idempotent. That the head is not
    // changed by its invocation, nor the headOption by the next call to head.
    val it = List(1,2,3).iterator.buffered
    val v1 = it.head
    val v2 = it.headOption
    val v3 = it.head
    val v4 = it.headOption
    assertEquals(v1, v3)
    assertEquals(v2, v4)
    assertEquals(Some(v1), v2)
  }
}
