{-# LANGUAGE CPP                 #-}
{-# LANGUAGE DeriveDataTypeable  #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE InstanceSigs        #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving  #-}
{-# LANGUAGE TypeOperators       #-}

{-# OPTIONS_GHC -Wall #-}

module Wizard
  ( WizardT(..)

    -- * Fantastical use of a Wizard
  , egg
  , tadpole

  , transform
  , essence
  , leviosa
  , summon
  , sageMode

    -- * Boring use of a Wizard
  , empty
  , singleton
  , mapWizard
  , foldWizard
  ) where

import Control.Applicative (liftA2, Alternative((<|>)))
import Control.Monad (MonadPlus(mzero))
import Control.Monad.Fail (MonadFail(fail))
import Control.Monad.Fix (MonadFix(mfix))
import Control.Monad.IO.Class (MonadIO(liftIO))
import Control.Monad.Trans.Class (MonadTrans(lift))
import Data.Coerce (coerce)
import Data.Data (Typeable)
import Data.Foldable (Foldable(foldMap))
import Data.Monoid (Monoid(..))
import Data.Semigroup (Semigroup((<>)))
import Data.Traversable (Traversable(traverse))
import GHC.Generics (Generic, Generic1)

import qualified Control.Applicative as Alternative (empty)

infixr 9 `WizardT`

-- | A 'Wizard' Monoid, based on ideas expressed by Gabriel Gonzalez
-- at http://www.haskellforall.com/2018/02/the-wizard-monoid.html.
--
-- One can view this as 'Data.Functor.Compose.Compose', specialised
-- to a single functor.
newtype WizardT m a = WizardT { wand :: m (m a) }
  deriving (Generic, Generic1, Typeable)

instance (Functor f) => Functor (WizardT f) where
  fmap f = WizardT . fmap (fmap f) . wand
  {-# INLINE fmap #-}

instance (Applicative f) => Applicative (WizardT f) where
  pure = WizardT . pure . pure
  {-# INLINE pure #-}
  WizardT f <*> WizardT x = WizardT (liftA2 (<*>) f x)
  liftA2 f (WizardT x) (WizardT y) =
    WizardT (liftA2 (liftA2 f) x y)
  {-# INLINE (<*>) #-}

instance (Monad m) => Monad (WizardT m) where
  (>>=) = flip summon . essence
  {-# INLINE (>>=) #-}

instance (Applicative f, Semigroup a) => Semigroup (WizardT f a) where
  (<>) :: WizardT f a -> WizardT f a -> WizardT f a
  (<>) = liftA2 (<>)
  {-# INLINE (<>) #-}

instance (Applicative f, Monoid a) => Monoid (WizardT f a) where
  mempty :: WizardT f a
  mempty = pure mempty
  {-# INLINE mempty #-}
#if !(MIN_VERSION_base(4,11,0))
  mappend :: WizardT f a -> WizardT f a -> WizardT f a
  mappend = liftA2 mappend
  {-# INLINE mappend #-}
#endif

instance (Foldable f) => Foldable (WizardT f) where
  foldMap f (WizardT t) = foldMap (foldMap f) t
  {-# INLINE foldMap #-}

instance (Traversable t) => Traversable (WizardT t) where
  traverse f (WizardT t) = WizardT <$> traverse (traverse f) t
  {-# INLINE traverse #-}

instance (Alternative f) => Alternative (WizardT f) where
  empty = WizardT Alternative.empty
  {-# INLINE empty #-}
  (<|>) = coerce ((<|>) :: f (f a) -> f (f a) -> f (f a))
    :: forall a . WizardT f a -> WizardT f a -> WizardT f a
  {-# INLINE (<|>) #-}

instance (Alternative m, Monad m) => MonadPlus (WizardT m) where

instance (Alternative m, Monad m) => MonadFail (WizardT m) where
  fail _ = mzero

instance (MonadFix m) => MonadFix (WizardT m) where
  mfix f = WizardT (pure (mfix (essence . f)))

instance MonadTrans WizardT where
  lift = WizardT . fmap pure
  {-# INLINE lift #-}

instance (MonadIO m) => MonadIO (WizardT m) where
  liftIO = lift . liftIO
  {-# INLINE liftIO #-}

-- | /O(1)/ boring - construct an empty Wizard.
empty :: (Applicative f, Monoid a) => WizardT f a
empty = egg
{-# INLINE empty #-}

-- | /O(1)/ boring - construct a singleton Wizard.
singleton :: (Applicative f) => a -> WizardT f a
singleton = pure
{-# INLINE singleton #-}

-- | /O(1)/ fantastical - construct an empty Wizard.
egg :: (Applicative f, Monoid a) => WizardT f a
egg = pure mempty
{-# INLINE egg #-}

-- | /O(1)/ fantastical - construct a singleton Wizard.
tadpole :: (Applicative f) => a -> WizardT f a
tadpole = pure
{-# INLINE tadpole #-}

-- | Map over a Wizard in a fantastical manner.
transform :: Functor f => (a -> b) -> WizardT f a -> WizardT f b
transform f = WizardT . fmap (fmap f) . wand
{-# INLINE transform #-}

-- | Map over a Wizard in a boring manner.
mapWizard :: Functor f => (a -> b) -> WizardT f a -> WizardT f b
mapWizard f = WizardT . fmap (fmap f) . wand
{-# INLINE mapWizard #-}

-- | Get the input (essence) out of the Wizard.
essence :: (Monad m) => WizardT m a -> m a
essence w = (wand w) >>= id
{-# INLINE essence #-}

-- | Lift an input into a Wizard.
leviosa :: (Monad m) => m a -> WizardT m a
leviosa = WizardT . pure
{-# INLINE leviosa #-}

-- | Summon a Wizard out of a monad using
--   a conjuring spell.
--
--   @ ('>>=') = 'flip' 'summon' '.' 'essence' @
summon :: Monad m => (a -> WizardT m b) -> m a -> WizardT m b
summon f = WizardT . (wand . f =<<)
{-# INLINE summon #-}

-- | Run an action over a collection of inputs
--   fantastically.
--
--   This is like 'Beast Mode', but specialised
--   to 'Wizard's.
sageMode
  :: forall m t a b. (Monad m, Foldable t, Monoid a, Monoid b)
  => (a -> WizardT m b)
  -> t a
  -> m b
sageMode f t = essence (foldMap f t)
{-# INLINE sageMode #-}

-- | Run an action over a collection of inputs.
foldWizard
  :: forall m t a b. (Monad m, Foldable t, Monoid a, Monoid b)
  => (a -> WizardT m b)
  -> t a
  -> m b
foldWizard f t = essence (foldMap f t)
{-# INLINE foldWizard #-}