{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}

-- | Inject values into accumulators.
module Dep.Injects
  ( -- * General-purpose injector.
    Injects (..),
    InjectsAll
  )
where

import Data.Coerce
import Data.Kind
import GHC.Records
import GHC.TypeLits

-- | Mirror image of 'Dep.Has.Has'.
--
-- Can be useful with 'Dep.Constructor.AccumConstructor' to register particular
-- monoidal values into a \"wider"\ monoidal value which is accumulated accross
-- all components.
type Injects :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Constraint
class Injects r_ (m :: Type -> Type) (accum :: Type) | accum -> m where
  -- | Given a value parameterized by the accumulator's effect monad @m@,
  -- produce an accumulator.
  inject :: r_ m -> accum

instance (Monoid b) => Injects r_ m (r_ m, b) where
  inject :: r_ m -> (r_ m, b)
inject r_ m
r = (r_ m
r, forall a. Monoid a => a
mempty)

instance (Monoid a) => Injects r_ m (a, r_ m) where
  inject :: r_ m -> (a, r_ m)
inject r_ m
r = (forall a. Monoid a => a
mempty, r_ m
r)

instance (Monoid b, Monoid c) => Injects r_ m (r_ m, b, c) where
  inject :: r_ m -> (r_ m, b, c)
inject r_ m
r = (r_ m
r, forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty)

instance (Monoid a, Monoid c) => Injects r_ m (a, r_ m, c) where
  inject :: r_ m -> (a, r_ m, c)
inject r_ m
r = (forall a. Monoid a => a
mempty, r_ m
r, forall a. Monoid a => a
mempty)

instance (Monoid a, Monoid b) => Injects r_ m (a, b, r_ m) where
  inject :: r_ m -> (a, b, r_ m)
inject r_ m
r = (forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty, r_ m
r)

instance (Monoid b, Monoid c, Monoid d) => Injects r_ m (r_ m, b, c, d) where
  inject :: r_ m -> (r_ m, b, c, d)
inject r_ m
r = (r_ m
r, forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty)

instance (Monoid a, Monoid c, Monoid d) => Injects r_ m (a, r_ m, c, d) where
  inject :: r_ m -> (a, r_ m, c, d)
inject r_ m
r = (forall a. Monoid a => a
mempty, r_ m
r, forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty)

instance (Monoid a, Monoid b, Monoid d) => Injects r_ m (a, b, r_ m, d) where
  inject :: r_ m -> (a, b, r_ m, d)
inject r_ m
r = (forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty, r_ m
r, forall a. Monoid a => a
mempty)

instance (Monoid a, Monoid b, Monoid c) => Injects r_ m (a, b, c, r_ m) where
  inject :: r_ m -> (a, b, c, r_ m)
inject r_ m
r = (forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty, forall a. Monoid a => a
mempty, r_ m
r)


-- | Mirror image of 'Dep.Has.HasAll'.
type InjectsAll :: [(Type -> Type) -> Type] -> (Type -> Type) -> Type -> Constraint
type family InjectsAll rs_ m e where
  InjectsAll '[] m e = ()
  InjectsAll (r_ : rs_) m e = (Injects r_ m e, InjectsAll rs_ m e)