    {-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

    module Propellor.Types.ResultCheck (
    	UncheckedProperty,
    	unchecked,
    	checkResult,
    	check,
    	Checkable,
    	assume,
    ) where

    import Propellor.Types
    import Propellor.Exception
    import Utility.Monad

    import Data.Monoid

    -- | This is a `Property` but its `Result` is not accurate; in particular
    -- it may return `NoChange` despite having made a change.
    --
    -- However, when it returns `MadeChange`, it really did make a change,
    -- and `FailedChange` is still an error.
    data UncheckedProperty i = UncheckedProperty (Property i)

    instance TightenTargets UncheckedProperty where
    	tightenTargets (UncheckedProperty p) = UncheckedProperty (tightenTargets p)

    -- | Use to indicate that a Property is unchecked.
    unchecked :: Property i -> UncheckedProperty i
    unchecked = UncheckedProperty

    -- | Checks the result of a property. Mostly used to convert a
    -- `UncheckedProperty` to a `Property`, but can also be used to further
    -- check a `Property`.
    checkResult
    	:: (Checkable p i, LiftPropellor m)
    	=> m a
    	-- ^ Run before ensuring the property.
    	-> (a -> m Result)
    	-- ^ Run after ensuring the property. Return `MadeChange` if a
    	-- change was detected, or `NoChange` if no change was detected.
    	-> p i
    	-> Property i
    checkResult precheck postcheck p = adjustPropertySatisfy (checkedProp p) $ \satisfy -> do
    	a <- liftPropellor precheck
    	r <- catchPropellor satisfy
    	-- Always run postcheck, even if the result is already MadeChange,
    	-- as it may need to clean up after precheck.
    	r' <- liftPropellor $ postcheck a
    	return (r <> r')

    -- | Makes a `Property` or an `UncheckedProperty` only run
    -- when a test succeeds.
    check :: (Checkable p i, LiftPropellor m) => m Bool -> p i -> Property i
    check test p = adjustPropertySatisfy (preCheckedProp p) $ \satisfy ->
            ifM (liftPropellor test)
                    ( satisfy
                    , return NoChange
                    )

    class Checkable p i where
    	checkedProp :: p i -> Property i
    	preCheckedProp :: p i -> Property i

    instance Checkable Property i where
    	checkedProp = id
    	preCheckedProp = id

    instance Checkable UncheckedProperty i where
    	checkedProp (UncheckedProperty p) = p
    	-- Since it was pre-checked that the property needed to be run,
    	-- if the property succeeded, we can assume it made a change.
    	preCheckedProp (UncheckedProperty p) = p `assume` MadeChange

    -- | Sometimes it's not practical to test if a property made a change.
    -- In such a case, it's often fine to say:
    --
    -- > someprop `assume` MadeChange
    --
    -- However, beware assuming `NoChange`, as that will make combinators
    -- like `onChange` not work.
    assume :: Checkable p i => p i -> Result -> Property i
    assume p result = adjustPropertySatisfy (checkedProp p) $ \satisfy -> do
    	r <- satisfy
    	return (r <> result)