{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Propellor.Property.List (
	props,
	Props,
	toProps,
	propertyList,
	combineProperties,
) where

import Propellor.Types
import Propellor.Types.Core
import Propellor.Types.MetaTypes
import Propellor.PropAccum
import Propellor.Engine
import Propellor.Exception

import Data.Monoid
import Prelude

toProps :: [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
toProps :: [Property (MetaTypes metatypes)] -> Props (MetaTypes metatypes)
toProps [Property (MetaTypes metatypes)]
ps = [ChildProperty] -> Props (MetaTypes metatypes)
forall metatypes. [ChildProperty] -> Props metatypes
Props ((Property (MetaTypes metatypes) -> ChildProperty)
-> [Property (MetaTypes metatypes)] -> [ChildProperty]
forall a b. (a -> b) -> [a] -> [b]
map Property (MetaTypes metatypes) -> ChildProperty
forall p. IsProp p => p -> ChildProperty
toChildProperty [Property (MetaTypes metatypes)]
ps)

-- | Combines a list of properties, resulting in a single property
-- that when run will run each property in the list in turn,
-- and print out the description of each as it's run. Does not stop
-- on failure; does propagate overall success/failure.
--
-- For example:
--
-- > propertyList "foo" $ props
-- > 	& bar
-- > 	& baz
propertyList :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
propertyList :: Desc
-> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
propertyList Desc
desc (Props [ChildProperty]
ps) = 
	Desc -> Propellor Result -> Property (MetaTypes metatypes)
forall k (metatypes :: k).
SingI metatypes =>
Desc -> Propellor Result -> Property (MetaTypes metatypes)
property Desc
desc ([ChildProperty] -> Propellor Result
ensureChildProperties [ChildProperty]
cs)
		Property (MetaTypes metatypes)
-> [ChildProperty] -> Property (MetaTypes metatypes)
forall p. IsProp p => p -> [ChildProperty] -> p
`addChildren` [ChildProperty]
cs
  where
	cs :: [ChildProperty]
cs = (ChildProperty -> ChildProperty)
-> [ChildProperty] -> [ChildProperty]
forall a b. (a -> b) -> [a] -> [b]
map ChildProperty -> ChildProperty
forall p. IsProp p => p -> ChildProperty
toChildProperty [ChildProperty]
ps

-- | Combines a list of properties, resulting in one property that
-- ensures each in turn. Stops if a property fails.
--
-- > combineProperties "foo" $ props
-- > 	& bar
-- > 	& baz
--
-- This is similar to using `mconcat` with a list of properties,
-- except it can combine together different types of properties.
combineProperties :: SingI metatypes => Desc -> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
combineProperties :: Desc
-> Props (MetaTypes metatypes) -> Property (MetaTypes metatypes)
combineProperties Desc
desc (Props [ChildProperty]
ps) = 
	Desc -> Propellor Result -> Property (MetaTypes metatypes)
forall k (metatypes :: k).
SingI metatypes =>
Desc -> Propellor Result -> Property (MetaTypes metatypes)
property Desc
desc ([ChildProperty] -> Result -> Propellor Result
combineSatisfy [ChildProperty]
cs Result
NoChange)
		Property (MetaTypes metatypes)
-> [ChildProperty] -> Property (MetaTypes metatypes)
forall p. IsProp p => p -> [ChildProperty] -> p
`addChildren` [ChildProperty]
cs
  where
	cs :: [ChildProperty]
cs = (ChildProperty -> ChildProperty)
-> [ChildProperty] -> [ChildProperty]
forall a b. (a -> b) -> [a] -> [b]
map ChildProperty -> ChildProperty
forall p. IsProp p => p -> ChildProperty
toChildProperty [ChildProperty]
ps

combineSatisfy :: [ChildProperty] -> Result -> Propellor Result
combineSatisfy :: [ChildProperty] -> Result -> Propellor Result
combineSatisfy [] Result
rs = Result -> Propellor Result
forall (m :: * -> *) a. Monad m => a -> m a
return Result
rs
combineSatisfy (ChildProperty
p:[ChildProperty]
ps) Result
rs = do
	Result
r <- Propellor Result
-> (Propellor Result -> Propellor Result)
-> Maybe (Propellor Result)
-> Propellor Result
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Result -> Propellor Result
forall (m :: * -> *) a. Monad m => a -> m a
return Result
NoChange) Propellor Result -> Propellor Result
forall (m :: * -> *).
(MonadIO m, MonadCatch m) =>
m Result -> m Result
catchPropellor (ChildProperty -> Maybe (Propellor Result)
forall p. IsProp p => p -> Maybe (Propellor Result)
getSatisfy ChildProperty
p)
	case Result
r of
		Result
FailedChange -> Result -> Propellor Result
forall (m :: * -> *) a. Monad m => a -> m a
return Result
FailedChange
		Result
_ -> [ChildProperty] -> Result -> Propellor Result
combineSatisfy [ChildProperty]
ps (Result
r Result -> Result -> Result
forall a. Semigroup a => a -> a -> a
<> Result
rs)