module Chapter04 where
import Data.Char (isSpace, digitToInt, ord, isDigit) 
import Data.List (foldl')


-- Exercise 1
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead xs = Just (head xs)

safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail xs = Just (tail xs)

safeLast :: [a] -> Maybe a
safeLast [] = Nothing
safeLast xs = Just (last xs)

safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit xs = Just (init xs)


-- Exercise 2
 -- preserving all list members
splitWith :: (a -> Bool) -> [a] -> [[a]]
splitWith _ []     = []
splitWith p (x:xs) = (x : pre) : splitWith p suf
    where (pre,suf) = span p xs

-- removing all list members where p x is False
splitWith' :: (a-> Bool) -> [a] -> [[a]]
splitWith' _ [] = []
splitWith' p xs = 
    case pre of
      [] -> splitSuffix
      _  -> pre : splitSuffix
    where
      (pre,suf)    = span p xs
      splitSuffix  = splitWith' p (dropWhile (not . p) suf)
            

-- Exercise 3
nonSpace :: Char -> Bool
nonSpace = not . isSpace

firstWord ws = takeWhile nonSpace $ dropWhile isSpace ws

-- Folds section
-- Exercise 1

-- asInt :: String -> Int
-- asInt xs = loop 0 xs

-- loop :: Int -> String -> Int
-- loop acc [] = acc
-- loop acc (x:xs) = let acc' = acc * 10 + digitToInt x
--                   in loop acc' xs

asInt_fold :: String -> Int
asInt_fold ('-':xs) = - asInt_fold xs
asInt_fold xs       = foldl shiftRightAndAdd 0 xs
    where 
      -- shiftRightAndAdd :: Int -> Char -> Int
      shiftRightAndAdd acc x = 10 * acc + digitToInt x

-- Exercise 2
type ErrorMessage = String

asInt_either :: String -> Either ErrorMessage Int
asInt_either ('-':[]) = Left "No digits supplied"
asInt_either ('-':xs) = case asInt_either xs of
                          Left mess -> Left mess
                          Right val -> Right (negate val)
asInt_either xs       = foldl radixAdd (Right 0) xs 
    where 
      --radixAdd :: Either ErrorMessage Int -> Char -> Either ErrorMessage Int
      radixAdd acc x = 
          case acc of
            Left mess -> Left mess
            Right acc -> digitToInt' x 
                where --digitToInt' :: Char -> Either ErrorMessage Int
                      digitToInt' x = if isDigit x
                                      then Right (10 * acc + digitToInt x)
                                      else Left ("non digit '" ++ x:"'")

-- Exercise 3
baConcat :: [[a]] -> [a]
baConcat = foldr (++) []

-- Exercise 4
baTakeWhile :: (a -> Bool) -> [a] -> [a]
baTakeWhile f [] = []
baTakeWhile f (x:xs) 
 | f x       = x : baTakeWhile f xs
 | otherwise = []

baTakeWhile' :: (a -> Bool) -> [a] -> [a]
baTakeWhile' f xs = foldr takeStep [] xs
    where takeStep x acc = if f x then x:acc else []

-- Exercise 5
baGroupBy :: (a -> a -> Bool) -> [a] -> [[a]]
baGroupBy f xs = foldl step [] xs
    where
    step [] x  = [[x]]
    step acc x = 
        if f (head lastAdded) x
        then init acc ++ [lastAdded ++ [x]]
        else acc ++ [[x]] 
            where lastAdded = last acc
                                

-- Exercise 6
baAny :: (a -> Bool) -> [a] -> Bool
baAny p = foldr or False
    where or x acc = p x || acc 

baCycle :: [a] -> [a]
baCycle xs = foldr selfAppend xs [1..]
    where selfAppend x ys = xs ++ ys

baWords :: String -> [String]
baWords ws = if null headWord 
             then tailWords
             else headWord:tailWords
    where 
      (headWord, tailWords) = foldr buildWord ("",[]) ws
      buildWord c (thisWord,allWords) | isSpace c = if null thisWord 
                                                    then ("", allWords) 
                                                    else ("", thisWord:allWords)
                                      | otherwise = (c:thisWord, allWords)

baUnlines :: [String] -> String
baUnlines ls = foldr newLine [] ls
    where newLine w ws = w ++ "\n" ++ ws

