{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE Safe              #-}

module List
    ( ordNub
    , sortOn
    , uncons
    , snoc
    , windows
    , chunksOf
    ) where

import           Data.Bool     (otherwise)
import           Data.Function ((.))
import           Data.Int      (Int)
import           Data.List     (drop, length, sortBy, take, (++))
import           Data.Maybe    (Maybe (..))
import           Data.Ord      (Ord (..), comparing)
import qualified Data.Set      as Set

sortOn :: (Ord o) => (a -> o) -> [a] -> [a]
sortOn = sortBy . comparing

-- | Fast @nub@ if the contents of the list are orderable
--
-- O(n * log n)
ordNub :: (Ord a) => [a] -> [a]
ordNub l = go Set.empty l
  where
    go _ []     = []
    go s (x:xs) =
      if x `Set.member` s
      then go s xs
      else x : go (Set.insert x s) xs

-- | Deconstruct a list into its first element the rest of the list
-- This will evaluate to @Nothing@ if the list is empty.
uncons :: [a] -> Maybe (a, [a])
uncons []     = Nothing
uncons (x:xs) = Just (x, xs)

-- | @(:)@, appending instead of prepending
-- @(:)@ is sometimes called cons, so when we append instead of prepend, we call the function @snoc@
-- Note that this has an O(length list) time complexity
snoc :: [a] -> a -> [a]
snoc as a = as ++ [a]

-- | Find all @i@-sized windows in a list
windows :: Int -> [a] -> [[a]]
windows _ [] = []
windows i xss@(_:xs)
    | length xss >= i = (take i xss) : windows i xs
    | otherwise       = []

-- | Takes chunks of a given size, the last chunk may be smaller but not empty.
chunksOf :: Int -> [a] -> [[a]]
chunksOf _ [] = []
chunksOf i xs
    | length xs >= i = take i xs : chunksOf i (drop i xs)
    | otherwise = [xs]