{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK show-extensions #-}

-- |
-- Module      :  Yi.Keymap.Vim.Ex.Commands.Global
-- License     :  GPL-2
-- Maintainer  :  yi-devel@googlegroups.com
-- Stability   :  experimental
-- Portability :  portable

module Yi.Keymap.Vim.Ex.Commands.Global (parse) where

import           Control.Applicative                  (Alternative ((<|>)), (<$>))
import           Control.Lens                         (use)
import           Control.Monad                        (forM_, void, when)
import           Data.Monoid                          ((<>))
import qualified Data.Text                            as T (Text, isInfixOf, pack, snoc)
import qualified Text.ParserCombinators.Parsec        as P (anyChar, char, many, noneOf, string, try)
import           Yi.Buffer.Adjusted
import           Yi.Editor                            (withCurrentBuffer)
import           Yi.Keymap                            (Action (BufferA, EditorA))
import           Yi.Keymap.Vim.Common                 (EventString (Ev))
import qualified Yi.Keymap.Vim.Ex.Commands.Common     as Common (parse, pureExCommand)
import qualified Yi.Keymap.Vim.Ex.Commands.Delete     as Delete (parse)
import qualified Yi.Keymap.Vim.Ex.Commands.Substitute as Substitute (parse)
import           Yi.Keymap.Vim.Ex.Types               (ExCommand (cmdAction, cmdShow), evStringToExCommand)
import qualified Yi.Rope                              as R (toText)
import           Yi.String                            (showT)

parse :: EventString -> Maybe ExCommand
parse = Common.parse $ do
    void $ P.try (P.string "global/") <|> P.string "g/"
    predicate <- T.pack <$> P.many (P.noneOf "/")
    void $ P.char '/'
    cmdString <- Ev . T.pack <$> P.many P.anyChar
    cmd <- case evStringToExCommand allowedCmds cmdString of
            Just c -> return c
            _ -> fail "Unexpected command argument for global command."
    return $! global predicate cmd

global :: T.Text -> ExCommand -> ExCommand
global p c = Common.pureExCommand {
    cmdShow = "g/" <> p `T.snoc` '/' <> showT c
  , cmdAction = EditorA $ do
        mark <- withCurrentBuffer setMarkHereB
        lineCount <- withCurrentBuffer lineCountB
        forM_ (reverse [1..lineCount]) $ \l -> do
            ln <- withCurrentBuffer $ gotoLn l >> R.toText <$> readLnB
            when (p `T.isInfixOf` ln) $
                case cmdAction c of
                    BufferA action -> withCurrentBuffer $ void action
                    EditorA action -> void action
                    _ -> error "Impure command as an argument to global."
        withCurrentBuffer $ do
            use (markPointA mark) >>= moveTo
            deleteMarkB mark
  }

allowedCmds :: [EventString -> Maybe ExCommand]
allowedCmds = [Delete.parse, Substitute.parse]