{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveFunctor #-}
-- | Handy utilities
module Tip.Utils where

import Data.List
import Data.Maybe
import Data.Graph hiding (components)
import Data.List.Split
import Data.Char
import Data.Foldable (Foldable)
import qualified Data.Foldable as F
import Data.Function (on)
import Data.Ord
import Data.Function

-- | Sort and remove duplicates
usort :: Ord a => [a] -> [a]
usort = usortOn id

-- | Sort and remove duplicates wrt some custom ordering
usortOn :: Ord b => (a -> b) -> [a] -> [a]
usortOn f = map head . groupBy ((==) `on` f) . sortBy (comparing f)

-- | Union on a predicate
unionOn :: Ord b => (a -> b) -> [a] -> [a] -> [a]
unionOn k = unionBy ((==) `on` k)

-- | Returns the duplicates in a list
duplicates :: Ord a => [a] -> [a]
duplicates xs = usort [ x | x <- xs, count x > 1 ]
  where count x = length (filter (== x) xs)

data Component a = Rec [a] | NonRec a
  deriving (Eq,Ord,Show,Functor)

flattenComponent :: Component a -> [a]
flattenComponent (Rec xs) = xs
flattenComponent (NonRec x) = [x]

-- | Strongly connected components
components :: Ord name => (thing -> name) -> (thing -> [name]) -> [thing] -> [Component thing]
components name refers things =
    [ case comp of
        [(thing,n,refs)]
          | n `notElem` refs -> NonRec thing
        _                    -> Rec [ thing | (thing,_,_) <- comp ]
    | comp <- map flattenSCC $ stronglyConnCompR
        [ (thing,name thing,filter (`elem` names) (refers thing))
        | thing <- things
        ]
    ]
  where
  names = map name things

lookupComponent :: Eq thing => thing -> [Component thing] -> Maybe (Component thing)
lookupComponent x = listToMaybe . mapMaybe h
  where
  h c = case c of NonRec y | x == y      -> Just c
                  Rec ys   | x `elem` ys -> Just c
                  _ -> Nothing

-- | Sort things in topologically in strongly connected components
sortThings :: Ord name => (thing -> name) -> (thing -> [name]) -> [thing] -> [[thing]]
sortThings name refers things = map flattenComponent (components name refers things)

-- | Recursive
recursive :: Ord name => (thing -> name) -> (thing -> [name]) -> [thing] -> [name]
recursive name refers things =
  [ name x | Rec xs <- components name refers things, x <- xs ]

-- | Makes a nice flag from a constructor string
--
-- > > flagify "PrintPolyFOL"
-- > "print-poly-fol"
flagify :: String -> String
flagify
    = map toLower . intercalate "-"
    . split (condense $ dropInitBlank $ keepDelimsL $ whenElt (\x -> isUpper x || isSpace x))

-- | Makes a flag from something @Show@-able
flagifyShow :: Show a => a -> String
flagifyShow = flagify . show

-- | Calculates the maximum value of a foldable value.
maximumOn :: forall f a b . (F.Foldable f,Ord b) => (a -> b) -> f a -> b
maximumOn f = f . F.maximumBy (comparing f)

-- | Pair up a list with its previous elements
--
-- > withPrevious "abc" = [('a',""),('b',"a"),('c',"ab")]
withPrevious :: [a] -> [(a,[a])]
withPrevious xs = zip xs (inits xs)

-- | Cursored traversal with previous and next elements of a list
cursor :: [a] -> [([a],a,[a])]
cursor xs = [ let (l,x:r) = splitAt i xs in (l,x,r) | i <- [0..length xs-1] ]