module Darcs.Patch.Merge
    ( 
      CleanMerge(..)
    , Merge(..)
      
    , selfMerger
    , swapMerger
    , mergerIdFL
    , mergerFLId
    , mergerFLFL
    , cleanMergeFL
    , mergeFL
    , swapMerge
    , swapCleanMerge
    , mergeList
      
    , prop_mergeSymmetric
    , prop_mergeCommute
    ) where
import Control.Monad ( foldM )
import Darcs.Prelude
import Darcs.Patch.Commute ( Commute(..) )
import Darcs.Patch.CommuteFn ( MergeFn, PartialMergeFn )
import Darcs.Patch.Invert ( Invert(..) )
import Darcs.Patch.Witnesses.Eq ( Eq2(..), isIsEq )
import Darcs.Patch.Witnesses.Ordered
    ( (:\/:)(..)
    , (:/\:)(..)
    , FL(..)
    , (:>)(..)
    , (+>+)
    )
import Darcs.Patch.Witnesses.Sealed ( Sealed(..) )
class CleanMerge p where
  cleanMerge :: (p :\/: p) wX wY -> Maybe ((p :/\: p) wX wY)
instance CleanMerge p => CleanMerge (FL p) where
  cleanMerge (NilFL :\/: x) = return $ x :/\: NilFL
  cleanMerge (x :\/: NilFL) = return $ NilFL :/\: x
  cleanMerge ((x :>: xs) :\/: ys) = do
    ys' :/\: x' <- cleanMergeFL (x :\/: ys)
    xs' :/\: ys'' <- cleanMerge (ys' :\/: xs)
    return $ ys'' :/\: (x' :>: xs')
cleanMergeFL :: CleanMerge p => PartialMergeFn p (FL p)
cleanMergeFL (p :\/: NilFL) = return $ NilFL :/\: p
cleanMergeFL (p :\/: (x :>: xs)) = do
  x' :/\: p'  <- cleanMerge (p :\/: x)
  xs' :/\: p'' <- cleanMergeFL (p' :\/: xs)
  return $ (x' :>: xs') :/\: p''
class CleanMerge p => Merge p where
    merge :: (p :\/: p) wX wY -> (p :/\: p) wX wY
selfMerger :: Merge p => MergeFn p p
selfMerger = merge
instance Merge p => Merge (FL p) where
    merge = mergerFLFL merge
mergeFL :: Merge p
        => (p :\/: FL p) wX wY
        -> (FL p :/\: p) wX wY
mergeFL = mergerIdFL merge
mergerIdFL :: MergeFn p q -> MergeFn p (FL q)
mergerIdFL _mergeFn (p :\/: NilFL) = NilFL :/\: p
mergerIdFL  mergeFn (p :\/: (x :>: xs)) =
  case mergeFn (p :\/: x) of
    x' :/\: p' -> case mergerIdFL mergeFn (p' :\/: xs) of
      xs' :/\: p'' -> (x' :>: xs') :/\: p''
mergerFLId :: MergeFn p q -> MergeFn (FL p) q
mergerFLId mergeFn = swapMerger (mergerIdFL (swapMerger mergeFn))
mergerFLFL :: MergeFn p q -> MergeFn (FL p) (FL q)
mergerFLFL mergeFn = mergerIdFL (mergerFLId mergeFn)
swapMerge :: Merge p => (p :\/: p) wX wY -> (p :/\: p) wX wY
swapMerge = swapMerger merge
swapMerger :: MergeFn p q -> MergeFn q p
swapMerger mergeFn (x :\/: y) = case mergeFn (y :\/: x) of x' :/\: y' -> y' :/\: x'
swapCleanMerge :: CleanMerge p => (p :\/: p) wX wY -> Maybe ((p :/\: p) wX wY)
swapCleanMerge (x :\/: y) = do
  x' :/\: y' <- cleanMerge (y :\/: x)
  return $ y' :/\: x'
mergeList :: CleanMerge p
          => [Sealed (FL p wX)]
          -> Either (Sealed (FL p wX), Sealed (FL p wX)) (Sealed (FL p wX))
mergeList = foldM mergeTwo (Sealed NilFL)
  where
    mergeTwo (Sealed ps) (Sealed qs) =
      case cleanMerge (ps :\/: qs) of
        Just (qs' :/\: _) -> Right $ Sealed $ ps +>+ qs'
        Nothing -> Left (Sealed ps, Sealed qs)
_forceCommute :: (Commute p, Merge p, Invert p) => (p :> p) wX wY -> (p :> p) wX wY
_forceCommute (p :> q) =
  case commute (p :> q) of
    Just (q' :> p') -> q' :> p'
    Nothing ->
      case merge (invert p :\/: q) of
        q' :/\: ip' -> q' :> invert ip'
prop_mergeSymmetric :: (Eq2 p, Merge p) => (p :\/: p) wX wY -> Bool
prop_mergeSymmetric (p :\/: q) =
  case merge (p :\/: q) of
    q' :/\: p' ->
      case merge (q :\/: p) of
        p'' :/\: q'' ->
          isIsEq (q' =\/= q'') && isIsEq (p' =\/= p'')
prop_mergeCommute :: (Commute p, Eq2 p, Merge p) => (p :\/: p) wX wY -> Bool
prop_mergeCommute (p :\/: q) =
  case merge (p :\/: q) of
    q' :/\: p' ->
      case commute (p :> q') of
        Nothing -> False
        Just (q'' :> p'') ->
          isIsEq (q'' =\/= q) && isIsEq (p'' =/\= p')
      &&
      case commute (q :> p') of
        Nothing -> False
        Just (p'' :> q'') ->
          isIsEq (p'' =\/= p) && isIsEq (q'' =/\= q')