content: module Person where
  
  import Control.Lens
  import Data.List (isInfixOf)
  import Data.Validation
  
  newtype Name = Name { unName :: String } deriving Show
  newtype Email = Email { unEmail :: String } deriving Show
  newtype Age = Age { unAge :: Int } deriving Show
  
  data Person = Person { name :: Name
                       , email :: Email
                       , age :: Age
                       } deriving Show
  
  data Error = NameBetween1And50
             | EmailMustContainAtChar
             | AgeBetween0and120
             deriving Show
  
  -- Smart constructors
  mkName :: String -> Validation [Error] Name
  mkName s = let l = length s
             in if l >= 1 && l <= 50
                then _Success # Name s
                else _Failure # [ NameBetween1And50 ]
  
  
  mkEmail :: String -> Validation [Error] Email
  mkEmail s = if "@" `isInfixOf` s
              then _Success # Email s
              else _Failure # [ EmailMustContainAtChar ]
  
  mkAge :: Int -> Validation [Error] Age
  mkAge a = if a >= 0 && a <= 120
            then _Success # Age a
            else _Failure # [ AgeBetween0and120 ]
  
  mkPerson :: String -> String -> Int -> Validation [Error] Person
  mkPerson pName pEmail pAge =
    Person
    <$> mkName pName
    <*> mkEmail pEmail
    <*> mkAge pAge
  
  -- Examples
  -- Data constructors for `Name`, `Age`, `Email`, and `Person` should not be
  -- exported to the example code below:
  
  validPerson :: Validation [Error] Person
  validPerson = mkPerson "Bob" "bob@gmail.com" 25
  -- Success (Person {name = Name {unName = "Bob"}, email = Email {unEmail = "bob@gmail.com"}, age = Age {unAge = 25}})
  
  badName :: Validation [Error] Person
  badName = mkPerson "" "bob@gmail.com" 25
  -- Failure [NameBetween1And50]
  
  badEmail :: Validation [Error] Person
  badEmail = mkPerson "Bob" "bademail" 25
  -- Failure [EmailMustContainAtChar]
  
  badAge :: Validation [Error] Person
  badAge = mkPerson "Bob" "bob@gmail.com" 150
  -- Failure [AgeBetween0and120]
  
  badEverything :: Validation [Error] Person
  badEverything = mkPerson "" "bademail" 150
  -- Failure [NameBetween1And50,EmailMustContainAtChar,AgeBetween0and120]
  
  asMaybeGood :: Maybe Person
  asMaybeGood = validPerson ^? _Success
  -- Just (Person {name = Name {unName = "Bob"}, email = Email {unEmail = "bob@gmail.com"}, age = Age {unAge = 25}})
  
  asMaybeBad :: Maybe Person
  asMaybeBad = badEverything ^? _Success
  -- Nothing
  
  asEitherGood :: Either [Error] Person
  asEitherGood = validPerson ^. _Either
  -- Right (Person {name = Name {unName = "Bob"}, email = Email {unEmail = "bob@gmail.com"}, age = Age {unAge = 25}})
  
  asEitherBad :: Either [Error] Person
  asEitherBad = badEverything ^. _Either
  -- Left [NameBetween1And50,EmailMustContainAtChar,AgeBetween0and120]
  
  main :: IO ()
  main = do
    putStrLn $ "validPerson:   " ++ show validPerson
    putStrLn $ "badName:       " ++ show badName
    putStrLn $ "badEmail:      " ++ show badEmail
    putStrLn $ "badAge:        " ++ show badAge
    putStrLn $ "badEverything: " ++ show badEverything
    putStrLn $ "asMaybeGood:   " ++ show asMaybeGood
    putStrLn $ "asMaybeBad:    " ++ show asMaybeBad
    putStrLn $ "asEitherGood:  " ++ show asEitherGood
    putStrLn $ "asEitherBad:   " ++ show asEitherBad
  