-- We want to take a list of divisors and a number
-- We traverse the list of divisors and check if the number is divisible by the divisor
-- If the number is divisible by the return the number divided by the divisor
-- If the number is not divisible repeatedly increment the number by 1 until it is divisible by the divisor
-- If the divisor is 0, throw an error
data MyMonad a = DivByZeroError String | NotDivisible Int | Divisible a deriving (Show)

-- This is where we define fmap (i.e. map but for monads)
-- Essentially, this is where we define what happens when we apply a function to a monad
-- i.e.:
-- fmap (+1) (Divisible 3) => Divisible 4
-- fmap (+1) (NotDivisible 3) => NotDivisible 3
-- fmap (+1) (DivByZeroError "error") => DivByZeroError "error"
instance Functor MyMonad where
    fmap f (DivByZeroError s) = DivByZeroError s -- Note the error
    fmap f (Divisible a) = Divisible (f a) -- Apply the function f to the value a
    fmap f (NotDivisible a) = NotDivisible a -- Not divisible, propagate the value forward

-- This is where we define <*> (i.e. apply but for monads) essentially applying the 
-- "wrapped" function to the "wrapped" value
-- i.e.:
-- (Divisible (+1)) <*> (Divisible 3) => Divisible 4
-- (Divisible (+1)) <*> (NotDivisible 3) => NotDivisible 3
-- (Divisible (+1)) <*> (DivByZeroError "error") => DivByZeroError "error"
instance Applicative MyMonad where
    (DivByZeroError s) <*> _ = DivByZeroError s -- Note the error
    (Divisible a) <*> b = fmap a b -- Apply the function a to the value b
    (NotDivisible a) <*> b = NotDivisible a -- Not divisible, propagate the value forward
    pure = Divisible -- Wrap the value in the Divisible constructor

-- This is where we define >>=, the bind operator, which chains monadic operations
-- i.e. in this case, we chain:
-- tryDivide 2 420 >>= tryDivide 3 >>= tryDivide 5 >>= tryDivide 9 >>= tryDivide 0
-- If we encounter a DivByZeroError, we propagate it forward (i.e. we don't do anything)
-- If we encounter a NotDivisible, we propagate it forward
-- If we encounter a Divisible, we apply the function f to the value x
instance Monad MyMonad where
    DivByZeroError msg >>= _ = DivByZeroError msg -- if we encounter a DivByZeroError, we propagate it forward
    NotDivisible n >>= _ = NotDivisible n -- if we encounter a NotDivisible, we propagate it forward
    Divisible x >>= f = f x -- if we encounter a Divisible, we apply the function f to the value x


-- Try to divide the number n by d
tryDivide :: Int -> Int -> MyMonad Int
tryDivide 0 _ = DivByZeroError "Division by zero"
tryDivide d n
    | n `mod` d == 0 = Divisible (n `div` d)
    | otherwise = NotDivisible n

-- Sequentially divide the number n by the divisors in the list
sequentialDividing :: [Int] -> Int -> MyMonad Int
sequentialDividing [] n = return n
sequentialDividing (d:ds) n = do
    newN <- tryDivide d n -- Note how we're not doing any pattern matching here, we're just applying the function, and the monad takes care of the rest
    sequentialDividing ds newN

main :: IO ()
main = do
    let divisors = [2, 3, 5, 9, 0]
    let number = 420
    -- 420/2 = 210
    -- 210/3 = 70
    -- 70/5 = 14
    -- 14/9 not good => 14 is returned
    print $ sequentialDividing divisors number

    let divisors2 = [2, 3, 5, 7, 0]
    let number2 = 420
    -- 420/2 = 210
    -- 210/3 = 70
    -- 70/5 = 14
    -- 14/7 = 2
    -- 2/0 not good => error

    print $ sequentialDividing divisors2 number2


    let divisors3 = [2, 3, 5, 7, 2]
    let number3 = 420
    -- 420/2 = 210
    -- 210/3 = 70
    -- 70/5 = 14
    -- 14/7 = 2
    -- 2/2 = 1, all good => 1 is returned
    print $ sequentialDividing divisors3 number3

-- [Divisible 210,Divisible 140,Divisible 84,NotDivisible 420,DivByZeroError "Division by zero"]
-- 420 is divisible by: [2,3,5]

-- Wtf is the point of a monad here?
