module Data.InvertibleGrammar
  ( Grammar (..)
  , (:-) (..)
  , iso
  , osi
  , partialIso
  , partialOsi
  , push
  , pushForget
  , InvertibleGrammar(..)
  , GrammarError (..)
  , Mismatch
  , expected
  , unexpected
  ) where
import Prelude hiding ((.), id)
#if !defined(__GLASGOW_HASKELL__) || __GLASGOW_HASKELL__ < 710
import Control.Applicative
#endif
import Control.Category
import Control.Monad
import Data.Semigroup as Semi
import Data.InvertibleGrammar.Monad
data Grammar g t t' where
  
  PartialIso :: String -> (a -> b) -> (b -> Either Mismatch a) -> Grammar g a b
  
  Iso :: (a -> b) -> (b -> a) -> Grammar g a b
  
  Flip :: Grammar g a b -> Grammar g b a
  
  (:.:) :: Grammar g b c -> Grammar g a b -> Grammar g a c
  
  (:<>:) :: Grammar g a b -> Grammar g a b -> Grammar g a b
  
  Inject :: g a b -> Grammar g a b
instance Category (Grammar c) where
  id = Iso id id
  (.) x y = x :.: y
instance Semi.Semigroup (Grammar c t1 t2) where
  (<>) = (:<>:)
data h :- t = h :- t deriving (Eq, Show, Functor)
infixr 5 :-
iso :: (a -> b) -> (b -> a) -> Grammar g (a :- t) (b :- t)
iso f' g' = Iso f g
  where
    f (a :- t) = f' a :- t
    g (b :- t) = g' b :- t
osi :: (b -> a) -> (a -> b) -> Grammar g (a :- t) (b :- t)
osi f' g' = Iso g f
  where
    f (a :- t) = f' a :- t
    g (b :- t) = g' b :- t
partialIso :: String -> (a -> b) -> (b -> Either Mismatch a) -> Grammar g (a :- t) (b :- t)
partialIso prismName f' g' = PartialIso prismName f g
  where
    f (a :- t) = f' a :- t
    g (b :- t) = (:- t) <$> g' b
partialOsi :: String -> (b -> a) -> (a -> Either Mismatch b) -> Grammar g (a :- t) (b :- t)
partialOsi prismName f' g' = Flip $ PartialIso prismName f g
  where
    f (a :- t) = f' a :- t
    g (b :- t) = (:- t) <$> g' b
push :: (Eq a) => a -> Grammar g t (a :- t)
push a = PartialIso "push" f g
  where
    f t = a :- t
    g (a' :- t)
      | a == a' = Right t
      | otherwise = Left $ unexpected "pushed element"
pushForget :: a -> Grammar g t (a :- t)
pushForget a = Iso f g
  where
    f t = a :- t
    g (_ :- t) = t
class InvertibleGrammar m g where
  forward  :: g a b -> (a -> m b)
  backward :: g a b -> (b -> m a)
instance
  ( Monad m
  , MonadPlus m
  , MonadContextError (Propagation p) (GrammarError p) m
  , InvertibleGrammar m g
  ) => InvertibleGrammar m (Grammar g) where
  forward (Iso f _)           = return . f
  forward (PartialIso _ f _)  = return . f
  forward (Flip g)            = backward g
  forward (g :.: f)           = forward g <=< forward f
  forward (f :<>: g)          = \x -> forward f x `mplus` forward g x
  forward (Inject g)          = forward g
  
  backward (Iso _ g)          = return . g
  backward (PartialIso _ _ g) = either (\mis -> throwInContext (\ctx -> GrammarError ctx mis)) return . g
  backward (Flip g)           = forward g
  backward (g :.: f)          = backward g >=> backward f
  backward (f :<>: g)         = \x -> backward f x `mplus` backward g x
  backward (Inject g)         = backward g