{-# LANGUAGE ScopedTypeVariables, DeriveAnyClass, DeriveGeneric,
    OverloadedStrings #-}
{-|
  Module      : Text.ANTLR.Allstar.Stacks
  Description : Graph-structured stack (GSS) for the ALL(*) algorithm
  Copyright   : (c) Karl Cronburg, 2018
  License     : BSD3
  Maintainer  : karl@cs.tufts.edu
  Stability   : experimental
  Portability : POSIX

-}
module Text.ANTLR.Allstar.Stacks
  ( Stacks(..)
  , (#)
  , merge
  , push
  , pop
  ) where
import qualified Prelude as P
import Prelude hiding (map, foldr, filter)
import Text.ANTLR.Set
  ( union, Set(..), foldr, map, filter
  , fromList, singleton, Hashable(..), Generic(..)
  )
import qualified Text.ANTLR.Set as Set
import Data.List (nub)
import Text.ANTLR.Pretty

-- | Graph-structured stack representation
data Stacks a =
    Empty
  | Wildcard
  | Stacks (Set [a])
  deriving (Eq, Ord, Generic, Hashable, Show)

instance (Prettify a, Hashable a, Eq a) => Prettify (Stacks a) where
  prettify Empty      = pStr "[]"
  prettify Wildcard   = pStr "#"
  prettify (Stacks s) = prettify s

-- | Represents the set of __all__ stacks
(#) = Wildcard

-- | Combine two GSSs
merge :: (Eq a, Hashable a) => Stacks a -> Stacks a -> Stacks a
merge Wildcard _   = Wildcard
merge _ Wildcard   = Wildcard
merge Empty Empty  = Empty
merge (Stacks )  Empty       = Stacks $  `union` fromList [[]]
merge Empty       (Stacks )  = Stacks $  `union` fromList [[]]
merge (Stacks ) (Stacks _Γ') = Stacks $  `union` _Γ'

-- | Push a state onto all the leaves of the given GSS
push :: (Eq a, Hashable a) => a -> Stacks a -> Stacks a
push a Empty        = Stacks $ singleton [a]
push a Wildcard     = Wildcard
push a (Stacks )  = Stacks $ map ((:) a) 

-- | Get heads of non-empty stacks / lists:
heads :: (Eq a, Hashable a) => Set [a] -> [a]
heads = let
    heads' :: [a] -> [a] -> [a]
    heads' []     bs = bs
    heads' (a:as) bs = a:bs
  in foldr heads' []

-- | Pop off all the current states from the given GSS
pop  :: (Eq a, Hashable a) => Stacks a -> [(a, Stacks a)]
pop Empty = []
pop Wildcard = []
pop (Stacks ) = let
--    ss :: a -> Stacks a
    ss a = Stacks $ map tail $ filter (\as -> (not . null) as && ((== a) . head) as) 
  in P.map (\a -> (a, ss a)) (nub . heads $ )