content: {-# LANGUAGE RankNTypes #-}
  
  -- |
  -- Module    : Aura.Utils
  -- Copyright : (c) Colin Woodbury, 2012 - 2024
  -- License   : GPL3
  -- Maintainer: Colin Woodbury <colin@fosskers.ca>
  --
  -- Utility functions specific to Aura.
  
  module Aura.Utils
    ( -- * Strings
      Pattern(..)
    , searchLines
      -- * Network
    , urlContents
      -- * Semigroupoids
    , foldMap1
    , fold1
      -- * Errors
    , hush
    , note
      -- * Compactable
    , fmapEither
    , traverseEither
      -- * These
    , These(..)
    , these
      -- * Directory
    , edit
      -- * Lens
    , Traversal'
      -- * Misc.
    , maybe'
    , groupsOf
    , nes
    , partNonEmpty
    ) where
  
  import           Network.HTTP.Client
  import           Network.HTTP.Types.Status (statusCode)
  import           RIO
  import qualified RIO.ByteString.Lazy as BL
  import qualified RIO.List as L
  import qualified RIO.NonEmpty as NEL
  import qualified RIO.Set as S
  import qualified RIO.Text as T
  import           System.Process.Typed (proc, runProcess)
  
  ---
  
  ---------
  -- STRING
  ---------
  -- | For regex-like find-and-replace in some `Text`.
  data Pattern = Pattern { _pattern :: !Text, _target :: !Text }
  
  -- | Find lines which contain some given `Text`.
  searchLines :: Text -> [Text] -> [Text]
  searchLines pat = filter (T.isInfixOf pat)
  
  ----------
  -- NETWORK
  ----------
  -- | Assumes the given URL is correctly formatted.
  urlContents :: Manager -> String -> IO (Maybe ByteString)
  urlContents m url = f <$> httpLbs (parseRequest_ url) m
    where
      f :: Response BL.ByteString -> Maybe ByteString
      f res | statusCode (responseStatus res) == 200 = Just . BL.toStrict $ responseBody res
            | otherwise = Nothing
  
  --------------
  -- DIRECTORIES
  --------------
  -- | Edit some file in-place with the user's specified editor.
  edit :: FilePath -> FilePath -> IO ()
  edit editor p = void . runProcess $ proc editor [p]
  
  -------
  -- MISC
  -------
  -- | `maybe` with the function at the end.
  maybe' :: b -> Maybe a -> (a -> b) -> b
  maybe' zero m f = maybe zero f m
  
  -- | Borrowed from Compactable.
  fmapEither :: (a -> Either b c) -> [a] -> ([b], [c])
  fmapEither f = foldl' (deal f) ([],[])
    where
      deal :: (a -> Either b c) -> ([b], [c]) -> a -> ([b], [c])
      deal g ~(bs, cs) a = case g a of
        Left b  -> (b:bs, cs)
        Right c -> (bs, c:cs)
  
  -- | Borrowed from Compactable.
  traverseEither :: Applicative f => (a -> f (Either b c)) -> [a] -> f ([b], [c])
  traverseEither f = fmap partitionEithers . traverse f
  
  -- | Break a list into groups of @n@ elements. The last item in the result is
  -- not guaranteed to have the same length as the others.
  groupsOf :: Int -> [a] -> [[a]]
  groupsOf n as
    | n <= 0 = []
    | otherwise = go as
    where
      go [] = []
      go bs = xs : go rest
        where
          (xs, rest) = L.splitAt n bs
  
  nes :: Set a -> Maybe (NonEmpty a)
  nes = NEL.nonEmpty . S.toList
  
  hush :: Either a b -> Maybe b
  hush = either (const Nothing) Just
  
  note :: a -> Maybe b -> Either a b
  note a = maybe (Left a) Right
  
  -- | Borrowed from semigroupoids.
  foldMap1 :: Semigroup m => (a -> m) -> NonEmpty a -> m
  foldMap1 f (a :| [])     = f a
  foldMap1 f (a :| b : bs) = f a <> foldMap1 f (b :| bs)
  
  -- | Borrowed from semigroupoids.
  fold1 :: Semigroup m => NonEmpty m -> m
  fold1 = foldMap1 id
  
  -- | Partition a `NonEmpty` based on some function.
  partNonEmpty :: (a -> These b c) -> NonEmpty a -> These (NonEmpty b) (NonEmpty c)
  partNonEmpty f = foldMap1 (bimap pure pure . f)
  
  --------------------------------------------------------------------------------
  -- Lens
  
  -- | Simple Traversals compatible with both lens and microlens.
  type Traversal' s a = forall f. Applicative f => (a -> f a) -> s -> f s
  
  --------------------------------------------------------------------------------
  -- These
  
  data These a b = This a | That b | These a b
  
  instance (Semigroup a, Semigroup b) => Semigroup (These a b) where
    This x <> This y       = This (x <> y)
    This x <> These z y    = These (x <> z) y
    This x <> That y       = These x y
  
    That x <> That y       = That (x <> y)
    That x <> This y       = These y x
    That x <> These y z    = These y (x <> z)
  
    These w x <> This y    = These (w <> y) x
    These w x <> That y    = These w (x <> y)
    These w x <> These y z = These (w <> y) (x <> z)
  
  instance Functor (These a) where
    fmap _ (This x)    = This x
    fmap f (That y)    = That (f y)
    fmap f (These x y) = These x (f y)
  
  instance Bifunctor These where
    bimap f _ (This x)    = This (f x)
    bimap _ g (That y)    = That (g y)
    bimap f g (These x y) = These (f x) (g y)
  
  these :: (a -> t) -> (b -> t) -> (a -> b -> t) -> These a b -> t
  these f _ _ (This a)    = f a
  these _ g _ (That b)    = g b
  these _ _ h (These a b) = h a b
  