-- | Some CmdParser actions that add predefined commands.
module UI.Butcher.Monadic.BuiltinCommands
  ( addHelpCommand
  , addHelpCommand2
  , addHelpCommandWith
  , addHelpCommandShallow
  , addButcherDebugCommand
  , addShellCompletionCommand
  , addShellCompletionCommand'
  )
where



#include "prelude.inc"
import           Control.Monad.Free
import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS

import qualified Text.PrettyPrint as PP

import           Data.HList.ContainsType

import           UI.Butcher.Monadic.Internal.Types
import           UI.Butcher.Monadic.Internal.Core
import           UI.Butcher.Monadic.Pretty
import           UI.Butcher.Monadic.Param
import           UI.Butcher.Monadic.Interactive

import           System.IO



-- | Adds a proper full help command. To obtain the 'CommandDesc' value, see
-- 'UI.Butcher.Monadic.cmdRunParserWithHelpDesc' or
-- 'UI.Butcher.Monadic.IO.mainFromCmdParserWithHelpDesc'.
--
-- > addHelpCommand = addHelpCommandWith
-- >   (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpShallow)
addHelpCommand :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
addHelpCommand = addHelpCommandWith
  (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpShallow)

-- | Adds a proper full help command. In contrast to 'addHelpCommand',
-- this version is a bit more verbose about available subcommands as it
-- includes their synopses.
--
-- To obtain the 'CommandDesc' value, see
-- 'UI.Butcher.Monadic.cmdRunParserWithHelpDesc' or
-- 'UI.Butcher.Monadic.IO.mainFromCmdParserWithHelpDesc'.
--
-- > addHelpCommand2 = addHelpCommandWith
-- >   (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpDepthOne)
addHelpCommand2 :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
addHelpCommand2 = addHelpCommandWith
  (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpDepthOne)

-- | Adds a proper full help command, using the specified function to turn
-- the relevant subcommand's 'CommandDesc' into a String.
addHelpCommandWith
  :: Applicative f
  => (CommandDesc a -> IO String)
  -> CommandDesc a
  -> CmdParser f (IO ()) ()
addHelpCommandWith f desc = addCmd "help" $ do
  addCmdSynopsis "print help about this command"
  rest <- addParamRestOfInput "SUBCOMMAND(s)" mempty
  addCmdImpl $ do
    let restWords = List.words rest
    let
      descent :: [String] -> CommandDesc a -> CommandDesc a
      descent [] curDesc = curDesc
      descent (w:wr) curDesc =
        case
            List.lookup (Just w) $ Data.Foldable.toList $ _cmd_children curDesc
          of
            Nothing    -> curDesc
            Just child -> descent wr child
    s <- f $ descent restWords desc
    putStrLn s

-- | Adds a help command that prints help for the command currently in context.
--
-- This version does _not_ include further childcommands, i.e. "help foo" will
-- not print the help for subcommand "foo".
--
-- This also yields slightly different output depending on if it is used
-- before or after adding other subcommands. In general 'addHelpCommand'
-- should be preferred.
addHelpCommandShallow :: Applicative f => CmdParser f (IO ()) ()
addHelpCommandShallow = addCmd "help" $ do
  desc <- peekCmdDesc
  _rest <- addParamRestOfInput "SUBCOMMAND(s)" mempty
  addCmdImpl $ do
    let parentDesc = maybe undefined snd (_cmd_mParent desc)
    print $ ppHelpShallow $ parentDesc

-- | Prints the raw CommandDesc structure.
addButcherDebugCommand :: Applicative f => CmdParser f (IO ()) ()
addButcherDebugCommand = addCmd "butcherdebug" $ do
  desc <- peekCmdDesc
  addCmdImpl $ do
    print $ maybe undefined snd (_cmd_mParent desc)

-- | Adds the "completion" command and several subcommands.
--
-- This command can be used in the following manner:
--
-- > $ source <(foo completion bash-script foo)
addShellCompletionCommand
  :: CmdParser Identity (IO ()) () -> CmdParser Identity (IO ()) ()
addShellCompletionCommand mainCmdParser = do
  addCmdHidden "completion" $ do
    addCmdSynopsis "utilites to enable bash-completion"
    addCmd "bash-script" $ do
      addCmdSynopsis "generate a bash script for completion functionality"
      exeName <- addParamString "EXENAME" mempty
      addCmdImpl $ do
        putStr $ completionScriptBash exeName
    addCmd "bash-gen" $ do
      addCmdSynopsis
        "generate possible completions for given input arguments"
      rest <- addParamRestOfInputRaw "REALCOMMAND" mempty
      addCmdImpl $ do
        let (cdesc, remaining, _result) =
              runCmdParserExt Nothing rest mainCmdParser
        let
          compls = shellCompletionWords (inputString rest)
                                        cdesc
                                        (inputString remaining)
        let lastWord =
              reverse $ takeWhile (not . Char.isSpace) $ reverse $ inputString
                rest
        putStrLn $ List.unlines $ compls <&> \case
          CompletionString s  -> s
          CompletionFile      -> "$(compgen -f -- " ++ lastWord ++ ")"
          CompletionDirectory -> "$(compgen -d -- " ++ lastWord ++ ")"
 where
  inputString (InputString s ) = s
  inputString (InputArgs   as) = List.unwords as

-- | Adds the "completion" command and several subcommands
--
-- This command can be used in the following manner:
--
-- > $ source <(foo completion bash-script foo)
addShellCompletionCommand'
  :: (CommandDesc out -> CmdParser Identity (IO ()) ())
  -> CmdParser Identity (IO ()) ()
addShellCompletionCommand' f = addShellCompletionCommand (f emptyCommandDesc)

completionScriptBash :: String -> String
completionScriptBash exeName =
  List.unlines
    $ [ "function _" ++ exeName ++ "()"
      , "{"
      , "  local IFS=$'\\n'"
      , "  COMPREPLY=()"
      , "  local result=$("
      ++ exeName
      ++ " completion bash-gen \"${COMP_WORDS[@]:1}\")"
      , "  for r in ${result[@]}; do"
      , "    local IFS=$'\\n '"
      , "    for s in $(eval echo ${r}); do"
      , "      COMPREPLY+=(${s})"
      , "    done"
      , "  done"
      , "}"
      , "complete -F _" ++ exeName ++ " " ++ exeName
      ]