-- |
--
-- Module:      Control.Egison.Matcher.Collection
-- Description: Matchers for collection data types
-- Stability:   experimental

module Control.Egison.Matcher.Collection
  ( CollectionPattern(..)
  , List(..)
  , Multiset(..)
  , Set(..)
  )
where

import           Data.List                      ( tails )
import           Control.Monad                  ( MonadPlus(..) )
import           Control.Monad.Search
import           Control.Egison.Match
import           Control.Egison.Matcher
import           Control.Egison.Matcher.Pair
import           Control.Egison.QQ

-- | Class for collection pattern constructors.
class CollectionPattern m t where
  -- | Matcher for the elements.
  type ElemM m
  -- | Type of the target elements.
  type ElemT t
  -- | Pattern that matches with empty collections.
  -- @[]@ is desugared into 'nil' by the quasi-quoter.
  nil :: Pattern () m t ()
  nilM :: m -> t -> ()
  default nilM :: m -> t -> ()
  {-# INLINE nilM #-}
  nilM _ _ = ()
  -- | Pattern that destructs collections into its head and tail.
  -- @:@ is desugared into 'cons' by the quasi-quoter.
  cons :: Pattern (PP (ElemT t), PP t) m t (ElemT t, t)
  consM :: m -> t -> (ElemM m, m)
  -- | Pattern that destructs collections into its initial prefix and remaining suffix.
  -- @++@ is desugared into 'join' by the quasi-quoter.
  join :: Pattern (PP t, PP t) m t (t, t)
  joinM :: m -> t -> (m, m)
  default joinM :: m -> t -> (m, m)
  {-# INLINE joinM #-}
  joinM m _ = (m, m)
  elm :: Pattern (PP (ElemT t)) m t (ElemT t)
  elmM :: m -> t -> ElemM m
  joinCons :: Pattern (PP t, PP (ElemT t), PP t) m t (t, ElemT t, t)
  joinConsM :: m -> t -> (m, ElemM m, m)

-- | 'List' matcher is a matcher for collections that matches as if they're normal lists.
newtype List m = List m

instance Matcher m t => Matcher (List m) [t]

instance Matcher m t => CollectionPattern (List m) [t] where
  {-# SPECIALIZE instance Matcher m t => CollectionPattern (List m) [t] #-}
  type ElemM (List m) = m
  type ElemT [t] = t
  {-# INLINE nil #-}
  nil _ _ [] = pure ()
  nil _ _ _  = mzero
  {-# INLINE cons #-}
  cons _ _        []       = mzero
  cons _ (List _) (x : xs) = pure (x, xs)
  {-# INLINE consM #-}
  consM (List m) _ = (m, List m)
  {-# INLINABLE join #-}
  join (WC, _) _ xs       = map (\ts -> (undefined, ts)) (tails xs)
  join _       _ []       = pure ([], [])
  join ps      m (x : xs) = pure ([], x : xs) `mplus` do
    (ys, zs) <- join ps m xs
    pure (x : ys, zs)
  {-# INLINE elm #-}
  elm _ (List _) xs = xs
  {-# INLINE elmM #-}
  elmM (List m) _ = m
  {-# INLINE joinCons #-}
  joinCons (_, _, _) (List _) = f []
   where
    f _ [] = []
    f rhs (x : ts) = (reverse rhs, x, ts) : f (x : rhs) ts
  {-# INLINE joinConsM #-}
  joinConsM (List m) _ = (List m, m, List m)

instance (Eq a, Matcher m a, ValuePattern m a) => ValuePattern (List m) [a] where
  value e () (List m) v = if eqAs (List m) (List m) e v then pure () else mzero

newtype Multiset m = Multiset m

instance Matcher m t => Matcher (Multiset m) [t]

instance Matcher m t => CollectionPattern (Multiset m) [t] where
  {-# SPECIALIZE instance Matcher m t => CollectionPattern (Multiset m) [t] #-}
  type ElemM (Multiset m) = m
  type ElemT [t] = t
  {-# INLINE nil #-}
  nil _ _ [] = pure ()
  nil _ _ _  = mzero
  {-# INLINE cons #-}
  cons (_, WC) (Multiset _) xs = map (\x -> (x, undefined)) xs
  cons _       (Multiset _) xs = matchAll
    dfs
    xs
    (List Something)
    [[mc| $hs ++ $x : $ts -> (x, hs ++ ts) |]]
  {-# INLINE consM #-}
  consM (Multiset m) _ = (m, Multiset m)
  {-# INLINABLE join #-}
  join = undefined
  elm = undefined
  elmM = undefined
  joinCons = undefined
  joinConsM = undefined

instance (Eq a, Matcher m a, ValuePattern m a) => ValuePattern (Multiset m) [a] where
  value e () (Multiset m) v =
    if eqAs (List m) (Multiset m) e v then pure () else mzero

newtype Set m = Set m

instance Matcher m t => Matcher (Set m) [t]

instance Matcher m t => CollectionPattern (Set m) [t] where
  {-# SPECIALIZE instance Matcher m t => CollectionPattern (Set m) [t] #-}
  type ElemM (Set m) = m
  type ElemT [t] = t
  {-# INLINE nil #-}
  nil _ _ [] = pure ()
  nil _ _ _  = mzero
  {-# INLINE cons #-}
  cons (_, WC) (Set _) xs = map (\x -> (x, undefined)) xs
  cons _       (Set _) xs = map (\x -> (x, xs)) xs
  {-# INLINE consM #-}
  consM (Set m) _ = (m, Set m)
  {-# INLINABLE join #-}
  join = undefined
  elm = undefined
  elmM = undefined
  joinCons = undefined
  joinConsM = undefined

instance (Eq a, Matcher m a, ValuePattern m a) => ValuePattern (Set m) [a] where
  value e () (Set m) v = if eqAs (List m) (Set m) e v then pure () else mzero

eqAs :: (Matcher m1 t1, Matcher m2 t2,
         ValuePattern (ElemM m2) (ElemT t2), ElemT t1 ~ ElemT t2,
         CollectionPattern m1 t1, CollectionPattern m2 t2) =>
        m1 -> m2 -> t1 -> t2 -> Bool
eqAs m1 m2 xs ys = match
  dfs
  (xs, ys)
  (Pair m1 m2)
  [ [mc| ([], []) -> True |]
  , [mc| ($x : $xs, #x : $ys) -> eqAs m1 m2 xs ys |]
  , [mc| _ -> False |]
  ]