/* CSci 555, Functional Programming, Spring 2019
    Wrapping Exception API with Option and Either
    H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2019-03-07: Original

* */

import List._    // use List from FPS Ch. 3

import scala.{Option => _, Either => _, _} // hide standard
import Option._  // use Option from FPS Ch. 4
import Either._  // use Either from FPS Ch. 4

object WrapException {

  // Specific function for tailList and Option
  def tailOption[A](ls: List[A]): Option[List[A]] = {
    try Some(tailList(ls))
    catch { case ex: RuntimeException => None }
  }

  // Specific function for tailList and Either
  def tailEither[A](ls: List[A]): Either[Exception,List[A]] = {
    try Right(tailList(ls))
    catch { case ex: RuntimeException => Left(ex) }
  }

  // General function to wrap RuntimeException with Option
  // Use by-name parameter to defer evaluation
  // Add to Option2.scala?
  def tryOption[A](attempt: => A): Option[A] = {
    try Some(attempt)  // evaluate thunk
    catch { case ex: RuntimeException => None }
  }

  // Using tryOption
  def tailOption2[A](ls: List[A]): Option[List[A]] = 
    tryOption(tailList(ls))

  // General function to wrap RuntimeException with Either
  // Use by-name parameter to defer evaluation
  // Add to Either2.scala?
  def tryEither[A](attempt: => A): Either[Exception,A] = {
    try Right(attempt)  // evaluate thunk
    catch { case ex: RuntimeException => Left(ex) }
  }
 
  // Using tryEither
  def tailEither2[A](ls: List[A]): Either[Exception,List[A]] = 
    tryEither(tailList(ls))

  // Main method for testing
  def main(args: Array[String]) {
    val l1 = Nil
    val l2 = Cons(1,Nil)

    println(s"tailOption(l1)          = ${tailOption(l1)}")
    println(s"tailOption(l2)          = ${tailOption(l2)}")
    println(s"tailEither(l1)          = ${tailEither(l1)}")
    println(s"tailEither(l2)          = ${tailEither(l2)}")
    println(s"tryOption(tailList(l1)) = ${tryOption(tailList(l1))}")
    println(s"tryOption(tailList(l2)) = ${tryOption(tailList(l2))}")
    println(s"tryEither(tailList(l1)) = ${tryEither(tailList(l1))}")
    println(s"tryEither(tailList(l2)) = ${tryEither(tailList(l2))}")
    println(s"tailOption2(l1)         = ${tailOption2(l1)}")
    println(s"tailOption2(l2)         = ${tailOption2(l2)}")
    println(s"tailEither2(l1)         = ${tailEither2(l1)}")
    println(s"tailEither2(l2)         = ${tailEither2(l2)}")
  } 
}
