module Darcs.Test.Patch.Properties.V1Set1
       ( checkMerge, checkMergeEquiv, checkMergeSwap, checkCanon
       , checkCommute, checkCantCommute
       , tShowRead
       , tMergeEitherWayValid, tTestCheck ) where

import Darcs.Patch
     ( commute, invert, merge, effect
     , readPatch, showPatch
     , fromPrim, canonize, sortCoalesceFL )
import Darcs.Patch.Invert ( Invert )
import Darcs.Patch.Merge ( Merge )
import Darcs.Patch.Read ( ReadPatch )
import Darcs.Patch.Show ( ShowPatchBasic, ShowPatchFor(..) )
import qualified Darcs.Patch.V1 as V1 ( RepoPatchV1 )
import qualified Darcs.Patch.V1.Prim as V1 ( Prim(..) )
import Darcs.Test.Patch.Properties.Check ( checkAPatch, Check )
import Darcs.Util.Printer ( renderPS )
import Darcs.Patch.Witnesses.Eq
import Darcs.Patch.Witnesses.Ordered
import Darcs.Patch.Witnesses.Show
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed) )
import Darcs.Patch.Witnesses.Unsafe( unsafeCoercePEnd )
import Darcs.Test.Util.TestResult
import qualified Darcs.Util.Diff as D (DiffAlgorithm(..))
import Darcs.Util.Printer ( text )


type Patch = V1.RepoPatchV1 V1.Prim


quickmerge :: (Merge p) => (p :\/: p ) wX wY -> p wY wZ
quickmerge (p1:\/:p2) = case merge (p1:\/:p2) of
                        _ :/\: p1' -> unsafeCoercePEnd p1'

instance Eq2 p => Eq ((p :/\: p) wX wY) where
   (x :/\: y) == (x' :/\: y') = isIsEq (x =\/= x') && isIsEq (y =\/= y')

-- ----------------------------------------------------------------------------
-- A number of "comparison" properties: these carry out some operation on
-- inputs (first value in the pair) and compare the results with a known
-- expected value (the second value in the pair).
--

