{-# language OverloadedStrings #-}

module Yi.Keymap.Vim.Substitution
    ( substituteE
    , substituteConfirmE
    , repeatSubstitutionE
    , repeatSubstitutionFlaglessE
    ) where

import Control.Monad (void)
import Data.Monoid
import Yi.MiniBuffer
import Yi.Keymap (Keymap)
import qualified Yi.Rope as R
import Yi.Regex
import Yi.Buffer
import Yi.Editor
import Yi.Search
import Yi.Keymap.Keys (char, choice, (?>>!))
import Yi.Keymap.Vim.Common
import Yi.Keymap.Vim.StateUtils

substituteE :: Substitution -> BufferM Region -> EditorM ()
substituteE s@(Substitution from to global caseInsensitive confirm) regionB = do
        let opts = if caseInsensitive then [IgnoreCase] else []
        lines' <- withCurrentBuffer $ regionB >>= linesOfRegionB
        regex <- if R.null from
                    then getRegexE
                    else return . (either (const Nothing) Just)
                            . makeSearchOptsM opts . R.toString $ from
        case regex of
            Nothing -> printMsg "No previous search pattern"
            Just regex' -> do
                saveSubstitutionE s
                if confirm
                then substituteConfirmE regex' to global lines'
                else do
                    withCurrentBuffer $ do
                        -- We need to reverse the lines' here so that replacing
                        -- does not effect the regions in question.
                        mapM_ (void . searchAndRepRegion0 regex' to global) (reverse lines')
                        moveToSol

-- | Run substitution in confirm mode
substituteConfirmE :: SearchExp -> R.YiString -> Bool -> [Region] -> EditorM ()
substituteConfirmE regex to global lines' = do
    -- TODO This highlights all matches, even in non-global mode
    -- and could potentially be classified as a bug. Fixing requires
    -- changing the regex highlighting api.
    setRegexE regex
    regions <- withCurrentBuffer $ findMatches regex global lines'
    substituteMatch to 0 False regions

-- | All matches to replace under given flags
findMatches :: SearchExp -> Bool -> [Region] -> BufferM [Region]
findMatches regex global lines' = do
    let f = if global then id else take 1
    concat <$> mapM (fmap f . regexRegionB regex) lines'

-- | Runs a list of matches using itself as a continuation
substituteMatch :: R.YiString -> Int -> Bool -> [Region] -> EditorM ()
substituteMatch _ _ _ [] = resetRegexE
substituteMatch to co autoAll (m:ms) = do
    let m' = offsetRegion co m
    withCurrentBuffer . moveTo $ regionStart m'
    len <- withCurrentBuffer $ R.length <$> readRegionB m'
    let diff = R.length to - len
        tex = "replace with " <> R.toText to <> " (y/n/a/q)?"
    if autoAll
        then do withCurrentBuffer $ replaceRegionB m' to
                substituteMatch to (co + diff) True ms
        else void . spawnMinibufferE tex . const $ askKeymap to co (co + diff) m ms

-- | Offsets a region (to account for a region prior being modified)
offsetRegion :: Int -> Region -> Region
offsetRegion k reg = mkRegion (regionStart reg + k') (regionEnd reg + k')
    where k' = fromIntegral k

-- | Actual choices during confirm mode.
askKeymap :: R.YiString -> Int -> Int -> Region -> [Region] -> Keymap
askKeymap to co co' m ms = choice [
      char 'n' ?>>! cleanUp >> substituteMatch to co False ms
    , char 'a' ?>>! do cleanUp
                       replace
                       substituteMatch to co' True ms
    , char 'y' ?>>! do cleanUp
                       replace
                       substituteMatch to co' False ms
    , char 'q' ?>>! cleanUp >> resetRegexE
    ]
    where cleanUp = closeBufferAndWindowE
          replace = withCurrentBuffer $ replaceRegionB (offsetRegion co m) to

repeatSubstitutionFlaglessE :: Substitution -> EditorM ()
repeatSubstitutionFlaglessE (Substitution from to _ _ _) =
    substituteE (Substitution from to False False False) (regionOfB Line)

repeatSubstitutionE :: Substitution -> EditorM ()
repeatSubstitutionE s = substituteE s (regionOfB Line)