{-# LANGUAGE LambdaCase      #-}
{-# LANGUAGE RecordWildCards #-}
{- |
Module      :  Neovim.Plugin.ConfigHelper.Internal
Description :  Internals for a config helper plugin that helps recompiling nvim-hs
Copyright   :  (c) Sebastian Witte
License     :  Apache-2.0

Maintainer  :  woozletoff@gmail.com
Stability   :  experimental
Portability :  GHC

-}
module Neovim.Plugin.ConfigHelper.Internal
    where

import           Neovim.API.String       (vim_command)
import           Neovim.Config
import           Neovim.Context
import           Neovim.Plugin.Classes
import           Neovim.Quickfix
import           Neovim.Util             (withCustomEnvironment)

import           Neovim.Compat.Megaparsec as P hiding (count)

import           Config.Dyre             (Params)
import           Config.Dyre.Compile
import           Config.Dyre.Paths       (getPaths)
import           Control.Applicative     hiding (many, (<|>))
import           Control.Monad           (void, forM_)
import           Data.Char
import           System.SetEnv
import           System.Directory        (removeDirectoryRecursive)

import           Prelude


-- | Simple function that will return @"Pong"@ if the plugin provider is
-- running.
pingNvimhs :: Neovim' String
pingNvimhs = return "Pong"


-- | Recompile the plugin provider and put comile errors in the quickfix list.
recompileNvimhs :: Neovim (Params NeovimConfig, [(String, Maybe String)]) [QuickfixListItem String] ()
recompileNvimhs = ask >>= \(cfg,env) -> withCustomEnvironment env $ do
    mErrString <- liftIO (customCompile cfg >> getErrorString cfg)
    let qs = maybe [] parseQuickfixItems mErrString
    put qs
    setqflist qs Replace
    void $ vim_command "cwindow"


-- | Note that restarting the plugin provider implies compilation because Dyre
-- does this automatically. However, if the recompilation fails, the previously
-- compiled binary is executed. This essentially means that restarting may take
-- more time then you might expect.
--
-- If you provide a bang to the command, the cache directory of /nvim-hs/ is
-- forcibly removed.
restartNvimhs :: CommandArguments
              -> Neovim (Params NeovimConfig, [(String, Maybe String)]) [QuickfixListItem String] ()
restartNvimhs CommandArguments{..} = do
    case bang of
        Just True -> do
            (_,_,_, cacheDir,_) <- liftIO . getPaths =<< asks fst
            liftIO $ removeDirectoryRecursive cacheDir

        _ ->
            return ()

    recompileNvimhs

    (_, env) <- ask
    forM_ env $ \(var, val) -> liftIO $ do
        maybe (unsetEnv var) (setEnv var) val
    restart

-- Parsing {{{1
-- See the tests in @test-suite\/Neovim\/Plugin\/ConfigHelperSpec.hs@ on how the
-- error messages look like.
parseQuickfixItems :: String -> [QuickfixListItem String]
parseQuickfixItems s =
    case parse (P.many pQuickfixListItem) "Quickfix parser" s of
        Right qs -> qs
        Left _   -> []


pQuickfixListItem :: P.Parser (QuickfixListItem String)
pQuickfixListItem = do
    _ <- P.many blankLine
    (f,l,c) <- pLocation

    void $ P.many tabOrSpace
    e <- pSeverity
    desc <- try pShortDesrciption <|> pLongDescription
    return $ (quickfixListItem (Right f) (Left l))
        { col = Just (c, True)
        , text = desc
        , errorType = e
        }

pSeverity :: P.Parser QuickfixErrorType
pSeverity = do
    try (string "Warning:" *> return Warning)
    <|> try (string "error:"   *> return Error)
    <|> return Error

pShortDesrciption :: P.Parser String
pShortDesrciption = (:)
    <$> (notFollowedBy blankLine *> anyChar)
    <*> anyChar `manyTill` (void (P.some blankLine) <|> eof)


pLongDescription :: P.Parser String
pLongDescription = anyChar `manyTill` (blank <|> eof)
  where
    blank = try (try newline *> try blankLine)


tabOrSpace :: P.Parser Char
tabOrSpace = satisfy $ \c -> c == ' ' || c == '\t'


blankLine :: P.Parser ()
blankLine = void .try $ P.many tabOrSpace >> newline


-- | Skip anything until the next location information appears.
--
-- The result will be a triple of filename, line number and column

-- | Try to parse location information.
--
-- @\/some\/path\/to\/a\/file.hs:42:88:@
pLocation :: P.Parser (String, Int, Int)
pLocation = (,,)
    <$> P.some (noneOf ":\n\t\r") <* char ':'
    <*> pInt <* char ':'
    <*> pInt <* char ':' <* P.many tabOrSpace


pInt :: P.Parser Int
pInt = read <$> P.some (satisfy isDigit)
-- 1}}}