{-# 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 qualified Data.List.PointedList.Circular as PL
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 <- gets currentBuffer >>= Common.needsSaving
    ws <- withEditor $ use currentWindowA >>= windowsOnBufferE . bufkey
    if length ws == 1 && nw
       then errorEditor "No write since last change (add ! to override)"
       else do
         winCount <- withEditor $ uses windowsA PL.length
         tabCount <- withEditor $ uses tabsA PL.length
         if winCount == 1 && tabCount == 1
            -- if its the last window, quitting will quit the editor
            then quitAllE
            else closeWindow

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

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