{-# LANGUAGE TypeFamilies #-}
-----------------------------------------------------------------------------

-- Copyright 2019, Ideas project team. This file is distributed under the

-- terms of the Apache License 2.0. For more information, see the files

-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.

-----------------------------------------------------------------------------

-- |

-- Maintainer  :  bastiaan.heeren@ou.nl

-- Stability   :  provisional

-- Portability :  portable (depends on ghc)

--

-- Basic machinery for fully executing a strategy expression, or only

-- partially. Partial execution results in a prefix that keeps the current

-- locations in the strategy (a list of @Path@s) for continuing the execution

-- later on. A path can be used to reconstruct the sequence of steps already

-- performed (a so-called trace). Prefixes can be merged with the Monoid

-- operation.

--

-----------------------------------------------------------------------------


module Ideas.Common.Strategy.Prefix
   ( -- * Prefix

     Prefix, noPrefix, makePrefix, firstsOrdered
   , replayProcess
   , isEmptyPrefix, majorPrefix, searchModePrefix, prefixPaths
     -- * Path

   , Path, emptyPath, readPath, readPaths
   ) where

import Data.Char
import Data.List (intercalate)
import Data.Maybe
import Data.Semigroup as Sem
import Ideas.Common.Classes
import Ideas.Common.Environment
import Ideas.Common.Rewriting.Term
import Ideas.Common.Rule
import Ideas.Common.Strategy.Choice
import Ideas.Common.Strategy.Process
import Ideas.Common.Strategy.Sequence
import Ideas.Common.Strategy.StrategyTree
import Ideas.Utils.Prelude (splitsWithElem, readM)

--------------------------------------------------------------------------------

-- Prefix datatype


data Prefix a = Prefix
   { getPaths  :: [Path]
   , remainder :: Menu (Rule a) (a, Environment, Prefix a)
   }

instance Show (Prefix a) where
   show = intercalate ";" . map show . prefixPaths

instance Sem.Semigroup (Prefix a) where
   (Prefix xs p) <> (Prefix ys q) = Prefix (xs ++ ys) (p .|. q)

instance Monoid (Prefix a) where
   mempty  = noPrefix
   mappend = (<>)

instance Firsts (Prefix a) where
   type Elem (Prefix a) = (Rule a, a, Environment)

   ready  = hasDone . remainder
   firsts = map reorder . bests . remainder

firstsOrdered :: (Rule a -> Rule a -> Ordering) -> Prefix a -> [((Rule a, a, Environment), Prefix a)]
firstsOrdered cmp = map reorder . bestsOrdered cmp . remainder

reorder :: (a, (b, env, c)) -> ((a, b, env), c)
reorder (x, (y, env, z)) = ((x, y, env), z)

--------------------------------------------------------------------------------

-- Constructing a prefix


-- | The error prefix (i.e., without a location in the strategy).

noPrefix :: Prefix a
noPrefix = Prefix [] empty

-- | Make a prefix from a core strategy and a start term.

makePrefix :: Process (Leaf a) -> a -> Prefix a
makePrefix = snd . replayProcess emptyPath

-- | Construct a prefix by replaying a path in a core strategy: the third

-- argument is the current term.

replayProcess :: Path -> Process (Leaf a) -> ([Rule a], a -> Prefix a)
replayProcess (Path is) = fromMaybe ([], const noPrefix) . replay [] is
 where
   replay acc path p =
      case path of
         []         -> return (reverse acc, createPrefix p)
         Input _:_  -> Nothing
         Index n:ns -> do
            (leaf, q) <- getByIndex n (menu p)
            case (leaf, ns) of
               (LeafRule r, _) -> replay (r:acc) ns q
               (LeafDyn d, Input t:ns2) -> do
                  a <- dynamicFromTerm d t
                  replay acc ns2 (treeToProcess a .*. q)
               _ -> Nothing

   createPrefix p = Prefix [Path is] . flip (rec []) p

   rec ns a = cut . onMenuWithIndex f doneMenu . menu
    where
      f n (LeafDyn d) p = fromMaybe empty $ do
         t <- dynamicToTerm d a
         s <- dynamicFromTerm d t
         return (rec (Input t:Index n:ns) a (treeToProcess s .*. p))
      f n (LeafRule r) p = choice
         [ r ?~> (b, env, mk b)
         | (b, env) <- transApply (transformation r) a
         ]
       where
         ms   = Index n:ns
         path = Path (is ++ reverse ms)
         mk b = Prefix [path] (rec ms b p)

         x ?~> y@(_, _, q)
            | isMinor r && stopped q = empty
            | otherwise = x |-> y

stopped :: Prefix a -> Bool
stopped = isEmpty . remainder

--------------------------------------------------------------------------------

-- Prefix fuctions


isEmptyPrefix :: Prefix a -> Bool
isEmptyPrefix = all (== emptyPath) . getPaths

-- | Transforms the prefix such that only major steps are kept in the remaining

-- strategy.

majorPrefix :: Prefix a -> Prefix a
majorPrefix prfx = prfx { remainder = onMenu f doneMenu (remainder prfx) }
 where
   f r (a, env, p)
      | isMajor r = r |-> (a, env, majorPrefix p)
      | otherwise = remainder (majorPrefix p)

-- | The searchModePrefix transformation changes the process in such a way that

--   all intermediate states can only be reached by one path. A prerequisite is

--   that symbols are unique (or only used once).

searchModePrefix :: Prefix a -> Prefix a
searchModePrefix prfx =
   prfx { remainder = rec (remainder (majorPrefix prfx)) }
 where
   rec m | hasDone m = doneMenu
         | otherwise = process (bests m)

   process [] = empty
   process ((r, (a, env, pr)):xs) =
      (r |-> (a, env, pr { remainder = rec (remainder pr) }))
      .|. process (concatMap (change r) xs)

   change y (r, pair) =
      bests (filterPrefix (/= y) r pair)

filterPrefix :: (Rule a -> Bool) -> Rule a -> (a, Environment, Prefix a) -> Menu (Rule a) (a, Environment, Prefix a)
filterPrefix cond = f
 where
   rec = onMenu f doneMenu
   f r (a, env, pr) = if cond r then r |-> (a, env, pr { remainder = rec (remainder pr) }) else empty

-- | Returns the current @Path@.

prefixPaths :: Prefix a -> [Path]
prefixPaths = getPaths

--------------------------------------------------------------------------------

-- Path


-- | A path encodes a location in a strategy. Paths are represented as a list

-- of integers and terms (the latter act as input for the dynamic strategies).

newtype Path = Path [PathItem]
   deriving Eq

data PathItem = Index Int | Input Term
   deriving Eq

instance Show PathItem where
   show (Index n) = show n
   show (Input t) = show t

instance Read PathItem where
   readsPrec n s =
      case dropWhile isSpace s of
         s2@(c:_) | isDigit c -> map (mapFirst Index) (readsPrec n s2)
         s2 -> map (mapFirst Input) (readsPrec n s2)

instance Show Path where
   show (Path is) = show is
   showList = (++) . intercalate ";" . map show

-- | The empty path.

emptyPath :: Path
emptyPath = Path []

readPath :: Monad m => String -> m Path
readPath = fmap Path . readM

readPaths :: Monad m => String -> m [Path]
readPaths = mapM readPath . splitsWithElem ';'