-- I AM NOT DONE

import Data.List (sort)

import Test.Tasty
import Test.Tasty.HUnit



 - The ' Read ' and '  ' typeclass are two other useful typeclasses that can 
   be easily derived most of the time . 

 - ' Read ' is the " inverse " of ' Show ' . It allows us to create a type from a String 

 read : : String - > a 

 simpleInts : : [ Int ] 
 simpleInts = map read [ " 1 " , " 2 " , " 3 " ] -- > [ 1 , 2 , 3 ] 

 - '  ' allows us to provide an " ordering " on objects . That is , we can use 
   operators like < , > , > = , and < = . The ' minimal complete definition ' requires 
   implementing ' ( < =) ' or a ' compare ' function . The latter takes two objects 
   and produces an " Ordering " . 

 -- Greater Than , Less Than , Equal 
 data Ordering = GT | LT | EQ 

 compare : : a - > a - > Ordering 

 compare 1 2 - > LT 
 compare " Hello " " Hello " - > EQ 
 compare " Hello " " Goodbye " - > GT 

 - So far , all of our constrained functions have had a single input type . 
   Note however , that we can have multiple variable types with different 
   constraints ! 

 readAndCompare : : ( Read a , Eq b ) = > ( a - > b ) - > String - > b - > Bool 
 readAndCompare f input expected = f ( read input ) = = expected 



- The 'Read' and 'Ord' typeclass are two other useful typeclasses that can
  be easily derived most of the time.

- 'Read' is the "inverse" of 'Show'. It allows us to create a type from a String

read :: String -> a

simpleInts :: [Int]
simpleInts = map read ["1", "2", "3"] -- > [1, 2, 3]

- 'Ord' allows us to provide an "ordering" on objects. That is, we can use
  operators like <, >, >=, and <=. The 'minimal complete definition' requires
  implementing '(<=)' or a 'compare' function. The latter takes two objects
  and produces an "Ordering".

-- Greater Than, Less Than, Equal
data Ordering = GT | LT | EQ

compare :: a -> a -> Ordering

compare 1 2 -> LT
compare "Hello" "Hello" -> EQ
compare "Hello" "Goodbye" -> GT

- So far, all of our constrained functions have had a single input type.
  Note however, that we can have multiple variable types with different
  constraints!

readAndCompare :: (Read a, Eq b) => (a -> b) -> String -> b -> Bool
readAndCompare f input expected = f (read input) == expected

-}

-- TODO:

  Here we have two different ' Adult ' types . Define a different '  '
  instance for each one . The first should order people by using
  first name * and then * last name . The second should reverse this ,
  ordering by last name * and then * first name .
-- Try deriving the instance to see which behavior is the default!
data Adult1 = Adult1 String String Int
  deriving (Show, Eq)

data Adult2 = Adult2 String String Int
  deriving (Show, Eq)

  Derive both '  ' and ' Read ' for this type .
newtype InterestRate = InterestRate Double
  deriving (Eq, Show)

  This function should take two tuples , of variable types ( a , b )
-- The 'a' type represents the person, the 'b' type represents
-- their interest rate. Whoever has the higher rate, return a string with
-- "'{p}' has a higher interest rate!", replacing {p} with 'show'-ing the person.
-- Fill in the type signature!
returnHigherInterestRate :: ???
returnHigherInterestRate = undefined

-- Test Code

main :: IO ()
main = defaultMain $ testGroup "Typeclasses3" $
  [ testCase "Ordering Adults 1" $ sort [Adult1 "Zach" "Whittaker" 31, Adult1 "John" "Smith" 45, Adult1 "Thomas" "Allen" 46] @?=
      [Adult1 "John" "Smith" 45, Adult1 "Thomas" "Allen" 46, Adult1 "Zach" "Whittaker" 31]
  , testCase "Ordering Adults 2" $ sort [Adult2 "Zach" "Whittaker" 31, Adult2 "John" "Smith" 45, Adult2 "Thomas" "Allen" 46] @?=
      [Adult2 "Thomas" "Allen" 46, Adult2 "John" "Smith" 45, Adult2 "Zach" "Whittaker" 31]
  , testCase "Read Interest Rate" $ map read ["InterestRate 0.5", "InterestRate 0.3", "InterestRate 0.788"] @?= [InterestRate 0.5, InterestRate 0.3, InterestRate 0.788]
  , testCase "Higher Interest Rate 1" $ returnHigherInterestRate ("John", 0.3) ("Tom", 0.03) @?=
      "'\"John\"' has a higher interest rate!"
  , testCase "Higher Interest Rate 1" $ returnHigherInterestRate (Adult1 "John" "Smith" 15, InterestRate 0.07) (Adult1 "Tom" "Allen" 18, InterestRate 0.1) @?=
      "'Adult1 \"Tom\" \"Allen\" 18' has a higher interest rate!"
  ]

 I AM NOT DONE
 > [ 1 , 2 , 3 ] 
 Greater Than , Less Than , Equal 
 > [1, 2, 3]
 Greater Than, Less Than, Equal
 TODO:
 Try deriving the instance to see which behavior is the default!
 The 'a' type represents the person, the 'b' type represents
 their interest rate. Whoever has the higher rate, return a string with
 "'{p}' has a higher interest rate!", replacing {p} with 'show'-ing the person.
 Fill in the type signature!
 Test Code

