package scalaz

////
/**
 * A type giving rise to two unrelated [[scalaz.Functor]]s.
 */
////
trait Bifunctor[F[_, _]]  { self =>
  ////

  /** `map` over both type parameters. */
  def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]

  /**The composition of Bifunctors `F` and `G`, `[x,y]F[G[x,y],G[x,y]]`, is a Bifunctor */
  def compose[G[_, _]](implicit G0: Bifunctor[G]): Bifunctor[({type λ[α, β]=F[G[α, β], G[α, β]]})] = new CompositionBifunctor[F, G] {
    implicit def F = self

    implicit def G = G0
  }

  /**The product of Bifunctors `F` and `G`, `[x,y]F[G[x,y],G[x,y]]`, is a Bifunctor */
  def product[G[_, _]](implicit G0: Bifunctor[G]): Bifunctor[({type λ[α, β]=(F[α, β], G[α, β])})] = new ProductBifunctor[F, G] {
    implicit def F = self

    implicit def G = G0
  }

  /** Extract the Functor on the first param. */
  def leftFunctor[X]: Functor[({type λ[α] = F[α, X]})] = new Functor[({type λ[α] = F[α, X]})] {
    def map[A, C](fax: F[A, X])(f: A => C): F[C, X] = bimap(fax)(f, z => z)
  }

  def leftMap[A, B, C](fab: F[A, B])(f: A => C): F[C, B] = bimap(fab)(f, z => z)

  /** Extract the Functor on the second param. */
  def rightFunctor[X]: Functor[({type λ[α] = F[X, α]})] = new Functor[({type λ[α] = F[X, α]})] {
    def map[B, D](fab: F[X, B])(g: B => D): F[X, D] = bimap(fab)(z => z, g)
  }

  def rightMap[A, B, D](fab: F[A, B])(g: B => D): F[A, D] =
    bimap(fab)(z => z, g)

  def umap[A, B](faa: F[A, A])(f: A => B): F[B, B] =
    bimap(faa)(f, f)
  ////
  val bifunctorSyntax = new scalaz.syntax.BifunctorSyntax[F] { def F = Bifunctor.this }
}

object Bifunctor {
  @inline def apply[F[_, _]](implicit F: Bifunctor[F]): Bifunctor[F] = F

  ////

  ////
}