package monocle.generic

import monocle._
import monocle.function._
import shapeless.{Lens => _, _}
import shapeless.ops.hlist.{Reverse => HReverse, Prepend, IsHCons, ReplaceAt, At, Init => HInit, Last => HLast}


object hlist extends HListInstances

trait HListInstances {

  def toHList[S, A <: HList](implicit gen: Generic.Aux[S, A]): Iso[S, A] =
    Iso[S, A]{ s => gen.to(s)}{l => gen.from(l)}

  def fromHList[S <: HList, A](implicit gen: Generic.Aux[A, S]): Iso[S, A] =
    toHList.reverse

  implicit def hListReverse[S <: HList, A <: HList](implicit ev1: HReverse.Aux[S, A],
                                                    ev2: HReverse.Aux[A, S]): Reverse[S, A] = new Reverse[S, A]{
    def reverse = Iso[S, A](ev1.apply)(ev2.apply)
  }

  implicit def hListCons1[S <: HList, H, T <: HList]
     (implicit evIsCons: IsHCons.Aux[S, H, T],
              evPrepend: Prepend.Aux[H :: HNil, T, S]): Cons1[S, H, T] = new Cons1[S, H, T] {
    def cons1 = Iso[S, (H, T)](s => (evIsCons.head(s), evIsCons.tail(s))){ case (h, t) => evPrepend(h :: HNil, t) }
  }

  implicit def hListSnoc1[S <: HList, I <: HList, L]
   (implicit evInit: HInit.Aux[S, I],
             evLast: HLast.Aux[S, L],
          evPrepend: Prepend.Aux[I, L :: HNil, S]): Snoc1[S, I, L] = new Snoc1[S, I, L] {
    def snoc1 = Iso[S, (I, L)](s => (evInit(s), evLast(s))){ case (i, l) => evPrepend(i, l :: HNil) }
  }

  implicit def hListField1[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._0.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._0.N, A, (A, S)]) = new Field1[S, A] {
    def first = hListAt(shapeless.nat._0)
  }

  implicit def hListField2[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._1.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._1.N, A, (A, S)]) = new Field2[S, A] {
    def second = hListAt(shapeless.nat._1)
  }

  implicit def hListField3[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._2.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._2.N, A, (A, S)]) = new Field3[S, A] {
    def third = hListAt(shapeless.nat._2)
  }

  implicit def hListField4[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._3.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._3.N, A, (A, S)]) = new Field4[S, A] {
    def fourth = hListAt(shapeless.nat._3)
  }

  implicit def hListField5[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._4.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._4.N, A, (A, S)]) = new Field5[S, A] {
    def fifth = hListAt(shapeless.nat._4)
  }

  implicit def hListField6[S <: HList, A](implicit evAt: At.Aux[S, shapeless.nat._5.N, A],
                                              evReplace: ReplaceAt.Aux[S, shapeless.nat._5.N, A, (A, S)]) = new Field6[S, A] {
    def sixth = hListAt(shapeless.nat._5)
  }


  private def hListAt[S <: HList, A](n : Nat)(implicit evAt: At.Aux[S, n.N, A],
                                                  evReplace: ReplaceAt.Aux[S, n.N, A, (A, S)]): Lens[S, A]  =
    Lens[S, A](_.at(n))(a => hlist => hlist.updatedAt(n, a) )

}