-- error handling with a custom monad 
-- say we want to divide a list of numbers by a divisor
-- if the divisor is even, we throw an error, then apply ^2 to the rest of the numbers
-- if the divisor is odd, we return the result
-- if the divisor is 0, we throw an error

-- custom monad
data MyMonad a = DivByZeroError String | DivByEven a | DivByOdd a deriving (Show)

-- instance of Functor
instance Functor MyMonad where -- Functor is where we define fmap (i.e. map)
    -- customMap, where we have 2 functions, one for even and one for odd
    fmap f (DivByZeroError s) = DivByZeroError s
    fmap f (DivByEven a) = DivByEven (f a)
    fmap f (DivByOdd a) = DivByOdd (f a)

-- instance of Applicative
instance Applicative MyMonad where
    (DivByZeroError s) <*> _ = DivByZeroError s
    -- DivByEvenError -> ^2
    (DivByEven a) <*> b = fmap a b -- 
    (DivByOdd a) <*> b = fmap a b
    pure = DivByOdd

-- instance of Monad
instance Monad MyMonad where
    return = pure
    (DivByZeroError s) >>= _ = DivByZeroError s
    (DivByEven a) >>= f =
        case f a of
            DivByOdd b -> DivByEven b  -- propagate the "evenness context" forward
            DivByEven b -> DivByEven b -- keep even state if it continues
            DivByZeroError s -> DivByZeroError s
    (DivByOdd a) >>= f = f a

safeDivide :: Int -> [Int] -> MyMonad [Int]
safeDivide 0 _ = DivByZeroError "Division by zero"
safeDivide d xs
    | even d    = DivByEven (map (^2) xs)
    | otherwise = DivByOdd (map (`div` d) xs)

main :: IO ()
main = do
    let nums = [69, 420, 666, 1337]
    let divisor = 2
    let result = safeDivide divisor nums

    let nums2 = [1,2,3,4,5]
    let divisor2 = 0
    let result2 = safeDivide divisor2 nums2

    let nums3 = [69, 420, 666, 1337]
    let divisor3 = 3
    let result3 = safeDivide divisor3 nums3

    print result
    print result2
    print result3








