{-# LANGUAGE TypeFamilies              #-}
{-# LANGUAGE UndecidableInstances      #-}
module Agda.Syntax.Abstract.Pattern where
import Prelude hiding (null)
import Control.Arrow ((***), second)
import Control.Monad ((>=>))
import Control.Monad.Identity
import Control.Applicative (liftA2)
import Data.Foldable (Foldable, foldMap)
import Data.Traversable (Traversable, traverse)
import Data.Maybe
import Data.Monoid
import Agda.Syntax.Abstract as A
import Agda.Syntax.Common
import Agda.Syntax.Concrete (FieldAssignment')
import qualified Agda.Syntax.Concrete as C
import Agda.Syntax.Concrete.Pattern (IsWithP(..))
import Agda.Syntax.Info
import Agda.Syntax.Position
import Agda.Utils.Functor
import Agda.Utils.List
import Agda.Utils.Null
import Agda.Utils.Impossible
type NAP = NamedArg Pattern
class MapNamedArgPattern a  where
  mapNamedArgPattern :: (NAP -> NAP) -> a -> a
  default mapNamedArgPattern
     :: (Functor f, MapNamedArgPattern a', a ~ f a') => (NAP -> NAP) -> a -> a
  mapNamedArgPattern = fmap . mapNamedArgPattern
instance MapNamedArgPattern NAP where
  mapNamedArgPattern f p =
    case namedArg p of
      
      VarP{}    -> f p
      WildP{}   -> f p
      DotP{}    -> f p
      EqualP{}  -> f p
      LitP{}    -> f p
      AbsurdP{} -> f p
      ProjP{}   -> f p
      
      ConP i qs ps       -> f $ setNamedArg p $ ConP i qs $ mapNamedArgPattern f ps
      DefP i qs ps       -> f $ setNamedArg p $ DefP i qs $ mapNamedArgPattern f ps
      PatternSynP i x ps -> f $ setNamedArg p $ PatternSynP i x $ mapNamedArgPattern f ps
      
      
      RecP i fs          -> f $ setNamedArg p $ RecP i $ map (fmap namedArg) $ mapNamedArgPattern f $ map (fmap (setNamedArg p)) fs
      
      AsP i x p0         -> f $ updateNamedArg (AsP i x) $ mapNamedArgPattern f $ setNamedArg p p0
      
      WithP i p0         -> f $ updateNamedArg (WithP i) $ mapNamedArgPattern f $ setNamedArg p p0
instance MapNamedArgPattern a => MapNamedArgPattern [a]                  where
instance MapNamedArgPattern a => MapNamedArgPattern (FieldAssignment' a) where
instance MapNamedArgPattern a => MapNamedArgPattern (Maybe a)            where
instance (MapNamedArgPattern a, MapNamedArgPattern b) => MapNamedArgPattern (a,b) where
  mapNamedArgPattern f (a, b) = (mapNamedArgPattern f a, mapNamedArgPattern f b)
class APatternLike a p | p -> a where
  
  foldrAPattern
    :: Monoid m
    => (Pattern' a -> m -> m)
         
    -> p -> m
  default foldrAPattern
    :: (Monoid m, Foldable f, APatternLike a b, f b ~ p)
    => (Pattern' a -> m -> m) -> p -> m
  foldrAPattern = foldMap . foldrAPattern
  
  traverseAPatternM
    :: Monad m
    => (Pattern' a -> m (Pattern' a))  
    -> (Pattern' a -> m (Pattern' a))  
    -> p -> m p
  default traverseAPatternM
    :: (Traversable f, APatternLike a q, f q ~ p, Monad m)
    => (Pattern' a -> m (Pattern' a))
    -> (Pattern' a -> m (Pattern' a))
    -> p -> m p
  traverseAPatternM pre post = traverse $ traverseAPatternM pre post
foldAPattern :: (APatternLike a p, Monoid m) => (Pattern' a -> m) -> p -> m
foldAPattern f = foldrAPattern $ \ p m -> f p `mappend` m
preTraverseAPatternM
  :: (APatternLike a b, Monad m )
  => (Pattern' a -> m (Pattern' a))  
  -> b -> m b
preTraverseAPatternM pre p = traverseAPatternM pre return p
postTraverseAPatternM
  :: (APatternLike a b, Monad m)
  => (Pattern' a -> m (Pattern' a))  
  -> b -> m b
postTraverseAPatternM post p = traverseAPatternM return post p
mapAPattern :: APatternLike a p => (Pattern' a -> Pattern' a) -> p -> p
mapAPattern f = runIdentity . postTraverseAPatternM (Identity . f)
instance APatternLike a (Pattern' a) where
  foldrAPattern f p = f p $
    case p of
      AsP _ _ p          -> foldrAPattern f p
      ConP _ _ ps        -> foldrAPattern f ps
      DefP _ _ ps        -> foldrAPattern f ps
      RecP _ ps          -> foldrAPattern f ps
      PatternSynP _ _ ps -> foldrAPattern f ps
      WithP _ p          -> foldrAPattern f p
      VarP _             -> mempty
      ProjP _ _ _        -> mempty
      WildP _            -> mempty
      DotP _ _           -> mempty
      AbsurdP _          -> mempty
      LitP _             -> mempty
      EqualP _ _         -> mempty
  traverseAPatternM pre post = pre >=> recurse >=> post
    where
    recurse p = case p of
      
      A.VarP{}             -> return p
      A.WildP{}            -> return p
      A.DotP{}             -> return p
      A.LitP{}             -> return p
      A.AbsurdP{}          -> return p
      A.ProjP{}            -> return p
      A.EqualP{}           -> return p
      
      A.ConP        i ds ps -> A.ConP        i ds <$> traverseAPatternM pre post ps
      A.DefP        i q  ps -> A.DefP        i q  <$> traverseAPatternM pre post ps
      A.AsP         i x  p  -> A.AsP         i x  <$> traverseAPatternM pre post p
      A.RecP        i    ps -> A.RecP        i    <$> traverseAPatternM pre post ps
      A.PatternSynP i x  ps -> A.PatternSynP i x  <$> traverseAPatternM pre post ps
      A.WithP       i p     -> A.WithP       i    <$> traverseAPatternM pre post p
instance APatternLike a b => APatternLike a (Arg b)              where
instance APatternLike a b => APatternLike a (Named n b)          where
instance APatternLike a b => APatternLike a [b]                  where
instance APatternLike a b => APatternLike a (Maybe b)            where
instance APatternLike a b => APatternLike a (FieldAssignment' b) where
instance (APatternLike a b, APatternLike a c) => APatternLike a (b,c) where
  foldrAPattern f (p, p') =
    foldrAPattern f p `mappend` foldrAPattern f p'
  traverseAPatternM pre post (p, p') =
    liftA2 (,)
      (traverseAPatternM pre post p)
      (traverseAPatternM pre post p')
patternVars :: forall a p. APatternLike a p => p -> [A.Name]
patternVars p = foldAPattern f p `appEndo` []
  where
  
  f :: Pattern' a -> Endo [A.Name]
  f = \case
    A.VarP x         -> Endo (unBind x :)
    A.AsP _ x _      -> Endo (unBind x :)
    A.LitP        {} -> mempty
    A.ConP        {} -> mempty
    A.RecP        {} -> mempty
    A.DefP        {} -> mempty
    A.ProjP       {} -> mempty
    A.WildP       {} -> mempty
    A.DotP        {} -> mempty
    A.AbsurdP     {} -> mempty
    A.EqualP      {} -> mempty
    A.PatternSynP {} -> mempty
    A.WithP _ _      -> mempty
containsAPattern :: APatternLike a p => (Pattern' a -> Bool) -> p -> Bool
containsAPattern f = getAny . foldAPattern (Any . f)
containsAbsurdPattern :: APatternLike a p => p -> Bool
containsAbsurdPattern = containsAPattern $ \case
    A.PatternSynP{} -> __IMPOSSIBLE__
    A.AbsurdP{}     -> True
    _               -> False
containsAsPattern :: APatternLike a p => p -> Bool
containsAsPattern = containsAPattern $ \case
    A.PatternSynP{} -> __IMPOSSIBLE__
    A.AsP{}         -> True
    _               -> False
checkPatternLinearity :: (Monad m, APatternLike a p)
                      => p -> ([C.Name] -> m ()) -> m ()
checkPatternLinearity ps err =
  unlessNull (duplicates $ map nameConcrete $ patternVars ps) $ \ys -> err ys
substPattern :: [(Name, Pattern)] -> Pattern -> Pattern
substPattern s = substPattern' (substExpr $ map (second patternToExpr) s) s
substPattern'
  :: (e -> e)              
  -> [(Name, Pattern' e)]  
  -> Pattern' e            
  -> Pattern' e
substPattern' subE s = mapAPattern $ \ p -> case p of
  VarP x            -> fromMaybe p $ lookup (A.unBind x) s
  DotP i e          -> DotP i $ subE e
  EqualP i es       -> EqualP i $ map (subE *** subE) es
  
  ConP _ _ _        -> p
  RecP _ _          -> p
  ProjP _ _ _       -> p
  WildP _           -> p
  AbsurdP _         -> p
  LitP _            -> p
  DefP _ _ _        -> p
  AsP _ _ _         -> p 
  PatternSynP _ _ _ -> p
  WithP _ _         -> p
instance IsWithP (Pattern' e) where
  isWithP = \case
    WithP _ p -> Just p
    _ -> Nothing
splitOffTrailingWithPatterns :: A.Patterns -> (A.Patterns, A.Patterns)
splitOffTrailingWithPatterns = spanEnd (isJust . isWithP)
trailingWithPatterns :: Patterns -> Patterns
trailingWithPatterns = snd . splitOffTrailingWithPatterns
data LHSPatternView e
  = LHSAppP  (NAPs e)
      
  | LHSProjP ProjOrigin AmbiguousQName (NamedArg (Pattern' e))
      
  | LHSWithP [Pattern' e]
      
      
  deriving (Show)
lhsPatternView :: IsProjP e => NAPs e -> Maybe (LHSPatternView e, NAPs e)
lhsPatternView [] = Nothing
lhsPatternView (p0 : ps) =
  case namedArg p0 of
    ProjP _i o d -> Just (LHSProjP o d p0, ps)
    
    WithP _i p   -> Just (LHSWithP (p : map namedArg ps1), ps2)
      where
      (ps1, ps2) = spanJust isWithP ps
    
    _ -> Just (LHSAppP (p0 : ps1), ps2)
      where
      (ps1, ps2) = span (\ p -> isNothing (isProjP p) && isNothing (isWithP p)) ps
class LHSToSpine a b where
  lhsToSpine :: a -> b
  spineToLhs :: b -> a
instance LHSToSpine Clause SpineClause where
  lhsToSpine = fmap lhsToSpine
  spineToLhs = fmap spineToLhs
instance LHSToSpine a b => LHSToSpine [a] [b] where
  lhsToSpine = map lhsToSpine
  spineToLhs = map spineToLhs
instance LHSToSpine LHS SpineLHS where
  lhsToSpine (LHS i core) = SpineLHS i f ps
    where QNamed f ps = lhsCoreToSpine core
  spineToLhs (SpineLHS i f ps) = LHS i (spineToLhsCore $ QNamed f ps)
lhsCoreToSpine :: LHSCore' e -> A.QNamed [NamedArg (Pattern' e)]
lhsCoreToSpine = \case
  LHSHead f ps     -> QNamed f ps
  LHSProj d h ps   -> lhsCoreToSpine (namedArg h) <&> (++ (p : ps))
    where p = updateNamedArg (const $ ProjP empty ProjPrefix d) h
  LHSWith h wps ps -> lhsCoreToSpine h <&> (++ map (defaultNamedArg . mkWithP) wps ++ ps)
    where mkWithP p = WithP (PatRange $ getRange p) p
spineToLhsCore :: IsProjP e => QNamed [NamedArg (Pattern' e)] -> LHSCore' e
spineToLhsCore (QNamed f ps) = lhsCoreAddSpine (LHSHead f []) ps
lhsCoreApp :: LHSCore' e -> [NamedArg (Pattern' e)] -> LHSCore' e
lhsCoreApp core ps = core { lhsPats = lhsPats core ++ ps }
lhsCoreWith :: LHSCore' e -> [Pattern' e] -> LHSCore' e
lhsCoreWith (LHSWith core wps []) wps' = LHSWith core (wps ++ wps') []
lhsCoreWith core                  wps' = LHSWith core wps' []
lhsCoreAddChunk :: IsProjP e => LHSCore' e -> LHSPatternView e -> LHSCore' e
lhsCoreAddChunk core = \case
  LHSAppP ps               -> lhsCoreApp core ps
  LHSWithP wps             -> lhsCoreWith core wps
  LHSProjP ProjPrefix d np -> LHSProj d (setNamedArg np core) []  
  LHSProjP _          _ np -> lhsCoreApp core [np]       
lhsCoreAddSpine :: IsProjP e => LHSCore' e -> [NamedArg (Pattern' e)] -> LHSCore' e
lhsCoreAddSpine core ps =
  
  case lhsPatternView ps of
    Nothing       -> core
    Just (v, ps') -> lhsCoreAddChunk core chunk `lhsCoreAddSpine` ps'
      where
      
      
      
      
      chunk = case v of
        LHSProjP ProjPrefix _ _
          -> v
        LHSProjP _ d np | let nh = C.numHoles d, nh > 0, nh <= 1 + length ps'
          -> LHSProjP ProjPrefix d np
        _ -> v
lhsCoreAllPatterns :: LHSCore' e -> [Pattern' e]
lhsCoreAllPatterns = map namedArg . qnamed . lhsCoreToSpine
lhsCoreToPattern :: LHSCore -> Pattern
lhsCoreToPattern lc =
  case lc of
    LHSHead f aps         -> DefP noInfo (unambiguous f) aps
    LHSProj d lhscore aps -> DefP noInfo d $
      fmap (fmap lhsCoreToPattern) lhscore : aps
    LHSWith h wps aps     -> case lhsCoreToPattern h of
      DefP r q ps         -> DefP r q $ ps ++ map (\ p -> defaultNamedArg $ WithP (PatRange $ getRange p) p) wps ++ aps
      _ -> __IMPOSSIBLE__
  where noInfo = empty 
mapLHSHead :: (QName -> [NamedArg Pattern] -> LHSCore) -> LHSCore -> LHSCore
mapLHSHead f = \case
  LHSHead x ps     -> f x ps
  LHSProj d h ps   -> LHSProj d (fmap (fmap (mapLHSHead f)) h) ps
  LHSWith h wps ps -> LHSWith (mapLHSHead f h) wps ps