checkMerge :: ((FL Patch:\/: FL Patch) wX wY, FL Patch wY wZ) -> TestResult
checkMerge (p1:\/:p2,p1') =
   case merge (p1:\/:p2) of
   _ :/\: p1a ->
       if isIsEq (p1a `eqFL` p1')
       then succeeded
       else failed $ text $ "Merge gave wrong value!\n"++show p1++show p2
            ++"I expected\n"++show p1'
            ++"but found instead\n"++show p1a

checkMergeEquiv :: ((FL Patch:\/:FL Patch) wX wY,FL Patch wY wZ) -> TestResult
checkMergeEquiv (p1:\/: p2, pe) =
    case quickmerge (p1:\/:p2) of
    p1' -> if checkAPatch (invert p1 :>: p2 :>: p1' :>: invert pe :>: NilFL)
           then succeeded
           else failed $ text $ "Oh no, merger isn't equivalent...\n"++show p1++"\n"++show p2
                 ++"in other words\n" ++ show (p1 :\/: p2)
                 ++"merges as\n" ++ show (merge $ p1 :\/: p2)
                 ++"merges to\n" ++ show (quickmerge $ p1 :\/: p2)
                 ++"which is equivalent to\n" ++ show (effect p1')
                 ++ "should all work out to\n"
                 ++ show pe

checkMergeSwap :: (FL Patch wX wY, FL Patch wX wZ) -> TestResult
checkMergeSwap (p1, p2) =
    case merge (p2:\/:p1) of
    _ :/\: p2' ->
        case merge (p1:\/:p2) of
        _ :/\: p1' ->
            case commute (p1 :> p2') of
            Just (_ :> p1'b) ->
                if not $ p1'b `eqFLUnsafe` p1'
                then failed $ text $ "Merge swapping problem with...\np1 "++
                      show p1++"merged with\np2 "++
                      show p2++"p1' is\np1' "++
                      show p1'++"p1'b is\np1'b  "++
                      show p1'b
                else succeeded
            Nothing -> failed $ text $ "Merge commuting problem with...\np1 "++
                        show p1++"merged with\np2 "++
                        show p2++"gives\np2' "++
                        show p2'++"which doesn't commute with p1.\n"

checkCanon :: forall wX wY . (FL Patch wX wY, FL Patch wX wY) -> TestResult
checkCanon (p1,p2) =
    if isIsEq $ eqFL p1_ p2
    then if isIsEq $ eqFL p1_p p2
         then succeeded
         else failed $ text $ "Canonization with Patience Diff failed:\n"++show p1++"canonized is\n"
               ++show (p1_p :: FL Patch wX wY)
               ++"which is not\n"++show p2
    else failed $ text $ "Canonization with Myers Diff failed:\n"++show p1++"canonized is\n"
          ++show (p1_ :: FL Patch wX wY)
          ++"which is not\n"++show p2
    where p1_ = mapFL_FL fromPrim $ concatFL $ mapFL_FL (canonize D.MyersDiff) $ sortCoalesceFL $ effect p1
          p1_p = mapFL_FL fromPrim $ concatFL $ mapFL_FL (canonize D.PatienceDiff) $ sortCoalesceFL $ effect p1

checkCommute :: ((FL Patch :> FL Patch) wX wY, (FL Patch :> FL Patch) wX wY) -> TestResult
checkCommute (p2 :> p1,p1' :> p2') =
   case commute (p2 :> p1) of
   Just (p1a :> p2a) ->
       if (p1a :> p2a) == (p1' :> p2')
       then succeeded
       else failed $ text $ "Commute gave wrong value!\n"++show p1++"\n"++show p2
             ++"should be\n"++show p2'++"\n"++show p1'
             ++"but is\n"++show p2a++"\n"++show p1a
   Nothing -> failed $ text $ "Commute failed!\n"++show p1++"\n"++show p2
   <&&>
   case commute (p1' :> p2') of
   Just (p2a :> p1a) ->
       if (p2a :> p1a) == (p2 :> p1)
       then succeeded
       else failed $ text $ "Commute gave wrong value!\n"++show p2a++"\n"++show p1a
             ++"should have been\n"++show p2'++"\n"++show p1'
   Nothing -> failed $ text $ "Commute failed!\n"++show p2'++"\n"++show p1'

checkCantCommute :: (FL Patch :> FL Patch) wX wY -> TestResult
checkCantCommute (p2 :> p1) =
    case commute (p2 :> p1) of
    Nothing -> succeeded
    _ -> failed $ text $ show p1 ++ "\n\n" ++ show p2 ++
          "\nArgh, these guys shouldn't commute!\n"

-- ----------------------------------------------------------------------------
-- A few "test" properties, doing things with input patches and giving a OK/not
-- OK type of answer.

tShowRead :: (Show2 p, ReadPatch p, ShowPatchBasic p)
          => (forall wX wY wW wZ . p wX wY -> p wW wZ -> Bool) -> forall wX wY . p wX wY -> TestResult
tShowRead eq p =
    case readPatch $ renderPS $ showPatch ForStorage p of
    Just (Sealed p') -> if p' `eq` p then succeeded
                        else failed $ text $ "Failed to read shown:  "++(show2 p)++"\n"
    Nothing -> failed $ text $ "Failed to read at all:  "++(show2 p)++"\n"

tMergeEitherWayValid :: forall wX wY p . (Check p, Show2 p, Merge p, Invert p) => (p :\/: p) wX wY -> TestResult
tMergeEitherWayValid (p1 :\/: p2) =
  case p2 :>: quickmerge (p1:\/: p2) :>: NilFL of
  combo2 ->
    case p1 :>: quickmerge (p2:\/: p1) :>: NilFL of
    combo1 ->
      if not $ checkAPatch combo1
      then failed $ text $ "oh my combo1 invalid:\n"++show2 p1++"and...\n"++show2 p2++show combo1
      else
        if checkAPatch (invert combo1 :>: combo2 :>: NilFL)
        then succeeded
        else failed $ text $ "merge both ways invalid:\n"++show2 p1++"and...\n"++show2 p2++
              show combo1++
              show combo2

tTestCheck :: forall wX wY . FL Patch wX wY -> TestResult
tTestCheck p = if checkAPatch p
                 then succeeded
                 else failed $ text $ "Failed the check:  "++show p++"\n"
