module Darcs.Test.Patch.Properties.Check ( Check(..), checkAPatch ) where

import Test.QuickCheck
import Control.Applicative
import Control.Monad ( liftM, liftM2, liftM3, liftM4, replicateM )

import Darcs.Patch.Info ( PatchInfo, patchinfo )
import Darcs.Test.Patch.Check ( PatchCheck,
                                checkMove, removeDir, createDir,
                                isValid, insertLine, fileEmpty, fileExists,
                                deleteLine, modifyFile, createFile, removeFile,
                                doCheck, doVerboseCheck, FileContents(..)
                              )
import Darcs.Patch.RegChars ( regChars )
import ByteStringUtils ( linesPS )
import qualified Data.ByteString as B ( ByteString, null, concat )
import qualified Data.ByteString.Char8 as BC ( break, pack )
import Darcs.Patch.FileName ( fn2fp )
import qualified Data.Map as M ( mapMaybe )

import Darcs.Patch ( addfile, adddir, move,
                     hunk, tokreplace, binary,
                     changepref, invert, merge,
                     effect )
import Darcs.Patch.Invert ( Invert )
import Darcs.Patch.V1 ()
import qualified Darcs.Patch.V1.Core as V1 ( Patch(..) )
import Darcs.Patch.V1.Core ( isMerger )
import Darcs.Patch.Prim.V1 ()
import Darcs.Patch.Prim.V1.Core ( Prim(..), DirPatchType(..), FilePatchType(..) )
import Darcs.Witnesses.Ordered
import Darcs.Witnesses.Sealed ( Sealed(Sealed), unseal, mapSeal, Sealed2(..) )
import Darcs.Witnesses.Unsafe

#include "gadts.h"
#include "impossible.h"

type Patch = V1.Patch Prim

class Check p where
   checkPatch :: p C(x y) -> PatchCheck Bool

instance Check p => Check (FL p) where
   checkPatch NilFL = isValid
   checkPatch (p :>: ps) = checkPatch p >> checkPatch ps

checkAPatch :: (Invert p, Check p) => p C(x y) -> Bool
checkAPatch p = doCheck $ do _ <- checkPatch p
                             checkPatch $ invert p

instance Check (V1.Patch Prim) where
   checkPatch p | isMerger p = do
     checkPatch $ effect p
   checkPatch (V1.Merger _ _ _ _) = impossible
   checkPatch (V1.Regrem _ _ _ _) = impossible
   checkPatch (V1.PP p) = checkPatch p

instance Check Prim where

   checkPatch (FP f RmFile) = removeFile $ fn2fp f
   checkPatch (FP f AddFile) =  createFile $ fn2fp f
   checkPatch (FP f (Hunk line old new)) = do
       _ <- fileExists $ fn2fp f
       mapM_ (deleteLine (fn2fp f) line) old
       mapM_ (insertLine (fn2fp f) line) (reverse new)
       isValid
   checkPatch (FP f (TokReplace t old new)) =
       modifyFile (fn2fp f) (tryTokPossibly t old new)
   -- note that the above isn't really a sure check, as it leaves PSomethings
   -- and PNothings which may have contained new...
   checkPatch (FP f (Binary o n)) = do
       _ <- fileExists $ fn2fp f
       mapM_ (deleteLine (fn2fp f) 1) (linesPS o)
       _ <- fileEmpty $ fn2fp f
       mapM_ (insertLine (fn2fp f) 1) (reverse $ linesPS n)
       isValid

   checkPatch (DP d AddDir) = createDir $ fn2fp d
   checkPatch (DP d RmDir) = removeDir $ fn2fp d

   checkPatch (Move f f') = checkMove (fn2fp f) (fn2fp f')
   checkPatch (ChangePref _ _ _) = return True

tryTokPossibly :: String -> String -> String
                -> (Maybe FileContents) -> (Maybe FileContents)
tryTokPossibly t o n = liftM $ \contents ->
        let lines' = M.mapMaybe (liftM B.concat
                                  . tryTokInternal t (BC.pack o) (BC.pack n))
                                (fcLines contents)
        in contents { fcLines = lines' }

tryTokInternal :: String -> B.ByteString -> B.ByteString
                 -> B.ByteString -> Maybe [B.ByteString]
tryTokInternal _ _ _ s | B.null s = Just []
tryTokInternal t o n s =
    case BC.break (regChars t) s of
    (before,s') ->
        case BC.break (not . regChars t) s' of
        (tok,after) ->
            case tryTokInternal t o n after of
            Nothing -> Nothing
            Just rest ->
                if tok == o
                then Just $ before : n : rest
                else if tok == n
                     then Nothing
                     else Just $ before : tok : rest

verboseCheckAPatch :: (Invert p, Check p) => p C(x y) -> Bool
verboseCheckAPatch p = doVerboseCheck $ do checkPatch p

