-- | Core (shared) functions.
module Sound.SC3.Lang.Core where

import Data.Maybe {- base -}
import Data.Monoid {- base -}

-- * "Data.Function" variants

-- | 'fmap' '.' 'fmap', ie. @(t -> c) -> (a -> b -> t) -> a -> b -> c@.
(.:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(.:) = fmap . fmap

-- | 'fmap' '.' '.:', ie. @(t -> d) -> (a -> b -> c -> t) -> a -> b -> c -> d@.
(.::) :: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))
(.::) = fmap . (.:)

-- | 'fmap' '.' '.::'.
(.:::) :: (Functor f, Functor g, Functor h,Functor i) => (a -> b) -> f (g (h (i a))) -> f (g (h (i b)))
(.:::) = fmap . (.::)

-- | 'fmap' '.' '.:::'.
(.::::) :: (Functor f, Functor g, Functor h,Functor i,Functor j) => (a -> b) -> f (g (h (i (j a)))) -> f (g (h (i (j b))))
(.::::) = fmap . (.:::)

-- | 'fmap' '.' '.::::'.
(.:::::) :: (Functor f, Functor g, Functor h,Functor i,Functor j,Functor k) => (a -> b) -> f (g (h (i (j (k a))))) -> f (g (h (i (j (k b)))))
(.:::::) = fmap . (.::::)

-- * "Data.List" variants

-- | Variant that either takes precisely /n/ elements or 'Nothing'.
--
-- > map (genericTake 3) (inits "abc") == inits "abc"
-- > Data.Maybe.mapMaybe (genericTakeMaybe 3) (inits "abc") == ["abc"]
genericTakeMaybe :: Integral i => i -> [a] -> Maybe [a]
genericTakeMaybe n l =
    case compare n 0 of
      LT -> Nothing
      EQ -> Just []
      GT -> case l of
              [] -> Nothing
              e : l' -> fmap (e :) (genericTakeMaybe (n - 1) l')

-- | Inverse of 'Data.List.:'.
--
-- > map uncons [[],1:[]] == [(Nothing,[]),(Just 1,[])]
uncons :: [a] -> (Maybe a,[a])
uncons l =
    case l of
      [] -> (Nothing,[])
      x:l' -> (Just x,l')

-- | 'Maybe' variant of '!!'.
--
-- > map (lindex "str") [2,3] == [Just 'r',Nothing]
lindex :: [a] -> Int -> Maybe a
lindex l n =
    if n < 0
    then Nothing
    else case (l,n) of
           ([],_) -> Nothing
           (x:_,0) -> Just x
           (_:l',_) -> lindex l' (n - 1)

-- | If /n/ is 'maxBound' this is 'id', else it is 'take'.
take_inf :: Int -> [a] -> [a]
take_inf n = if n == maxBound then id else take n

-- | Variant of 'transpose' for /fixed width/ interior lists.  Holes
-- are represented by 'Nothing'.
--
-- > transpose_fw undefined [] == []
--
-- > transpose [[1,3],[2,4]] == [[1,2],[3,4]]
-- > transpose_fw 2 [[1,3],[2,4]] == [[Just 1,Just 2],[Just 3,Just 4]]
--
-- > transpose [[1,5],[2],[3,7]] == [[1,2,3],[5,7]]
--
-- > transpose_fw 2 [[1,4],[2],[3,6]] == [[Just 1,Just 2,Just 3]
-- >                                     ,[Just 4,Nothing,Just 6]]
--
-- This function is more productive than 'transpose' for the case of
-- an infinite list of finite lists.
--
-- > map head (transpose_fw 4 (repeat [1..4])) == map Just [1,2,3,4]
-- > map head (transpose (repeat [1..4])) == _|_
transpose_fw :: Int -> [[a]] -> [[Maybe a]]
transpose_fw w l =
    if null l
    then []
    else let f n = map (`lindex` n) l
         in map f [0 .. w - 1]

-- | Variant of 'transpose_fw' with default value for holes.
transpose_fw_def :: a -> Int -> [[a]] -> [[a]]
transpose_fw_def def w l =
    let f n = map (fromMaybe def . (`lindex` n)) l
    in map f [0 .. w - 1]

-- | Variant of 'transpose_fw_def' deriving /width/ from first element.
transpose_fw_def' :: a -> [[a]] -> [[a]]
transpose_fw_def' def l =
    case l of
      [] -> []
      h:_ -> transpose_fw_def def (length h) l

-- | A 'transpose' variant, halting when first hole appears.
--
-- > transpose_st [[1,2,3],[4,5,6],[7,8]] == [[1,4,7],[2,5,8]]
transpose_st :: [[a]] -> [[a]]
transpose_st l =
    let (h,l') = unzip (map uncons l)
    in case all_just h of
         Just h' -> h' : transpose_st l'
         Nothing -> []

-- * Data.Maybe variants

-- | Variant of 'catMaybes' that returns 'Nothing' unless /all/
-- elements are 'Just'.
--
-- > map all_just [[Nothing,Just 1],[Just 0,Just 1]] == [Nothing,Just [0,1]]
all_just :: [Maybe a] -> Maybe [a]
all_just =
    let rec r l =
            case l of
              [] -> Just (reverse r)
              Nothing:_ -> Nothing
              Just e:l' -> rec (e:r) l'
    in rec []

-- * Data.Monoid variants

-- | 'mconcat' of 'repeat', for lists this is 'cycle'.
--
-- > [1,2,3,1,2] `isPrefixOf` take 5 (mcycle [1,2,3])
mcycle :: Monoid a => a -> a
mcycle = mconcat . repeat