{-# LANGUAGE RankNTypes #-}

-- |
-- Module    : Aura.Utils
-- Copyright : (c) Colin Woodbury, 2012 - 2020
-- 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 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
