{-# LANGUAGE TypeFamilies #-}
-----------------------------------------------------------------------------
-- Copyright 2015, Open Universiteit Nederland. This file is distributed
-- under the terms of the GNU General Public License. For more information,
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- A type class for sequences together with the 'Step' datatype.
--
-----------------------------------------------------------------------------
--  $Id: Sequential.hs 6612 2014-06-12 07:57:59Z bastiaan $

module Ideas.Common.Strategy.Sequence
   ( -- * Sequence type class
     Sequence(..)
     -- * Firsts type class
   , Firsts(..), firstsMenu, firstsOrdered, firstsTree, stopped
     -- * MenuItem data type with some utility functions
   , MenuItem(..), menuItem, isDone
   ) where

import Data.Function
import Ideas.Common.Classes
import Ideas.Common.DerivationTree
import Ideas.Common.Strategy.Choice

infixr 5 :~>, ~>, <*>

------------------------------------------------------------------------
-- Sequence type class

class Sequence f where
   -- | The empty sequence.
   done :: f a
   -- | Prefix a sequence with one element.
   (~>) :: a -> f a -> f a
   -- | Append two sequences.
   (<*>) :: f a -> f a -> f a

------------------------------------------------------------------------
-- Firsts type class

class Firsts s where
   -- | The type associated with a step in the first set.
   type Elem s
   -- | The ready predicate (we are done).
   ready :: s -> Bool
   ready = any isDone . bests . menu
   -- | The first set.
   firsts :: s -> [(Elem s, s)]
   firsts = bests . firstsMenu
   -- | The menu offers single steps (with the remainder) and 'done' steps.
   menu :: s -> Menu (MenuItem (Elem s) s)

firstsMenu :: Firsts s => s -> Menu (Elem s, s)
firstsMenu s = do
   item <- cut (menu s)
   case item of
      a :~> t -> return (a, t)
      Done    -> empty

firstsOrdered :: Firsts s => (Elem s -> Elem s -> Ordering)
              -> s -> [(Elem s, s)]
firstsOrdered cmp = bestsOrdered (cmp `on` fst) . firstsMenu

firstsTree :: Firsts s => s -> DerivationTree (Elem s) s
firstsTree x = addBranches bs tr
 where
   tr = singleNode x (ready x)
   bs = [ (a, firstsTree y) | (a, y) <- firsts x ]

-- | Not ready and no further steps to take.
stopped :: Firsts s => s -> Bool
stopped = isEmpty . menu

------------------------------------------------------------------------
-- MenuItem data type with some utility functions

data MenuItem a s = a :~> s   -- ^ A single step.
                  | Done      -- ^ No step (we are done).

instance Functor (MenuItem a) where
   fmap = mapSecond

instance BiFunctor MenuItem where
   biMap f g = menuItem Done (\a s -> f a :~> g s)

-- | The 'menuItem' function takes a default value for 'Done' and a function
-- to combine the values for a single step.
menuItem :: b -> (a -> s -> b) -> MenuItem a s -> b
menuItem b _ Done      = b
menuItem _ f (a :~> x) = f a x

-- | Is the item 'done'?
isDone :: MenuItem a s -> Bool
isDone Done = True
isDone _    = False