--  Copyright (C) 2002-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.Commands.Revert ( revert ) where

import Prelude hiding ( catch )

import System.Exit ( ExitCode(..), exitWith )
import Control.Applicative ( (<$>) )
import Control.Exception ( catch, IOException )
import Control.Monad ( when )
import Data.List ( sort )

import English (englishNum, This(..), Noun(..))
import Darcs.Flags( diffingOpts )
import Darcs.Commands ( DarcsCommand(..), nodefaults )
import Darcs.Commands.Util ( announceFiles )
import Darcs.Arguments ( DarcsFlag( All, Debug ),
                        ignoretimes, workingRepoDir,
                        allInteractive,
                        fixSubPaths,
                        listRegisteredFiles, umaskOption, unified
                      )
import Darcs.Utils ( askUser )
import Darcs.RepoPath ( toFilePath )
import Darcs.Repository ( withRepoLock, RepoJob(..), withGutsOf,
                    addToPending,
                    applyToWorking,
                    amInHashedRepository, readRecorded,
                    unrecordedChanges
                  )
import Darcs.Patch ( invert, effectOnFilePaths, commute )
import Darcs.Patch.Split ( reversePrimSplitter )
import Darcs.Witnesses.Ordered ( FL(..), (:>)(..), lengthFL, nullFL, (+>+) )
import Darcs.SelectChanges ( selectChanges, WhichChanges(Last), selectionContextPrim, runSelection )
import Darcs.Patch.TouchesFiles ( chooseTouching )
import Darcs.Commands.Unrevert ( writeUnrevert )
import Darcs.Witnesses.Sealed ( Sealed(..) )

#include "gadts.h"

revertDescription :: String
revertDescription = "Discard unrecorded changes."

revertHelp :: String
revertHelp =
 "The `darcs revert' command discards unrecorded changes the working\n" ++
 "tree.  As with `darcs record', you will be asked which hunks (changes)\n" ++
 "to revert.  The --all switch can be used to avoid such prompting. If\n" ++
 "files or directories are specified, other parts of the working tree\n" ++
 "are not reverted.\n" ++
 "\n" ++
 "In you accidentally reverted something you wanted to keep (for\n" ++
 "example, typing `darcs rev -a' instead of `darcs rec -a'), you can\n" ++
 "immediately run `darcs unrevert' to restore it.  This is only\n" ++
 "guaranteed to work if the repository has not changed since `darcs\n" ++
 "revert' ran.\n"

revert :: DarcsCommand
revert = DarcsCommand {commandProgramName = "darcs",
                       commandName = "revert",
                       commandHelp = revertHelp,
                       commandDescription = revertDescription,
                       commandExtraArgs = -1,
                       commandExtraArgHelp = ["[FILE or DIRECTORY]..."],
                       commandCommand = revertCmd,
                       commandPrereq = amInHashedRepository,
                       commandGetArgPossibilities = listRegisteredFiles,
                       commandArgdefaults = nodefaults,
                       commandAdvancedOptions = [ignoretimes, umaskOption],
                       commandBasicOptions = [allInteractive, unified,
                                               workingRepoDir]}

revertCmd :: [DarcsFlag] -> [String] -> IO ()
revertCmd opts args = withRepoLock opts $ RepoJob $ \repository -> do
  files <- if null args then return Nothing
    else Just . sort <$> fixSubPaths opts args
  announceFiles files "Reverting changes in"
  changes <- unrecordedChanges (diffingOpts opts {- always ScanKnown here -}) repository files
  let pre_changed_files = effectOnFilePaths (invert changes) . map toFilePath <$> files
  rec <- readRecorded repository
  Sealed touching_changes <- return (chooseTouching pre_changed_files changes)
  (case touching_changes of
    NilFL -> putStrLn "There are no changes to revert!"
    _ -> do
      let context = selectionContextPrim "revert" opts (Just reversePrimSplitter) pre_changed_files (Just rec)
      (norevert:>p) <- runSelection (selectChanges Last changes) context
      if nullFL p
       then putStrLn $ "If you don't want to revert after all," ++
                        " that's fine with me!"
       else do
             let theseChanges = englishNum (lengthFL p) . This . Noun $ "change"
             yorn <- if All `elem` opts
                     then return "y"
                     else askUser $ "Do you really want to revert " ++ theseChanges "? "
             case yorn of ('y':_) -> return ()
                          _ -> exitWith $ ExitSuccess
             withGutsOf repository $ do
                 addToPending repository $ invert p
                 when (Debug `elem` opts) $ putStrLn "About to write the unrevert file."
                 case commute (norevert:>p) of
                   Just (p':>_) -> writeUnrevert repository p' rec NilFL
                   Nothing -> writeUnrevert repository (norevert+>+p) rec NilFL
                 when (Debug `elem` opts) $ putStrLn "About to apply to the working directory."
                 _ <- applyToWorking repository opts (invert p) `catch` \(e :: IOException) ->
                     fail ("Unable to apply inverse patch!" ++ show e)
                 return ()) :: IO ()
  putStrLn "Finished reverting."

