{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UnicodeSyntax #-}

{-| This module contains infrastructure for working with 'Workload's, which
    describe a portion of work to be performed by a worker.
 -}
module LogicGrowsOnTrees.Workload
    (
    -- * Workload type and simple functions
      Workload(..)
    , entire_workload
    , workloadDepth
    -- * Exploration functions
    -- $exploration
    , exploreTreeWithinWorkload
    , exploreTreeTWithinWorkload
    , exploreTreeUntilFirstWithinWorkload
    , exploreTreeTUntilFirstWithinWorkload
    , exploreTreeUntilFoundWithinWorkload
    , exploreTreeTUntilFoundWithinWorkload
    ) where

import Control.Monad ((>=>))
import Data.Derive.Serialize
import Data.DeriveTH
import Data.Function (on)
import Data.Monoid (Monoid(..))
import qualified Data.Sequence as Seq
import Data.Serialize

import LogicGrowsOnTrees
import LogicGrowsOnTrees.Checkpoint
import LogicGrowsOnTrees.Path

--------------------------------------------------------------------------------
-------------------- Workload type and simple functions ------------------------
--------------------------------------------------------------------------------

{-| A 'Workload' describes a portion of work to be performed by a worker;  it
    consists of a 'Path' to the subtree where the workload is located paired
    with a 'Checkpoint' that indicates which parts of that subtree have already
    been explored.
 -}
data Workload = Workload
    {   workloadPath :: Path
    ,   workloadCheckpoint :: Checkpoint
    } deriving (Eq,Show)
$( derive makeSerialize ''Workload )

{-| Workloads are ordered first by their depth (the length of the 'Path'
    component), second by the value of the 'Path' component itself, and finally
    by the value of the 'Checkpoint' component. This ordering was chosen because
    there are times where it is nice to be able to conveniently order
    'Workload's by depth.
 -}
instance Ord Workload where
    x `compare` y =
        case (compare `on` workloadDepth) x y of
            EQ  case (compare `on` workloadPath) x y of
                EQ  (compare `on` workloadCheckpoint) x y
                c  c
            c  c

{-| A 'Workload' that consists of the entire tree. -}
entire_workload :: Workload
entire_workload = Workload Seq.empty Unexplored

{-| The depth of the workload, equal to the length of the 'Path' component. -}
workloadDepth :: Workload  Int
workloadDepth = Seq.length . workloadPath

--------------------------------------------------------------------------------
----------------------------- Exploration functions --------------------------------
--------------------------------------------------------------------------------

{- $exploration
The functions in this section explore the part of a tree that is given by a
'Workload'.
-}

{-| Explores the nodes in a pure tree given by a 'Workload', and sums
    over all the results in the leaves.
 -}
exploreTreeWithinWorkload ::
    Monoid α 
    Workload 
    Tree α 
    α
exploreTreeWithinWorkload Workload{..} =
    exploreTreeStartingFromCheckpoint workloadCheckpoint
    .
    sendTreeDownPath workloadPath

{-| Same as 'exploreTreeWithinWorkload' but for an impure tree. -}
exploreTreeTWithinWorkload ::
    (Monad m, Monoid α) 
    Workload 
    TreeT m α 
    m α
exploreTreeTWithinWorkload Workload{..} =
    sendTreeTDownPath workloadPath
    >=>
    exploreTreeTStartingFromCheckpoint workloadCheckpoint

{-| Explores the nodes in a pure tree given by a 'Workload' until
    a result (i.e. a leaf) has been found; if a result has been found then it is
    returned wrapped in 'Just', otherwise 'Nothing' is returned.
 -}
   
exploreTreeUntilFirstWithinWorkload ::
    Workload 
    Tree α 
    Maybe α
exploreTreeUntilFirstWithinWorkload Workload{..} =
    exploreTreeUntilFirstStartingFromCheckpoint workloadCheckpoint
    .
    sendTreeDownPath workloadPath

{-| Same as 'exploreTreeUntilFirstWithinWorkload' but for an impure tree. -}
exploreTreeTUntilFirstWithinWorkload ::
    Monad m 
    Workload 
    TreeT m α 
    m (Maybe α)
exploreTreeTUntilFirstWithinWorkload Workload{..} =
    sendTreeTDownPath workloadPath
    >=>
    exploreTreeTUntilFirstStartingFromCheckpoint workloadCheckpoint

{-| Explores the nodes in a pure tree given by a 'Workload', summing
    all results encountered (i.e., in the leaves) until the current partial sum
    satisfies the condition provided by the first parameter.

    See 'LogicGrowsOnTrees.exploreTreeUntilFound' for more details.
 -}
exploreTreeUntilFoundWithinWorkload ::
    Monoid α 
    (α  Bool) 
    Workload 
    Tree α 
    (α,Bool)
exploreTreeUntilFoundWithinWorkload condition Workload{..} =
    exploreTreeUntilFoundStartingFromCheckpoint condition workloadCheckpoint
    .
    sendTreeDownPath workloadPath

{-| Same as 'exploreTreeUntilFoundWithinWorkload' but for an impure tree. -}
exploreTreeTUntilFoundWithinWorkload ::
    (Monoid α, Monad m) 
    (α  Bool) 
    Workload 
    TreeT m α 
    m (α,Bool)
exploreTreeTUntilFoundWithinWorkload condition Workload{..} =
    sendTreeTDownPath workloadPath
    >=>
    exploreTreeTUntilFoundStartingFromCheckpoint condition workloadCheckpoint