-- |
-- Module      : Control.Applicative.ListF
-- Copyright   : (c) Justin Le 2019
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- This module provides functor combinators that are wrappers over lists or
-- maybes of @f a@s, especially for their
-- 'Data.HFunctor.Interpret.Interpret' instances.
--
-- Each one transforms a functor into some product of itself.  For example,
-- @'NonEmptyF' f@ represents @f ':*:' f@, or @f :*: f :*: f@, or @f :*:
-- f :*: f :*: f@, etc.
module Control.Applicative.ListF (
  -- * 'ListF'
    ListF(..), mapListF
  -- * 'NonEmptyF'
  , NonEmptyF(.., ProdNonEmpty, nonEmptyProd), mapNonEmptyF
  , toListF, fromListF
  -- * 'MaybeF'
  , MaybeF(..), mapMaybeF
  , listToMaybeF, maybeToListF
  -- * 'MapF'
  , MapF(..)
  , NEMapF(..)
  ) where

import           Control.Applicative
import           Control.Natural
import           Data.Coerce
import           Data.Data
import           Data.Deriving
import           Data.Foldable
import           Data.Functor.Bind
import           Data.Functor.Classes
import           Data.Functor.Plus
import           Data.List.NonEmpty         (NonEmpty(..))
import           Data.Maybe
import           Data.Pointed
import           Data.Semigroup.Foldable
import           Data.Semigroup.Traversable
import           GHC.Generics
import qualified Data.Map                   as M
import qualified Data.Map.NonEmpty          as NEM

-- | A list of @f a@s.  Can be used to describe a product of many different
-- values of type @f a@.
--
-- This is the Free 'Plus'.
newtype ListF f a = ListF { runListF :: [f a] }
  deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Typeable, Generic, Data)

deriveShow1 ''ListF
deriveRead1 ''ListF
deriveEq1 ''ListF
deriveOrd1 ''ListF

instance Apply f => Apply (ListF f) where
    ListF fs <.> ListF xs = ListF $ liftF2 (<.>) fs xs
instance Applicative f => Applicative (ListF f) where
    pure  = ListF . (:[]) . pure
    ListF fs <*> ListF xs = ListF $ liftA2 (<*>) fs xs

instance Functor f => Alt (ListF f) where
    (<!>) = (<>)

instance Functor f => Plus (ListF f) where
    zero = mempty

instance Applicative f => Alternative (ListF f) where
    empty = zero
    (<|>) = (<!>)

instance Semigroup (ListF f a) where
    ListF xs <> ListF ys = ListF (xs ++ ys)

instance Monoid (ListF f a) where
    mempty = ListF []

instance Pointed f => Pointed (ListF f) where
    point = ListF . (: []) . point

-- | Map a function over the inside of a 'ListF'.
mapListF
    :: ([f a] -> [g b])
    -> ListF f a
    -> ListF g b
mapListF = coerce

-- | A non-empty list of @f a@s.  Can be used to describe a product between
-- many different possible values of type @f a@.
--
-- Essentially:
--
-- @
-- 'NonEmptyF' f
--     ~ f                          -- one f
--   ':+:' (f ':*:' f)              -- two f's
--   :+: (f :*: f :*: f)            -- three f's
--   :+: (f :*: f :*: f :*: f)      -- four f's
--   :+: ...                        -- etc.
-- @
--
-- This is the Free 'Plus'.
newtype NonEmptyF f a = NonEmptyF { runNonEmptyF :: NonEmpty (f a) }
  deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Typeable, Generic, Data)

deriveShow1 ''NonEmptyF
deriveRead1 ''NonEmptyF
deriveEq1 ''NonEmptyF
deriveOrd1 ''NonEmptyF

instance Applicative f => Applicative (NonEmptyF f) where
    pure  = NonEmptyF . (:| []) . pure
    NonEmptyF fs <*> NonEmptyF xs = NonEmptyF $ liftA2 (<*>) fs xs

