module Lastik.Util(
                   (>>>>),
                   (>>>>>),
                   quote,
                   (>===<),
                   (~~),
                   (~??),
                   (~?),
                   param,
                   many,
                   manys,
                   (~~~~>),
                   (~~~>),
                   (~~>),
                   (-~>),
                   (^^^),
                   space,
                   space',
                   uncons,
                   ifM
                  ) where

import System.Exit
import Control.Applicative hiding (many)
import Control.Monad
import Data.Maybe
import Data.Monoid
import Data.List
import System.FilePath

-- | Applies the second value only if the first produces @ExitSuccess@.
(>>>>) :: (Applicative f) => f ExitCode -> f ExitCode -> f ExitCode
(>>>>) = let ExitSuccess `k` q = q
             p `k` _= p
         in liftA2 k

-- | Executes the second action only if the first produces @ExitSuccess@.
(>>>>>) :: (Monad m) => m ExitCode -> m () -> m ()
p >>>>> q = do e <- p
               when (e == ExitSuccess) q

-- | Surrounds the given string in double-quotes.
quote :: String -> String
quote s = '"' : s ++ ['"']

-- | Surrounds each string in the list with double-quotes then intercalates the other given value.
(>===<) :: String -> [String] -> String
s >===< k = intercalate s (fmap quote k)

-- | An empty list if the boolean is @False@ otherwise the given string value with @'-'@ prepended.
(~~) :: String -> Bool -> String
g ~~ k = if k then '-' : g else []

-- | If the given list of file paths is empty, then returns the empty list. Otherwise prepend @'-'@ to the string followed by @' '@ then the search path separator intercalated in the list of file paths.
--
-- > Posix
-- > "123" ~?? ["abc", "def"] == "-123 \"abc\":\"def\""
-- > "123" ~?? ["abc", "def", "ghi"] == "-123 \"abc\":\"def\":\"ghi\""
(~??) :: String -> [FilePath] -> String
s ~?? [] = []
s ~?? z = '-' : s ++ ' ' : [searchPathSeparator] >===< z

-- | If the given value is @Nothing@ return the empty list, otherwise run the given function.
(~?) :: (k -> [a]) -> Maybe k -> [a]
(~?) = maybe []

-- | If the given value is @Nothing@ return the empty list, otherwise prepend @'-'@ to the given string followed by the given character followed by surrounding the result of running the given function in double-quotes.
--
-- > param "abc" 'x' id (Just "tuv") == "-abcx\"tuv\""
-- > param "abc" 'x' id Nothing == ""
param :: String -> Char -> (k -> String) -> Maybe k -> String
param k c s = (~?) (\z -> '-' : k ++ c : quote (s z))

-- | A parameter with many values interspersed by @' '@.
--
-- > many "abc" ["tuv", "wxy"] == "-abc \"tuv\" -abc \"wxy\""
many :: String -> [String] -> String
many k v = intercalate " " $ map (k ~~~~>) v

-- | A parameter with many values interspersed by @' '@.
--
-- > manys id "abc" ["tuv", "wxy"] == "-abc \"tuv\" -abc \"wxy\""
manys :: (a -> String) -> String -> [a] -> String
manys f k v = many k (map f v)

-- | Prepends @'-'@ followed by the first value then @' '@ then the second value surrounded by double-quotes.
--
-- > "abc" ~~~~> "def" == "-abc \"def\""
(~~~~>) :: String -> String -> String
(~~~~>) = (. Just) . (~~~>)

-- | If the given value is @Nothing@ return the empty list, otherwise prepend @'-'@ followed by the first value then @' '@ then the second value surrounded by double-quotes.
--
-- > "abc" ~~~> Just "def" == "-abc \"def\""
-- > "abc" ~~~> Nothing == ""
(~~~>) :: String -> Maybe String -> String
(~~~>) = flip (~~>) id

-- | If the given value is @Nothing@ return the empty list, otherwise prepend @'-'@ followed by the first value then @' '@ followed by surrounding the result of running the given function in double-quotes.
--
-- > "abc" ~~> id $ Just "def" == "-abc \"def\""
-- > "abc" ~~> id $ Nothing == ""
(~~>) :: String -> (k -> String) -> Maybe k -> String
(~~>) = flip param ' '

-- | If the given value is @Nothing@ return the empty list, otherwise prepend @'-'@ followed by the first value then @':'@ followed by surrounding the result of running the given function in double-quotes.
--
-- > "abc" ~~> id $ Just "def" == "-abc:\"def\""
-- > "abc" ~~> id $ Nothing == ""
(-~>) :: String -> (k -> String) -> Maybe k -> String
(-~>) = flip param ':'

-- | Removes all empty lists from the first argument the intercalates the second argument.
--
-- > ["abc", "", "def"] ^^^ "x" == "abcxdef"
(^^^) :: [[a]] -> [a] -> [a]
g ^^^ t = Data.List.intercalate t (filter (not . null) g)

-- | Surrounds each given value in double-quotes then intercalates @' '@.
--
-- > space ["abc", "def"] == "\"abc\" \"def\""
space :: [String] -> String
space = (>===<) " "

-- | Intercalates @' '@.
--
-- > space' ["abc", "def"] == "abc def"
space' :: [String] -> String
space' = intercalate " "

-- | If the given list is empty return the first argument, otherwise run the given function on the head and tail.
uncons :: a -> (b -> [b] -> a) -> [b] -> a
uncons x _ [] = x
uncons _ f (h:t) = f h t

-- | if/then/else with a lifted predicate.
ifM :: (Monad f) => f Bool -> f a -> f a -> f a
ifM c t f = do c' <- c
               t' <- t
               f' <- f
               return (if c' then t' else f')