package monocle.std

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

import scalaz.Id.Id
import scalaz.std.list._
import scalaz.std.option._
import scalaz.syntax.traverse._
import scalaz.{Applicative, \/}

object list extends ListInstances

trait ListInstances {

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

  implicit val nilEmpty: Empty[Nil.type] = new Empty[Nil.type] {
    def empty = Prism[Nil.type, Unit](_ => Some(()))(_ => Nil)
  }

  implicit def listReverse[A]: Reverse[List[A], List[A]] =
    reverseFromReverseFunction[List[A]](_.reverse)

  implicit def listEach[A]: Each[List[A], A] = Each.traverseEach[List, A]

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

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

  implicit def listCons[A]: Cons[List[A], A] = new Cons[List[A], A]{
    def cons = Prism[List[A], (A, List[A])]{
      case Nil     => None
      case x :: xs => Some((x, xs))
    }{ case (a, s) => a :: s }
  }

  implicit def listSnoc[A]: Snoc[List[A], A] = new Snoc[List[A], A]{
    def snoc = Prism[List[A], (List[A], A)](
      s => Applicative[Option].apply2(\/.fromTryCatchNonFatal(s.init).toOption, s.lastOption)((_,_))){
      case (init, last) => init :+ last
    }
  }

}