import Data.List (sort)

import Test.Tasty
import Test.Tasty.HUnit



 - The ' Read ' and '  ' typeclass are two other useful typeclasses that can 
   be easily derived most of the time . 

 - ' Read ' is the " inverse " of ' Show ' . It allows us to create a type from a String 

 read : : String - > a 

 simpleInts : : [ Int ] 

 - '  ' allows us to provide an " ordering " on objects . That is , we can use 
   operators like < , > , > = , and < = . The ' minimal complete definition ' requires 
   implementing ' ( < =) ' or a ' compare ' function . The latter takes two objects 
   and produces an " Ordering " . 

 data Ordering = GT | LT | EQ 

 compare : : a - > a - > Ordering 

 compare 1 2 - > LT 
 compare " Hello " " Hello " - > EQ 
 compare " Hello " " Goodbye " - > GT 

 - So far , all of our constrained functions have had a single input type . 
   Note however , that we can have multiple variable types with different 
   constraints ! 

 readAndCompare : : ( Read a , Eq b ) = > ( a - > b ) - > String - > b - > Bool 
 readAndCompare f input expected = f ( read input ) = = expected 



- The 'Read' and 'Ord' typeclass are two other useful typeclasses that can
  be easily derived most of the time.

- 'Read' is the "inverse" of 'Show'. It allows us to create a type from a String

read :: String -> a

simpleInts :: [Int]

- 'Ord' allows us to provide an "ordering" on objects. That is, we can use
  operators like <, >, >=, and <=. The 'minimal complete definition' requires
  implementing '(<=)' or a 'compare' function. The latter takes two objects
  and produces an "Ordering".

data Ordering = GT | LT | EQ

compare :: a -> a -> Ordering

compare 1 2 -> LT
compare "Hello" "Hello" -> EQ
compare "Hello" "Goodbye" -> GT

- So far, all of our constrained functions have had a single input type.
  Note however, that we can have multiple variable types with different
  constraints!

readAndCompare :: (Read a, Eq b) => (a -> b) -> String -> b -> Bool
readAndCompare f input expected = f (read input) == expected

-}


  Here we have two different ' Adult ' types . Define a different '  '
  instance for each one . The first should order people by using
  first name * and then * last name . The second should reverse this ,
  ordering by last name * and then * first name .
data Adult1 = Adult1 String String Int
  deriving (Show, Eq)

data Adult2 = Adult2 String String Int
  deriving (Show, Eq)

  Derive both '  ' and ' Read ' for this type .
newtype InterestRate = InterestRate Double
  deriving (Eq, Show)

  This function should take two tuples , of variable types ( a , b )
returnHigherInterestRate :: ???
returnHigherInterestRate = undefined


main :: IO ()
main = defaultMain $ testGroup "Typeclasses3" $
  [ testCase "Ordering Adults 1" $ sort [Adult1 "Zach" "Whittaker" 31, Adult1 "John" "Smith" 45, Adult1 "Thomas" "Allen" 46] @?=
      [Adult1 "John" "Smith" 45, Adult1 "Thomas" "Allen" 46, Adult1 "Zach" "Whittaker" 31]
  , testCase "Ordering Adults 2" $ sort [Adult2 "Zach" "Whittaker" 31, Adult2 "John" "Smith" 45, Adult2 "Thomas" "Allen" 46] @?=
      [Adult2 "Thomas" "Allen" 46, Adult2 "John" "Smith" 45, Adult2 "Zach" "Whittaker" 31]
  , testCase "Read Interest Rate" $ map read ["InterestRate 0.5", "InterestRate 0.3", "InterestRate 0.788"] @?= [InterestRate 0.5, InterestRate 0.3, InterestRate 0.788]
  , testCase "Higher Interest Rate 1" $ returnHigherInterestRate ("John", 0.3) ("Tom", 0.03) @?=
      "'\"John\"' has a higher interest rate!"
  , testCase "Higher Interest Rate 1" $ returnHigherInterestRate (Adult1 "John" "Smith" 15, InterestRate 0.07) (Adult1 "Tom" "Allen" 18, InterestRate 0.1) @?=
      "'Adult1 \"Tom\" \"Allen\" 18' has a higher interest rate!"
  ]
