-- This file is part of the 'term-rewriting' library. It is licensed
-- under an MIT license. See the accompanying 'LICENSE' file for details.
--
-- Authors: Bertram Felgenhauer

{-# LANGUAGE BangPatterns #-}
-- |
-- Simple rewriting.
--
-- Note: The rules are assumed to be non-creating, i.e., variables on the
-- rhs should also occur on the lhs. Rules violating this constraint
-- will have no effect.
module Data.Rewriting.Rules.Rewrite (
    Reduct (..),
    Strategy,
    fullRewrite,
    outerRewrite,
    innerRewrite,
    rootRewrite,
    -- * utilities not reexported from "Data.Rewriting.Rules"
    nested,
    listContexts,
) where

import Data.Rewriting.Substitution
import Data.Rewriting.Pos
import Data.Rewriting.Rule

import Data.Maybe

-- | A reduct. It contains the resulting term, the position that the term
-- was rewritten at, and the applied rule.
data Reduct f v v' = Reduct {
     result :: Term f v,
     pos :: Pos,
     rule :: Rule f v',
     subst :: GSubst v' f v
}

-- | A rewrite strategy.
type Strategy f v v' = Term f v -> [Reduct f v v']

-- | Full rewriting: Apply rules anywhere in the term.
--
-- Reducts are returned in pre-order: the first is a leftmost, outermost redex.
fullRewrite :: (Ord v', Eq v, Eq f)
    => [Rule f v'] -> Strategy f v v'
fullRewrite trs t = rootRewrite trs t ++ nested (fullRewrite trs) t

-- | Outer rewriting: Apply rules at outermost redexes.
--
-- Reducts are returned in left to right order.
outerRewrite :: (Ord v', Eq v, Eq f)
    => [Rule f v'] -> Strategy f v v'
outerRewrite trs t = case rootRewrite trs t of
    [] -> nested (outerRewrite trs) t
    rs -> rs

-- | Inner rewriting: Apply rules at innermost redexes.
--
-- Reducts are returned in left to right order.
innerRewrite :: (Ord v', Eq v, Eq f)
    => [Rule f v'] -> Strategy f v v'
innerRewrite trs t = case nested (innerRewrite trs) t of
    [] -> rootRewrite trs t
    rs -> rs

-- | Root rewriting: Apply rules only at the root of the term.
--
-- This is mainly useful as a building block for various rewriting strategies.
rootRewrite :: (Ord v', Eq v, Eq f)
    => [Rule f v'] -> Strategy f v v'
rootRewrite trs t = do
    r <- trs
    s <- maybeToList $ match (lhs r) t
    t' <- maybeToList $ gApply s (rhs r)
    return Reduct{ result = t', pos = [], rule = r, subst = s }

-- | Nested rewriting: Apply a rewriting strategy to all arguments of a
-- function symbol, left to right. For variables, the result will be empty.
--
-- This is another building block for rewriting strategies.
nested :: Strategy f v v' -> Strategy f v v'
nested _ (Var _) = []
nested s (Fun f ts) = do
    (n, cl, t) <- listContexts ts
    (\r -> r{ result = Fun f (cl (result r)), pos = n : pos r }) `fmap` s t

-- | Return a list of contexts of a list. Each returned element is an element
-- index (starting from 0), a function that replaces the list element by a
-- new one, and the original element.
listContexts :: [a] -> [(Int, a -> [a], a)]
listContexts = go 0 id where
    go !n f [] = []
    go !n f (x:xs) = (n, f . (: xs), x) : go (n+1) (f . (x:)) xs