module Darcs.Repository.Unrevert
    ( finalizeTentativeUnrevert
    , revertTentativeUnrevert
    , writeUnrevert
    , readUnrevert
    , removeFromUnrevertContext
    ) where

import Darcs.Prelude

import Darcs.Patch ( PrimOf, RepoPatch, commuteRL )
import Darcs.Patch.Apply ( ApplyState )
import Darcs.Patch.Bundle ( interpretBundle, makeBundle, parseBundle )
import Darcs.Patch.Depends ( patchSetMerge, removeFromPatchSet )
import Darcs.Patch.Info ( patchinfo )
import Darcs.Patch.Named ( infopatch )
import Darcs.Patch.PatchInfoAnd ( PatchInfoAnd, hopefully )
import Darcs.Patch.Set ( Origin, PatchSet, SealedPatchSet )
import Darcs.Patch.Witnesses.Ordered
    ( (:/\:)(..)
    , (:>)(..)
    , FL(..)
    , lengthFL
    , reverseFL
    )
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed) )
import Darcs.Repository.Paths ( tentativeUnrevertPath, unrevertPath )
import Darcs.Util.Exception ( catchDoesNotExistError, ifDoesNotExistError )
import Darcs.Util.Global ( debugMessage )
import Darcs.Util.IsoDate ( getIsoDateTime )
import Darcs.Util.Lock ( readBinFile, removeFileMayNotExist, writeDocBinFile )
import Darcs.Util.Prompt ( promptYorn )
import Darcs.Util.Tree ( Tree )

import System.Directory ( copyFile, renameFile )
import System.Exit ( exitSuccess )

finalizeTentativeUnrevert :: IO ()
finalizeTentativeUnrevert =
  renameFile tentativeUnrevertPath unrevertPath `catchDoesNotExistError`
    removeFileMayNotExist unrevertPath

revertTentativeUnrevert :: IO ()
revertTentativeUnrevert =
  copyFile unrevertPath tentativeUnrevertPath `catchDoesNotExistError`
    removeFileMayNotExist tentativeUnrevertPath

writeUnrevert :: (RepoPatch p, ApplyState p ~ Tree)
              => PatchSet p Origin wR
              -> FL (PrimOf p) wR wX
              -> IO ()
writeUnrevert _ NilFL = removeFileMayNotExist tentativeUnrevertPath
writeUnrevert recorded ps = do
  date <- getIsoDateTime
  info <- patchinfo date "unrevert" "anon" []
  let np = infopatch info ps
  bundle <- makeBundle Nothing recorded (np :>: NilFL)
  writeDocBinFile tentativeUnrevertPath bundle

readUnrevert :: RepoPatch p
             => PatchSet p Origin wR
             -> IO (SealedPatchSet p Origin)
readUnrevert us = do
  pf <- readBinFile tentativeUnrevertPath
        `catchDoesNotExistError` fail "There's nothing to unrevert!"
  case parseBundle pf of
      Right (Sealed bundle) -> do
        case interpretBundle us bundle of
          Left msg -> fail msg
          Right ps -> return (Sealed ps)
      Left err -> fail $ "Couldn't parse unrevert patch:\n" ++ err

removeFromUnrevertContext :: forall p wR wX. (RepoPatch p, ApplyState p ~ Tree)
                          => PatchSet p Origin wR
                          -> FL (PatchInfoAnd p) wX wR
                          -> IO ()
removeFromUnrevertContext _ NilFL = return () -- nothing to do
removeFromUnrevertContext ref ps =
  ifDoesNotExistError () $ do
    debugMessage "Reading the unrevert bundle..."
    Sealed bundle <- unrevert_patch_bundle
    debugMessage "Adjusting the context of the unrevert changes..."
    debugMessage $
      "Removing " ++ show (lengthFL ps) ++ " patches in removeFromUnrevertContext"
    Sealed bundle_ps <- bundle_to_patchset bundle
    case patchSetMerge ref bundle_ps of
      (unrevert :>: NilFL) :/\: _ -> do
        case commuteRL (reverseFL ps :> unrevert) of
          Nothing -> unrevert_impossible
          Just (unrevert' :> _) ->
            case removeFromPatchSet ps ref of
              Nothing -> unrevert_impossible
              Just common -> do
                debugMessage "Have now found the new context..."
                bundle' <- makeBundle Nothing common (hopefully unrevert' :>: NilFL)
                writeDocBinFile tentativeUnrevertPath bundle'
      _ -> return () -- TODO I guess this should be an error call
    debugMessage "Done adjusting the context of the unrevert changes"
  where
    unrevert_impossible = do
      confirmed <-
        promptYorn "This operation will make unrevert impossible!\nProceed?"
      if confirmed
        then removeFileMayNotExist tentativeUnrevertPath
        else putStrLn "Cancelled." >> exitSuccess
    unrevert_patch_bundle = do
      pf <- readBinFile tentativeUnrevertPath
      case parseBundle pf of
        Right foo -> return foo
        Left err -> fail $ "Couldn't parse unrevert patch:\n" ++ err
    bundle_to_patchset bundle =
      either fail (return . Sealed) $ interpretBundle ref bundle
