{-|
Module: Utils
Description: Helper functions not directly specific to Tidal
-}
module Sound.Tidal.Utils where

import Data.Maybe (listToMaybe)

{- | enumerate a list of things

>>> enumerate ["foo","bar","baz"]
[(1,"foo"), (2,"bar"), (3,"baz")]
-}
enumerate :: [a] -> [(Int, a)]
enumerate = zip [0..]

-- | apply @f@ to the first element of a tuple
mapFst :: (a -> b) -> (a, c) -> (b, c)
mapFst f (x,y) = (f x,y)

-- | apply function to the first value of each tuple in given list
mapFsts :: (a -> b) -> [(a, c)] -> [(b, c)]
mapFsts = map . mapFst

-- | apply @f@ to the second element of a tuple
mapSnd :: (a -> b) -> (c, a) -> (c, b)
mapSnd f (x,y) = (x,f y)

-- | apply function to the second value of each tuple in given list
mapSnds :: (a -> b) -> [(c, a)] -> [(c, b)]
mapSnds = fmap . mapSnd


{- | split given list of @a@ by given single a, e.g.

>>> wordsBy (== ':') "bd:3"
["bd", "3"]
-}
wordsBy :: (a -> Bool) -> [a] -> [[a]]
wordsBy p s = case dropWhile p s of
   []      -> []
   s':rest -> (s':w) : wordsBy p (drop 1 s'')
          where (w, s'') = break p rest

maybeRead :: String -> Maybe Double
maybeRead = fmap fst . listToMaybe . reads

-- | shorthand for first element of triple
fst' (a, _, _) = a
-- | shorthand for second element of triple
snd' (_, b, _) = b
-- | shorthand for third element of triple
thd' (_, _, c) = c

-- | apply @f@ to the first element of a triple
mapFst' :: (a -> x) -> (a, b, c) -> (x, b, c)
mapFst' f (x,y,z) = (f x,y,z)

-- | apply @f@ to the second element of a triple
mapSnd' :: (b -> x) -> (a, b, c) -> (a, x, c)
mapSnd' f (x,y,z) = (x,f y,z)

-- | apply @f@ to the third element of a triple
mapThd' :: (c -> x) -> (a, b, c) -> (a, b, x)
mapThd' f (x,y,z) = (x,y,f z)

-- | apply function to the second value of each triple in given list
mapFsts' :: (a -> x) -> [(a, b, c)] -> [(x, b, c)]
mapFsts' = fmap . mapFst'

-- | apply function to the second value of each triple in given list
mapSnds' :: (b -> x) -> [(a, b, c)] -> [(a, x, c)]
mapSnds' = fmap . mapSnd'

-- | apply function to the third value of each triple in given list
mapThds' :: (c -> x) -> [(a, b, c)] -> [(a, b, x)]
mapThds' = fmap . mapThd'

-- | map @f@ over a given list of arcs
mapArcs :: (a -> a) -> [(a, a, x)] -> [(a, a, x)]
mapArcs f = (mapFsts' f) . (mapSnds' f)

{- | combines two lists by interleaving them

>>> mergelists [1,2,3] [9,8,7]
[1,9,2,8,3,7]
-}
mergelists :: [a] -> [a] -> [a]
mergelists xs     []     = xs
mergelists []     ys     = ys
mergelists (x:xs) (y:ys) = x : y : mergelists xs ys

{- | like `!!` selects @n@th element from xs, but wraps over at the end of @xs@

>>> map ((!!!) [1,3,5]) [0,1,2,3,4,5]
[1,3,5,1,3,5]
-}
(!!!) :: [a] -> Int -> a
(!!!) xs n = xs !! (n `mod` length xs)

accumulate :: Num t => [t] -> [t]
accumulate = accumulate' 0
  where accumulate' _ [] = []
        accumulate' n (a:xs) = (n+a):(accumulate' (n+a) xs)