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

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

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

import           Control.Applicative
import           Control.Lens
import           Control.Monad
import           Data.Foldable (find)
import           Data.List.NonEmpty (NonEmpty(..))
import           Data.Monoid
import qualified Data.Text as T
import qualified Text.ParserCombinators.Parsec as P
import           Yi.Buffer
import           Yi.Core (quitEditor, errorEditor, closeWindow)
import           Yi.Editor
import           Yi.File
import           Yi.Keymap
import           Yi.Keymap.Vim.Common
import qualified Yi.Keymap.Vim.Ex.Commands.Common as Common
import           Yi.Keymap.Vim.Ex.Types
import           Yi.Monad
import           Yi.String (showT)
import           Yi.Window (bufkey)


parse :: EventString -> Maybe ExCommand
parse = Common.parse $ P.choice
    [ do
        _ <- (P.try ( P.string "xit") <|> P.string "x")
        bangs <- P.many (P.char '!')
        return (quit True (not $ null bangs) False)
    , do
        ws <- P.many (P.char 'w')
        void $ P.try ( P.string "quit") <|> P.string "q"
        as <- P.many (P.try ( P.string "all") <|> P.string "a")
        bangs <- P.many (P.char '!')
        return $! quit (not $ null ws) (not $ null bangs) (not $ null as)
    ]

quit :: Bool -> Bool -> Bool -> ExCommand
quit w f a = Common.impureExCommand {
    cmdShow = (if w then "w" else "")
              `T.append` "quit"
              `T.append` (if a then "all" else "")
              `T.append` (if f then "!" else "")
  , cmdAction = YiA $ action w f a
  }

action :: Bool -> Bool -> Bool -> YiM ()
action False False False = quitWindowE
action False False  True = quitAllE
action  True False False = viWrite >> closeWindow
action  True False  True = saveAndQuitAllE
action False  True False = closeWindow
action False  True  True = quitEditor
action  True  True False = viWrite >> closeWindow
action  True  True  True = saveAndQuitAllE

quitWindowE :: YiM ()
quitWindowE = do
    nw <- withCurrentBuffer needsAWindowB
    ws <- withEditor $ use currentWindowA >>= windowsOnBufferE . bufkey
    if length ws == 1 && nw
       then errorEditor "No write since last change (add ! to override)"
       else closeWindow

quitAllE :: YiM ()
quitAllE = do
  a :| as <- readEditor bufferStack
  let needsWindow b = (b,) <$> withEditor (withGivenBuffer b needsAWindowB)
  bs <- mapM needsWindow (a:as)
  -- Vim only shows the first modified buffer in the error.
  case find snd bs of
      Nothing -> quitEditor
      Just (b, _) -> do
          bufferName <- withEditor $ withGivenBuffer b $ gets file
          errorEditor $ "No write since last change for buffer "
                        <> showT bufferName
                        <> " (add ! to override)"

saveAndQuitAllE :: YiM ()
saveAndQuitAllE = Common.forAllBuffers fwriteBufferE >> quitEditor

needsAWindowB :: BufferM Bool
needsAWindowB = do
  isWorthless <- gets (^. identA) >>= return . \case
    MemBuffer _ -> True
    FileBuffer _ -> False
  canClose <- gets isUnchangedBuffer
  return (not (isWorthless || canClose))