package monocle.std

import monocle.function._
import monocle.{Optional, Prism}

import scala.collection.immutable.Stream.{#::, Empty}
import scalaz.Id.Id
import scalaz.std.stream._
import scalaz.syntax.traverse._

object stream extends StreamInstances

trait StreamInstances {

  implicit def streamEmpty[A]: Empty[Stream[A]] = new Empty[Stream[A]] {
    def empty = Prism[Stream[A], Unit](s => if(s.isEmpty) Some(()) else None)(_ => Stream.empty)
  }

  implicit def streamEach[A]: Each[Stream[A], A] = Each.traverseEach[Stream, A]

  implicit def streamIndex[A]: Index[Stream[A], Int, A] = new Index[Stream[A], Int, A] {
    def index(i: Int) = Optional[Stream[A], A](
      s      => if(i < 0) None else s.drop(i).headOption)(
      a => s => s.zipWithIndex.traverse[Id, A]{
        case (_    , index) if index == i => a
        case (value, index)               => value
      }
    )
  }

  implicit def streamFilterIndex[A]: FilterIndex[Stream[A], Int, A] =
    FilterIndex.traverseFilterIndex[Stream, A](_.zipWithIndex)

  implicit def streamCons[A]: Cons[Stream[A], A] = new Cons[Stream[A], A]{
    def cons = Prism[Stream[A], (A, Stream[A])]{
      case Empty    => None
      case x #:: xs => Some((x, xs))
    }{ case (a, s) => a #:: s }
  }

  implicit def streamSnoc[A]: Snoc[Stream[A], A] = new Snoc[Stream[A], A]{
    def snoc = Prism[Stream[A], (Stream[A], A)]( s =>
      for {
        init <- if(s.isEmpty) None else Some(s.init)
        last <- if(s.isEmpty) None else Some(s.last)
      } yield (init, last)){
      case (init, last) => init :+ last
    }
  }

  implicit def streamReverse[A]: Reverse[Stream[A], Stream[A]] =
    reverseFromReverseFunction[Stream[A]](_.reverse)

}