final case class Person(name: String, age: Int)
final class EmptyNameException(message: String) extends Exception(message)
final class InvalidAgeException(message: String) extends Exception(message)

// with Exceptions

def getName(providedName: String) : String = {
    if (providedName.trim.isEmpty) throw new EmptyNameException(s"provided name is empty")
    else providedName.trim
}

def getAge(providedAge: String) : Int = {
    try {
        val age = providedAge.toInt
        if (age >= 1 && age <= 120) age
        else throw new InvalidAgeException(s"provided age is invalid: ${providedAge}")
    } catch {
        case e: NumberFormatException => throw new InvalidAgeException(s"provided age is invalid: ${providedAge}")
    }
}

//throws EmptyNameException, InvalidAgeException
def createPerson(name: String, age: String): Person = {
    Person(getName(name), getAge(age))
}

val validPairs: List[(String, String)] = 
  personStringPairs.filter { case (name, age) =>
    try {
      createPerson(name, age)
      true
    } catch {
      case e: EmptyNameException => false //if we forget to check for one of these, an Exception is thrown
      case e: InvalidAgeException => false
    }
  }


//no type-validation that these are "valid" Strings.
validPairs.map(Function.tupled(createPerson))

// with Eithers

def getName2(providedName: String): Either[String, String] = {
    if (providedName.trim.isEmpty) Left(s"provided name empty")
    else Right(providedName.trim)
}

def getAge2(providedAge: String): Either[String, Int]  = {
    try {
        val age = providedAge.toInt
        if (age >= 1 && age <= 120) Right(age)
        else Left(s"provided age is invalid: ${providedAge}")
    } catch {
        case e: NumberFormatException => Left(s"provided age is invalid: ${providedAge}")
    }
}

def createPerson2(name: String, age: String): Either[String, Person] = {
    for {
        pName <- getName2(name)
        pAge  <- getAge2(age)
    } yield Person(pName, pAge)
}

personStringPairs.map(Function.tupled(createPerson2)).collect { 
  case Right(p) => p
}

// test data

//given a List of String pairs representing names and ages:

val personStringPairs = List(("Tokyo", "30"), 
                             ("Moscow", "5o"),
                             ("The Professor", "200"),
                             ("Berlin", "43"),
                             ("Arturo Roman", "0"),
                             ("", "30"))

// create a List of only valid Person instances