--  Copyright (C) 2003-2005 David Roundy
--
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2, or (at your option)
--  any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program; see the file COPYING.  If not, write to
--  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
--  Boston, MA 02110-1301, USA.

module Darcs.UI.Commands.Unrevert ( unrevert ) where

import Darcs.Prelude

import Control.Monad ( unless, when, void )

import Darcs.Patch ( commute )
import Darcs.Patch.Depends ( findCommon )
import Darcs.Patch.Witnesses.Ordered ( (:>)(..), FL(..), (+>+) )
import Darcs.Patch.Witnesses.Sealed ( Sealed(Sealed) )
import Darcs.Repository
    ( RepoJob(..)
    , applyToWorking
    , considerMergeToWorking
    , finalizeRepositoryChanges
    , readPatches
    , addToPending
    , unrecordedChanges
    , withRepoLock
    )
import Darcs.Repository.Flags
    ( AllowConflicts(..)
    , ResolveConflicts(..)
    , Reorder(..)
    , WantGuiPause(..)
    )
import Darcs.Repository.Unrevert ( readUnrevert, writeUnrevert )
import Darcs.UI.Commands
    ( DarcsCommand(..)
    , amInHashedRepository
    , nodefaults
    , putFinished
    , withStdOpts
    )
import Darcs.UI.Completion ( noArgs )
import Darcs.UI.Flags
    ( diffingOpts
    , isInteractive
    , umask
    , useCache
    , verbosity
    )
import Darcs.UI.Flags ( DarcsFlag )
import Darcs.UI.Options ( (?), (^) )
import qualified Darcs.UI.Options.All as O
import Darcs.UI.SelectChanges
    ( WhichChanges(First)
    , runInvertibleSelection
    , selectionConfigPrim
    )
import qualified Darcs.UI.SelectChanges as S ( PatchSelectionOptions(..) )
import Darcs.Util.Path ( AbsolutePath )
import Darcs.Util.Printer ( Doc, text )
import Darcs.Util.Progress ( debugMessage )
import Darcs.Util.Prompt ( promptYorn )
import Darcs.Util.SignalHandler ( withSignalsBlocked )

unrevertDescription :: String
unrevertDescription =
 "Undo the last revert."

unrevertHelp :: Doc
unrevertHelp = text $
 "Unrevert is a rescue command in case you accidentally reverted\n" ++
 "something you wanted to keep (for example, typing `darcs rev -a`\n" ++
 "instead of `darcs rec -a`).\n" ++
 "\n" ++
 "This command may fail if the repository has changed since the revert\n" ++
 "took place.  Darcs will ask for confirmation before executing an\n" ++
 "interactive command that will DEFINITELY prevent unreversion.\n"

patchSelOpts :: [DarcsFlag] -> S.PatchSelectionOptions
patchSelOpts flags = S.PatchSelectionOptions
    { S.verbosity = verbosity ? flags
    , S.matchFlags = []
    , S.interactive = isInteractive True flags
    , S.selectDeps = O.PromptDeps -- option not supported, use default
    , S.withSummary = O.NoSummary -- option not supported, use default
    }

unrevert :: DarcsCommand
unrevert = DarcsCommand
    { commandProgramName = "darcs"
    , commandName = "unrevert"
    , commandHelp = unrevertHelp
    , commandDescription = unrevertDescription
    , commandExtraArgs = 0
    , commandExtraArgHelp = []
    , commandCommand = unrevertCmd
    , commandPrereq = amInHashedRepository
    , commandCompleteArgs = noArgs
    , commandArgdefaults = nodefaults
    , commandOptions = unrevertOpts
    }
  where
    unrevertBasicOpts
      = O.interactive -- True
      ^ O.repoDir
      ^ O.diffAlgorithm
    unrevertAdvancedOpts = O.umask
    unrevertOpts = unrevertBasicOpts `withStdOpts` unrevertAdvancedOpts

unrevertCmd :: (AbsolutePath, AbsolutePath) -> [DarcsFlag] -> [String] -> IO ()
unrevertCmd _ opts [] =
 withRepoLock (useCache ? opts) (umask ? opts) $ RepoJob $ \_repository -> do
  us <- readPatches _repository
  Sealed them <- readUnrevert us
  unrecorded <- unrecordedChanges (diffingOpts opts) _repository Nothing
  Sealed pw <- considerMergeToWorking _repository "unrevert"
                      (YesAllowConflicts MarkConflicts)
                      NoWantGuiPause
                      NoReorder
                      (diffingOpts opts)
                      (findCommon us them)
  let selection_config =
        selectionConfigPrim
            First "unrevert" (patchSelOpts opts)
            Nothing Nothing
  (to_unrevert :> to_keep) <- runInvertibleSelection pw selection_config
  addToPending _repository (diffingOpts opts) to_unrevert
  recorded <- readPatches _repository
  debugMessage "I'm about to writeUnrevert."
  case commute ((unrecorded +>+ to_unrevert) :> to_keep) of
    Nothing -> do
      yes <- promptYorn "You will not be able to undo this operation! Proceed?"
      when yes $ writeUnrevert recorded NilFL -- i.e. remove unrevert
    Just (to_keep' :> _) -> writeUnrevert recorded to_keep'
  withSignalsBlocked $ do
    _repository <- finalizeRepositoryChanges _repository (O.dryRun ? opts)
    unless (O.yes (O.dryRun ? opts)) $
      void $ applyToWorking _repository (verbosity ? opts) to_unrevert
  putFinished opts "unreverting"
unrevertCmd _ _ _ = error "impossible case"
