module Set.Utils where

import Data.List     (tails)
import System.Random (newStdGen, RandomGen, randomR)

-------------------------------------------------------------------------------
-- List utilities--------------------------------------------------------------
-------------------------------------------------------------------------------

-- | 'groups' breaks a list into sublists of the given size. The final resulting
--   group may contain fewer elements than the given size.
--   Property: For all positive n. concat (groups n xs) == xs
groups :: Int -> [a] -> [[a]]
groups _ [] = []
groups n xs = as : groups n bs
  where
  (as,bs) = splitAt n xs

-- | 'delete1' returns a list with the first occurrence of @x removed. If there
-- is no occurrence 'Nothing' is returned.
delete1 :: Eq a => a -> [a] -> Maybe [a]
delete1 x (y:ys)
  | x == y = Just ys
  | otherwise = fmap (y:) (delete1 x ys)
delete1 _ [] = Nothing

-- | 'index' returns the element at the given 0-based index and returns
-- 'Nothing' on failure.
index :: Int -> [a] -> Maybe a
index 0 (x:_)           = Just x
index n (_:xs) | n > 0  = index (n-1) xs
index _ _               = Nothing

-- | Extract an element from a list by index returning that element
-- and the remaining list.
select :: Int -> [a] -> (a,[a])
select _ [] = error "select: index too large"
select 0 (x:xs) = (x,xs)
select n (x:xs) = (y,x:ys)
  where
  (y,ys) = select (n-1) xs

-- | Drop last element of list if there is an element to drop.
init' :: [a] -> [a]
init' [] = []
init' xs = init xs

-------------------------------------------------------------------------------
-- Other utilities ------------------------------------------------------------
-------------------------------------------------------------------------------

-- | 'chooseTwo' returns all combinations of two elements.
chooseTwo :: [a] -> [(a,a)]
chooseTwo xs = [ (a,b) | (a:as) <- tails xs
                       , b      <- as
                       ]

-- | 'seconds' converts seconds to microseconds for use in 'threadDelay'.
seconds :: Int -> Int
seconds x = 1000000 * x

-------------------------------------------------------------------------------
-- List shuffling utilities ---------------------------------------------------
-------------------------------------------------------------------------------

-- | 'shuffleIO' calls shuffle using a generator from 'newStdGen'.
shuffleIO :: [a] -> IO [a]
shuffleIO xs = fmap (fst . shuffle xs) newStdGen

-- | 'shuffle' shuffles the elements of a list using the given random generator.
shuffle :: RandomGen g => [a] -> g -> ([a], g)
shuffle xs = shuffle' (length xs) xs

shuffle' :: RandomGen g =>  Int -> [a] -> g -> ([a], g)
shuffle' _ [] g = ([], g)
shuffle' n xs g = (x:xs'', g'')
  where
  n'            = n - 1
  (i, g')       = randomR (0,n') g
  (x, xs')      = select i xs
  (xs'', g'')   = shuffle' n' xs' g'

-------------------------------------------------------------------------------
-- Text manipulation utilities ------------------------------------------------
-------------------------------------------------------------------------------

-- | 'centerText' centers the given string in a field of @width characters.
centerText :: Int -> String -> String
centerText width xs = replicate ((width - length xs) `div` 2 ) ' ' ++ xs
                   ++ replicate ((width - length xs + 1) `div` 2 ) ' '
-- | 'centerText' right-aligns the given string in a field of @width characters.
leftPadText :: Int -> String -> String
leftPadText width xs = replicate (width - length xs) ' ' ++ xs

-- | 'centerText' left-aligns the given string in a field of @width characters.
rightPadText :: Int -> String -> String
rightPadText width xs = xs ++ replicate (width - length xs) ' '