package laziness

enum LazyList[+A]:
  case Empty
  case Cons(h: () => A, t: () => LazyList[A])

  // uncomment to be able to use cons(h, t) and empty directly
  // imports methods from the companion object
  //import LazyList._

  def toList: List[A] = ???

  def take(n: Int): LazyList[A] = ???

  //This can be tailrecursive. Uncomment below to let the compiler check for it.
  //@annotation.tailrec
  def drop(n: Int): LazyList[A] = ???


  // Methods shown during lecture

  def headOption: Option[A] = this match
    case Cons(h, _) => Some(h())
    case Empty => None

  def exists(p: A => Boolean): Boolean = this match
    case Cons(x, xs) => p(x()) || xs().exists(p)
    case Empty => false

  def foldRight[B](z: => B)(f: (A, => B) => B): B = this match
    case Cons(x, xs) => f(x(), xs().foldRight(z)(f))
    case Empty => z


object LazyList: // companion object

  def fibs: LazyList[Int] =
    // tip: write a recursive def here and call it with some start values
    ???

  def unfold[A, S](z: S)(f: S => Option[(A, S)]): LazyList[A] = ???

  def fibsViaUnfold: LazyList[Int] = ???


  // Methods shown during Lecture

  def cons[A](h: => A, t: => LazyList[A]): LazyList[A] =
    lazy val head = h
    lazy val tail = t
    Cons(() => head, () => tail)

  def empty[A]: LazyList[A] = Empty

  def apply[A](as: A*): LazyList[A] =
    if as.isEmpty then empty
    else cons(as.head, apply(as.tail: _*))


  val ones: LazyList[Int] = cons(1, ones)