{-# LANGUAGE RankNTypes, ScopedTypeVariables, CPP #-}
module Data.Generics.Twins (
        
        gfoldlAccum,
        gmapAccumT,
        gmapAccumM,
        gmapAccumQl,
        gmapAccumQr,
        gmapAccumQ,
        gmapAccumA,
        
        gzipWithT,
        gzipWithM,
        gzipWithQ,
        
        geq,
        gzip,
        gcompare
  ) where
#ifdef __HADDOCK__
import Prelude
#endif
import Data.Data
import Data.Generics.Aliases
#ifdef __GLASGOW_HASKELL__
import Prelude hiding ( GT )
#endif
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative (Applicative(..))
import Data.Monoid         ( mappend, mconcat )
#endif
gfoldlAccum :: Data d
            => (forall e r. Data e => a -> c (e -> r) -> e -> (a, c r))
            -> (forall g. a -> g -> (a, c g))
            -> a -> d -> (a, c d)
gfoldlAccum k z a0 d = unA (gfoldl k' z' d) a0
 where
  k' c y = A (\a -> let (a', c') = unA c a in k a' c' y)
  z' f   = A (\a -> z a f)
newtype A a c d = A { unA :: a -> (a, c d) }
gmapAccumT :: Data d
           => (forall e. Data e => a -> e -> (a,e))
           -> a -> d -> (a, d)
gmapAccumT f a0 d0 = let (a1, d1) = gfoldlAccum k z a0 d0
                     in (a1, unID d1)
 where
  k a (ID c) d = let (a',d') = f a d
                  in (a', ID (c d'))
  z a x = (a, ID x)
gmapAccumA :: forall b d a. (Data d, Applicative a)
           => (forall e. Data e => b -> e -> (b, a e))
           -> b -> d -> (b, a d)
gmapAccumA f a0 d0 = gfoldlAccum k z a0 d0
    where
      k :: forall d' e. (Data d') =>
           b -> a (d' -> e) -> d' -> (b, a e)
      k a c d = let (a',d') = f a d
                    c' = c <*> d'
                in (a', c')
      z :: forall t c a'. (Applicative a') =>
           t -> c -> (t, a' c)
      z a x = (a, pure x)
gmapAccumM :: (Data d, Monad m)
           => (forall e. Data e => a -> e -> (a, m e))
           -> a -> d -> (a, m d)
gmapAccumM f = gfoldlAccum k z
 where
  k a c d = let (a',d') = f a d
             in (a', d' >>= \d'' -> c >>= \c' -> return (c' d''))
  z a x = (a, return x)
gmapAccumQl :: Data d
            => (r -> r' -> r)
            -> r
            -> (forall e. Data e => a -> e -> (a,r'))
            -> a -> d -> (a, r)
gmapAccumQl o r0 f a0 d0 = let (a1, r1) = gfoldlAccum k z a0 d0
                           in (a1, unCONST r1)
 where
  k a (CONST c) d = let (a', r) = f a d
                     in (a', CONST (c `o` r))
  z a _ = (a, CONST r0)
gmapAccumQr :: Data d
            => (r' -> r -> r)
            -> r
            -> (forall e. Data e => a -> e -> (a,r'))
            -> a -> d -> (a, r)
gmapAccumQr o r0 f a0 d0 = let (a1, l) = gfoldlAccum k z a0 d0
                           in (a1, unQr l r0)
 where
  k a (Qr c) d = let (a',r') = f a d
                  in (a', Qr (\r -> c (r' `o` r)))
  z a _ = (a, Qr id)
gmapAccumQ :: Data d
           => (forall e. Data e => a -> e -> (a,q))
           -> a -> d -> (a, [q])
gmapAccumQ f = gmapAccumQr (:) [] f
newtype ID x = ID { unID :: x }
newtype CONST c a = CONST { unCONST :: c }
newtype Qr r a = Qr { unQr  :: r -> r }
gzipWithT :: GenericQ (GenericT) -> GenericQ (GenericT)
gzipWithT f x y = case gmapAccumT perkid funs y of
                    ([], c) -> c
                    _       -> error "gzipWithT"
 where
  perkid a d = (tail a, unGT (head a) d)
  funs = gmapQ (\k -> GT (f k)) x
gzipWithM :: Monad m => GenericQ (GenericM m) -> GenericQ (GenericM m)
gzipWithM f x y = case gmapAccumM perkid funs y of
                    ([], c) -> c
                    _       -> error "gzipWithM"
 where
  perkid a d = (tail a, unGM (head a) d)
  funs = gmapQ (\k -> GM (f k)) x
gzipWithQ :: GenericQ (GenericQ r) -> GenericQ (GenericQ [r])
gzipWithQ f x y = case gmapAccumQ perkid funs y of
                   ([], r) -> r
                   _       -> error "gzipWithQ"
 where
  perkid a d = (tail a, unGQ (head a) d)
  funs = gmapQ (\k -> GQ (f k)) x
geq :: Data a => a -> a -> Bool
geq x0 y0 = geq' x0 y0
  where
    geq' :: GenericQ (GenericQ Bool)
    geq' x y =     (toConstr x == toConstr y)
                && and (gzipWithQ geq' x y)
gzip :: GenericQ (GenericM Maybe) -> GenericQ (GenericM Maybe)
gzip f x y =
  f x y
  `orElse`
  if toConstr x == toConstr y
    then gzipWithM (gzip f) x y
    else Nothing
gcompare :: Data a => a -> a -> Ordering
gcompare = gcompare'
  where
    gcompare' :: (Data a, Data b) => a -> b -> Ordering
    gcompare' x y
      = let repX = constrRep $ toConstr x
            repY = constrRep $ toConstr y
        in
        case (repX, repY) of
          (AlgConstr nX,   AlgConstr nY)   ->
            nX `compare` nY `mappend` mconcat (gzipWithQ gcompare' x y)
          (IntConstr iX,   IntConstr iY)   -> iX `compare` iY
          (FloatConstr rX, FloatConstr rY) -> rX `compare` rY
          (CharConstr cX,  CharConstr cY)  -> cX `compare` cY
          _ -> error "type incompatibility in gcompare"