-- |
-- This module describes the patterns supported by Relapse.
--
-- It also contains some simple functions for the map of references that a Relapse grammar consists of.
--
-- Finally it also contains some very simple pattern functions.
module Patterns (
    Pattern(..), 
    Refs, emptyRef, union, newRef, reverseLookupRef, lookupRef, hasRecursion,
    nullable, unescapable
) where

import qualified Data.Map.Strict as M
import qualified Data.Set as S

import Expr

-- |
-- Pattern recursively describes a Relapse Pattern.
data Pattern
    = Empty
    | ZAny
    | Node (Expr Bool) Pattern
    | Or Pattern Pattern
    | And Pattern Pattern
    | Not Pattern
    | Concat Pattern Pattern
    | Interleave Pattern Pattern
    | ZeroOrMore Pattern
    | Optional Pattern
    | Contains Pattern
    | Reference String
    deriving (Eq, Ord, Show)

-- |
-- The nullable function returns whether a pattern is nullable.
-- This means that the pattern matches the empty string.
nullable :: Refs -> Pattern -> Bool
nullable _ Empty = True
nullable _ ZAny = True
nullable _ Node{} = False
nullable refs (Or l r) = nullable refs l || nullable refs r
nullable refs (And l r) = nullable refs l && nullable refs r
nullable refs (Not p) = not $ nullable refs p
nullable refs (Concat l r) = nullable refs l && nullable refs r
nullable refs (Interleave l r) = nullable refs l && nullable refs r
nullable _ (ZeroOrMore _) = True
nullable _ (Optional _) = True
nullable refs (Contains p) = nullable refs p
nullable refs (Reference name) = nullable refs $ lookupRef refs name

-- |
-- unescapable is used for short circuiting.
-- A part of the tree can be skipped if all patterns are unescapable.
unescapable :: Pattern -> Bool
unescapable ZAny = True
unescapable (Not ZAny) = True
unescapable _ = False

-- |
-- Refs is a map from reference name to pattern and describes a relapse grammar.
newtype Refs = Refs (M.Map String Pattern)
    deriving (Show, Eq)

-- |
-- lookupRef looks up a pattern in the reference map, given a reference name.
lookupRef :: Refs -> String -> Pattern
lookupRef (Refs m) name = m M.! name

-- |
-- reverseLookupRef returns the reference name for a given pattern.
reverseLookupRef :: Pattern -> Refs -> Maybe String
reverseLookupRef p (Refs m) = case M.keys $ M.filter (== p) m of
    []      -> Nothing
    (k:_)  -> Just k

-- |
-- newRef returns a new reference map given a single pattern and its reference name.
newRef :: String -> Pattern -> Refs
newRef key value = Refs $ M.singleton key value

-- |
-- emptyRef returns an empty reference map.
emptyRef :: Refs
emptyRef = Refs M.empty

-- |
-- union returns the union of two reference maps.
union :: Refs -> Refs -> Refs
union (Refs m1) (Refs m2) = Refs $ M.union m1 m2 

-- |
-- hasRecursion returns whether an relapse grammar has any recursion, starting from the "main" reference.
hasRecursion :: Refs -> Bool
hasRecursion refs = hasRec refs (S.singleton "main") (lookupRef refs "main")

hasRec :: Refs -> S.Set String -> Pattern -> Bool
hasRec _ _ Empty = False
hasRec _ _ ZAny = False
hasRec _ _ Node{} = False
hasRec refs set (Or l r) = hasRec refs set l || hasRec refs set r
hasRec refs set (And l r) = hasRec refs set l || hasRec refs set r
hasRec refs set (Not p) = hasRec refs set p
hasRec refs set (Concat l r) = hasRec refs set l || (nullable refs l && hasRec refs set r)
hasRec refs set (Interleave l r) = hasRec refs set l || hasRec refs set r
hasRec _ _ (ZeroOrMore _) = False
hasRec refs set (Optional p) = hasRec refs set p
hasRec refs set (Contains p) = hasRec refs set p
hasRec refs set (Reference name) = S.member name set || hasRec refs (S.insert name set) (lookupRef refs name)