instance Functor f => Alt (NonEmptyF f) where
    (<!>) = (<>)

instance Semigroup (NonEmptyF f a) where
    NonEmptyF xs <> NonEmptyF ys = NonEmptyF (xs <> ys)

instance Pointed f => Pointed (NonEmptyF f) where
    point = NonEmptyF . (:| []) . point

-- | Map a function over the inside of a 'NonEmptyF'.
mapNonEmptyF
    :: (NonEmpty (f a) -> NonEmpty (g b))
    -> NonEmptyF f a
    -> NonEmptyF g b
mapNonEmptyF = coerce

-- | Convert a 'NonEmptyF' into a 'ListF' with at least one item.
toListF :: NonEmptyF f ~> ListF f
toListF (NonEmptyF xs) = ListF (toList xs)

-- | Convert a 'ListF' either a 'NonEmptyF', or a 'Proxy' in the case that
-- the list was empty.
fromListF :: ListF f ~> (Proxy :+: NonEmptyF f)
fromListF (ListF xs) = case xs of
    []   -> L1 Proxy
    y:ys -> R1 $ NonEmptyF (y :| ys)

-- | Treat a @'NonEmptyF' f@ as a product between an @f@ and a @'ListF' f@.
--
-- 'nonEmptyProd' is the record accessor.
pattern ProdNonEmpty :: (f :*: ListF f) a -> NonEmptyF f a
pattern ProdNonEmpty { nonEmptyProd
                     }
            <- ((\case NonEmptyF (x :| xs) -> x :*: ListF xs) -> nonEmptyProd)
  where
    ProdNonEmpty (x :*: ListF xs) = NonEmptyF (x :| xs)
{-# COMPLETE ProdNonEmpty #-}

-- | A maybe @f a@.
--
-- Can be useful for describing a "an @f a@ that may or may not be there".
--
-- This is the free structure for a "fail"-like typeclass that would only
-- have @zero :: f a@.
newtype MaybeF f a = MaybeF { runMaybeF :: Maybe (f a) }
  deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Typeable, Generic, Data)

deriveShow1 ''MaybeF
deriveRead1 ''MaybeF
deriveEq1 ''MaybeF
deriveOrd1 ''MaybeF

instance Applicative f => Applicative (MaybeF f) where
    pure = MaybeF . Just . pure
    MaybeF f <*> MaybeF x = MaybeF $ liftA2 (<*>) f x

instance Functor f => Alt (MaybeF f) where
    (<!>) = (<>)

instance Functor f => Plus (MaybeF f) where
    zero = mempty

instance Applicative f => Alternative (MaybeF f) where
    empty = zero
    (<|>) = (<!>)

-- | Picks the first 'Just'.
instance Semigroup (MaybeF f a) where
    MaybeF xs <> MaybeF ys = MaybeF (xs <!> ys)

instance Monoid (MaybeF f a) where
    mempty = MaybeF Nothing

instance Pointed f => Pointed (MaybeF f) where
    point = MaybeF . Just . point

-- | Map a function over the inside of a 'MaybeF'.
mapMaybeF
    :: (Maybe (f a) -> Maybe (g b))
    -> MaybeF f a
    -> MaybeF g b
mapMaybeF = coerce

-- | Convert a 'MaybeF' into a 'ListF' with zero or one items.
maybeToListF :: MaybeF f ~> ListF f
maybeToListF (MaybeF x) = ListF (maybeToList x)

-- | Convert a 'ListF' into a 'MaybeF' containing the first @f a@ in the
-- list, if it exists.
listToMaybeF :: ListF f ~> MaybeF f
listToMaybeF (ListF xs) = MaybeF (listToMaybe xs)

