{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wall #-}

-- | `commit`
module Box.Committer
  ( Committer (..),
    drain,
    mapC,
    premapC,
    postmapC,
    stateC,
    listC,
  )
where

import Data.Functor.Contravariant
import Data.Functor.Contravariant.Divisible
import NumHask.Prelude

-- | a Committer a "commits" values of type a. A Sink and a Consumer are some other metaphors for this.
--
-- A Committer absorbs the value being committed; the value disappears into the opaque thing that is a Committer from the pov of usage.
newtype Committer m a
  = Committer
      { commit :: a -> m Bool
      }

instance MFunctor Committer where
  hoist nat (Committer c) = Committer $ nat . c

instance (Applicative m) => Semigroup (Committer m a) where
  (<>) i1 i2 = Committer (\a -> (||) <$> commit i1 a <*> commit i2 a)

instance (Applicative m) => Monoid (Committer m a) where
  mempty = Committer (\_ -> pure False)

  mappend = (<>)

instance Contravariant (Committer m) where
  contramap f (Committer a) = Committer (a . f)

instance (Applicative m) => Divisible (Committer m) where
  conquer = Committer (\_ -> pure False)

  divide f i1 i2 =
    Committer $ \a ->
      case f a of
        (b, c) -> (||) <$> commit i1 b <*> commit i2 c

instance (Applicative m) => Decidable (Committer m) where
  lose f = Committer (absurd . f)

  choose f i1 i2 =
    Committer $ \a ->
      case f a of
        Left b -> commit i1 b
        Right c -> commit i2 c

-- | Do nothing with values that are committed.
--
-- This is useful for keeping the commit end of a box or pipeline open.
drain :: (Applicative m) => Committer m a
drain = Committer (\_ -> pure True)

-- | This is a contramapMaybe, if such a thing existed, as the contravariant version of a mapMaybe.  See [witherable](https://hackage.haskell.org/package/witherable)
mapC :: (Monad m) => (b -> m (Maybe a)) -> Committer m a -> Committer m b
mapC f c = Committer go
  where
    go b = do
      fb <- f b
      case fb of
        Nothing -> pure True
        Just fb' -> commit c fb'

-- | adds a monadic action to the committer
premapC ::
  (Applicative m) =>
  (Committer m a -> m ()) ->
  Committer m a ->
  Committer m a
premapC f c = Committer $ \a -> f c *> commit c a

-- | adds a post-commit monadic action to the committer
postmapC ::
  (Monad m) =>
  (Committer m a -> m ()) ->
  Committer m a ->
  Committer m a
postmapC f c = Committer $ \a -> do
  r <- commit c a
  f c
  pure r

-- | commit to a StateT list
stateC :: (Monad m) => Committer (StateT [a] m) a
stateC = Committer $ \a -> do
  modify (a :)
  pure True

-- | list committer
listC :: (Monad m) => Committer m a -> Committer m [a]
listC c = Committer $ \as ->
  any id <$> (sequence $ commit c <$> as)