{-# LANGUAGE LambdaCase, DeriveFunctor, DeriveFoldable, MultiParamTypeClasses #-}
import Prelude hiding (length, sum, fix)

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

lengthF :: ([a] -> Int) -> [a] -> Int
lengthF rec [] = 0
lengthF rec (_:xs) = 1 + rec xs

lengthF' = \rec -> \case
    [] -> 0
    _:xs -> 1 + rec xs

fix f = let x = f x in x

length' = fix lengthF

data MyList = MyNil | MyCons Int MyList
data MyListF a = MyNilF | MyConsF Int a 

newtype Fix f = Fix { unFix :: f (Fix f) }

testList :: Fix MyListF
testList = Fix (MyConsF 1 (Fix (MyConsF 2 (Fix (MyConsF 3 (Fix MyNilF))))))

myOut :: MyList -> MyListF MyList
myOut MyNil = MyNilF
myOut (MyCons i xs) = MyConsF i xs

myIn :: MyListF MyList -> MyList 
myIn MyNilF = MyNil
myIn (MyConsF i xs) = MyCons i xs

instance Functor MyListF where
    fmap f MyNilF = MyNilF
    fmap f (MyConsF i a) = MyConsF i (f a)

mySumF :: MyListF Int -> Int
mySumF MyNilF = 0
mySumF (MyConsF i rest) = i + rest

mySum :: MyList -> Int
mySum = mySumF . fmap mySum . myOut

myCata :: (MyListF a -> a) -> MyList -> a
myCata f = f . fmap (myCata f) . myOut

myLength = myCata $ \case
    MyNilF -> 0
    MyConsF _ l -> 1 + l

myMax = myCata $ \case
    MyNilF -> 0
    MyConsF x y -> max x y

myMin = myCata $ \case
    MyNilF -> 0
    MyConsF x y -> min x y

myTestList = MyCons 2 (MyCons 1 (MyCons 3 MyNil))

pack :: a -> (Int -> a -> a) -> MyListF a -> a
pack b f MyNilF = b
pack b f (MyConsF x y) = f x y

unpack :: (MyListF a -> a) -> (a, Int -> a -> a)
unpack f = (f MyNilF, \i a -> f (MyConsF i a))

class Functor f => Cata a f where
    out :: a -> f a

cata :: Cata a f => (f b -> b) -> a -> b
cata f = f . fmap (cata f) . out

instance Cata MyList MyListF where
    out = myOut

data ListF a b = Nil | Cons a b deriving Functor

instance Cata [a] (ListF a) where
    out [] = Nil
    out (x:xs) = Cons x xs

sum :: Num a => [a] -> a
sum = cata $ \case
    Nil -> 0
    Cons x xs -> x + xs

data BinaryTree a = Node a (BinaryTree a) (BinaryTree a) | Leaf deriving (Show, Foldable)
data BinaryTreeF a b = NodeF a b b | LeafF deriving Functor

instance Cata (BinaryTree a) (BinaryTreeF a) where
    out (Node a l r) = NodeF a l r
    out Leaf = LeafF

invert :: BinaryTree a -> BinaryTree a
invert = cata $ \case
    LeafF -> Leaf
    NodeF a l r -> Node a r l

data MaybeF a b = NothingF | JustF a deriving Functor

instance Cata (Maybe a) (MaybeF a) where
    out Nothing = NothingF
    out (Just x) = JustF x

getOrDefault :: a -> Maybe a -> a
getOrDefault d = cata $ \case
    NothingF -> d
    JustF a -> a
