module Darcs.Patch.Commute
    ( Commute(..)
    , commuteFL
    , commuteFLorComplain
    , commuteRL
    , commuteRLFL
    , selfCommuter
    ) where

import Prelude ()
import Darcs.Prelude

import Darcs.Patch.CommuteFn ( CommuteFn )

import Darcs.Patch.Witnesses.Ordered
    ( FL(..), RL(..), reverseFL, reverseRL,
    (:>)(..) )
import Darcs.Patch.Witnesses.Sealed ( Sealed2, seal2 )

-- | Commute represents things that can be (possibly) commuted.
class Commute p where
    commute :: (p :> p) wX wY -> Maybe ((p :> p) wX wY)

instance Commute p => Commute (FL p) where
    commute (NilFL :> x) = Just (x :> NilFL)
    commute (x :> NilFL) = Just (NilFL :> x)
    commute (xs :> ys) = do
        ys' :> rxs' <- commuteRLFL (reverseFL xs :> ys)
        return $ ys' :> reverseRL rxs'

-- |'commuteRLFL' commutes an 'RL' past an 'FL'.
commuteRLFL :: Commute p => (RL p :> FL p) wX wY
            -> Maybe ((FL p :> RL p) wX wY)
commuteRLFL (NilRL :> ys) = Just (ys :> NilRL)
commuteRLFL (xs :> NilFL) = Just (NilFL :> xs)
commuteRLFL (xs :> y :>: ys) = do
    y' :> xs' <- commuteRL (xs :> y)
    ys' :> xs'' <- commuteRLFL (xs' :> ys)
    return (y' :>: ys' :> xs'')

instance Commute p => Commute (RL p) where
    commute (xs :> ys) = do
        fys' :> xs' <- commuteRLFL (xs :> reverseRL ys)
        return (reverseFL fys' :> xs')

-- |'commuteRL' commutes a RL past a single element.
commuteRL :: Commute p => (RL p :> p) wX wY -> Maybe ((p :> RL p) wX wY)
commuteRL (zs :<: z :> w) = do
    w' :> z' <- commute (z :> w)
    w'' :> zs' <- commuteRL (zs :> w')
    return (w'' :> zs' :<: z')
commuteRL (NilRL :> w) = Just (w :> NilRL)

-- |'commuteFL' commutes a single element past a FL.
commuteFL :: Commute p => (p :> FL p) wX wY -> Maybe ((FL p :> p) wX wY)
commuteFL = either (const Nothing) Just . commuteFLorComplain

-- |'commuteFLorComplain' attempts to commute a single element past a FL. If
-- any individual commute fails, then we return the patch that first patch that
-- cannot be commuted past.
commuteFLorComplain :: Commute p => (p :> FL p) wX wY
                    -> Either (Sealed2 p) ((FL p :> p) wX wY)
commuteFLorComplain (p :> NilFL) = Right (NilFL :> p)
commuteFLorComplain (q :> p :>: ps) =
    case commute (q :> p) of
        Just (p' :> q') ->
            case commuteFLorComplain (q' :> ps) of
                Right (ps' :> q'') -> Right (p' :>: ps' :> q'')
                Left l -> Left l
        Nothing -> Left $ seal2 p

-- |Build a commuter between a patch and itself using the operation from the type class.
selfCommuter :: Commute p => CommuteFn p p
selfCommuter = commute