-- | A map of @f a@s, indexed by keys of type @k@.  It can be useful for
-- represeting a product of many different values of type @f a@, each "at"
-- a different @k@ location.
--
-- Can be considered a combination of 'Control.Comonad.Trans.Env.EnvT' and
-- 'ListF', in a way --- a @'MapF' k f a@ is like a @'ListF'
-- ('Control.Comonad.Trans.Env.EnvT' k f) a@ with unique (and ordered)
-- keys.
--
-- One use case might be to extend a schema with many "options", indexed by
-- some string.
--
-- For example, if you had a command line argument parser for a single
-- command
--
-- @
-- data Command a
-- @
--
-- Then you can represent a command line argument parser for /multiple/
-- named commands with
--
-- @
-- type Commands = 'MapF' 'String' Command
-- @
--
-- See 'NEMapF' for a non-empty variant, if you want to enforce that your
-- bag has at least one @f a@.
newtype MapF k f a = MapF { runMapF :: M.Map k (f a) }
  deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Typeable, Generic, Data)

deriveShow1 ''MapF
deriveEq1 ''MapF
deriveOrd1 ''MapF

instance (Ord k, Read k, Read1 f) => Read1 (MapF k f) where
    liftReadsPrec = $(makeLiftReadsPrec ''MapF)

-- | A union, combining matching keys with '<!>'.
instance (Ord k, Alt f) => Semigroup (MapF k f a) where
    MapF xs <> MapF ys = MapF $ M.unionWith (<!>) xs ys

instance (Ord k, Alt f) => Monoid (MapF k f a) where
    mempty = MapF M.empty

-- | Left-biased union
instance (Functor f, Ord k) => Alt (MapF k f) where
    MapF xs <!> MapF ys = MapF $ M.union xs ys

instance (Functor f, Ord k) => Plus (MapF k f) where
    zero = MapF M.empty

instance (Monoid k, Pointed f) => Pointed (MapF k f) where
    point = MapF . M.singleton mempty . point

-- | A non-empty map of @f a@s, indexed by keys of type @k@.  It can be
-- useful for represeting a product of many different values of type @f a@,
-- each "at" a different @k@ location, where you need to have at least one
-- @f a@ at all times.
--
-- Can be considered a combination of 'Control.Comonad.Trans.Env.EnvT' and
-- 'NonEmptyF', in a way --- an @'NEMapF' k f a@ is like a @'NonEmptyF'
-- ('Control.Comonad.Trans.Env.EnvT' k f) a@ with unique (and ordered)
-- keys.
--
-- See 'MapF' for some use cases.
newtype NEMapF k f a = NEMapF { runNEMapF :: NEM.NEMap k (f a) }
  deriving (Show, Read, Eq, Ord, Functor, Foldable, Traversable, Typeable, Generic, Data)

deriveShow1 ''NEMapF
deriveEq1 ''NEMapF
deriveOrd1 ''NEMapF

instance (Ord k, Read k, Read1 f) => Read1 (NEMapF k f) where
    liftReadsPrec = $(makeLiftReadsPrec ''NEMapF)

instance Foldable1 f => Foldable1 (NEMapF k f) where
    fold1      = foldMap1 fold1 . runNEMapF
    foldMap1 f = (foldMap1 . foldMap1) f . runNEMapF
    toNonEmpty = foldMap1 toNonEmpty . runNEMapF

instance Traversable1 f => Traversable1 (NEMapF k f) where
    traverse1 f = fmap NEMapF . (traverse1 . traverse1) f . runNEMapF
    sequence1   = fmap NEMapF . traverse1 sequence1 . runNEMapF

-- | A union, combining matching keys with '<!>'.
instance (Ord k, Alt f) => Semigroup (NEMapF k f a) where
    NEMapF xs <> NEMapF ys = NEMapF $ NEM.unionWith (<!>) xs ys

-- | Left-biased union
instance (Functor f, Ord k) => Alt (NEMapF k f) where
    NEMapF xs <!> NEMapF ys = NEMapF $ NEM.union xs ys

instance (Monoid k, Pointed f) => Pointed (NEMapF k f) where
    point = NEMapF . NEM.singleton mempty . point