package scalaz

sealed trait OptionT[M[_], A] extends NewType[M[Option[A]]] {
  def map[B](f: A => B)(implicit ftr: Functor[M]): OptionT[M, B] = new OptionT[M, B] {
    val value = ftr.fmap(OptionT.this.value, (x: Option[A]) => x map f)
  }

  def flatMap[B](f: A => OptionT[M, B])(implicit mnd: Monad[M]): OptionT[M, B] = new OptionT[M, B] {
    val value = mnd.bind(OptionT.this.value, (a: Option[A]) =>
      a match {
        case None => mnd.pure(None: Option[B])
        case Some(z) => f(z).value
      })
  }
}

object OptionT {
  import Scalaz._
  implicit def OptionTInjective[M[_]] = Injective[({type λ[α]= OptionT[M, α]})#λ]

  implicit def OptionTPure[M[_]: Pure]: Pure[({type λ[α]= OptionT[M, α]})] = new Pure[({type λ[α]=OptionT[M, α]})] {
    def pure[A](a: => A) = new OptionT[M, A] {
      val value = a.η[Option].η[M]
    }
  }

  implicit def OptionTFunctor[M[_]: Functor]: Functor[({type λ[α]=OptionT[M, α]})] = new Functor[({type λ[α]= OptionT[M, α]})] {
    def fmap[A, B](x: OptionT[M, A], f: A => B) = x map f
  }

  implicit def OptionTApply[M[_]](implicit ma: Apply[M], ftr: Functor[M]): Apply[({type λ[α]=OptionT[M, α]})] = new Apply[({type λ[α]=OptionT[M, α]})] {
    def apply[A, B](f: OptionT[M, A => B], a: OptionT[M, A]): OptionT[M, B] = new OptionT[M, B] {
      val value = f.value.<**>(a.value) { case (ff, aa) => aa <*> ff }
    }

  }

  implicit def OptionTBind[M[_]](implicit mnd: Monad[M]): Bind[({type λ[α]=OptionT[M, α]})] = new Bind[({type λ[α]=OptionT[M, α]})] {
    def bind[A, B](a: OptionT[M, A], f: A => OptionT[M, B]) =
      a flatMap f
  }

  implicit def OptionTEach[M[_]: Each]: Each[({type λ[α]=OptionT[M, α]})] = new Each[({type λ[α]= OptionT[M, α]})] {
    def each[A](x: OptionT[M, A], f: A => Unit) = x.value foreach (_ foreach f)
  }
}

trait OptionTs {
  import Scalaz._
  def optionT[M[_]] = new (({type λ[α]=M[Option[α]]})~> ({type λ[α]=OptionT[M, α]})) {
    def apply[A](a: M[Option[A]]) = new OptionT[M, A] {
      val value = a
    }
  }
}