-- | This module provides a variant of 'System.Console.GetOpt.usageInfo'.
--
--  Unlike the standard @usageInfo@ function, lists of long switches are broken
--  across multiple lines to economise on columns. For example,
--
--  @
--    -r  --recursive           add contents of subdirectories
--        --not-recursive,
--        --no-recursive        don't add contents of subdirectories
--  @

{-# LANGUAGE OverloadedStrings #-}
module Darcs.UI.Usage
    ( usageInfo
    , formatOptions
    , getCommandHelp
    , getCommandMiniHelp
    , usage
    , subusage
    ) where

import Prelude ()
import Darcs.Prelude

import Data.Functor.Compose
import System.Console.GetOpt( OptDescr(..), ArgDescr(..) )
import Darcs.UI.Options.All ( stdCmdActions )
import Darcs.UI.Commands
    ( CommandControl(..)
    , DarcsCommand(..)
    , wrappedCommandName
    , wrappedCommandDescription
    , getSubcommands
    , commandAlloptions
    )
import Darcs.UI.Options ( DarcsOptDescr, odesc )
import Darcs.Util.Printer
    ( Doc, text, vsep, ($$), vcat, hsep
    , renderString
    )

formatOptions :: [DarcsOptDescr a] -> [String]
formatOptions optDescrs = table
   where (ss,ls,ds)     = (unzip3 . concatMap fmtOpt) optDescrs
         table          = zipWith3 paste
                            shortPadded
                            (zipWith (++) (map (unlines' . init) ls)
                                          (sameLen $ map last ls))
                            ds
         shortPadded    = sameLen ss
         prePad         = replicate (3 + length (head shortPadded)) ' '
         -- Similar to unlines (additional ',' and padding):
         unlines'       = concatMap (\x -> x ++ ",\n" ++ prePad)
         -- Unchanged:
         paste x y z    = "  " ++ x ++ " " ++ y ++ "  " ++ z
         sameLen xs     = flushLeft ((maximum . map length) xs) xs
         flushLeft n xs = [ take n (x ++ repeat ' ') | x <- xs ]

-- | Variant of 'System.Console.GetOpt.usageInfo'.
-- Return a string describing the usage of a command, derived from the header
-- (first argument) and the options described by the second argument.
--
-- Sequences of long switches are presented on separate lines.
usageInfo :: String         -- header
          -> [DarcsOptDescr a]    -- option descriptors
          -> String          -- nicely formatted decription of options
usageInfo header optDescrs = unlines (header:formatOptions optDescrs)

-- Mild variant of the standard definition: 'losFmt' is a list rather than a
-- comma separated string.
fmtOpt :: DarcsOptDescr a -> [(String,[String],String)]
fmtOpt (Compose (Option sos los ad descr)) =
   case lines descr of
     []     -> [(sosFmt,losFmt,"")]
     (d:ds) ->  (sosFmt,losFmt,d) : [ ("",[],d') | d' <- ds ]
   where endBy _  []     = ""
         endBy ch [x]    = x ++ [ch]
         endBy ch (x:xs) = x ++ ch:' ':endBy ch xs
         sosFmt = endBy ',' (map fmtShort sos)
         losFmt = map (fmtLong ad) los

--------------------------------------------------------------------------------
-- Verbatim copies: these definitions aren't exported by System.Console.GetOpt
--------------------------------------------------------------------------------

fmtShort :: Char -> String
fmtShort so = "-" ++ [so]

fmtLong :: ArgDescr a -> String -> String
fmtLong (NoArg  _   ) lo = "--" ++ lo
fmtLong (ReqArg _ ad) lo = "--" ++ lo ++ "=" ++ ad
fmtLong (OptArg _ ad) lo = "--" ++ lo ++ "[=" ++ ad ++ "]"
--------------------------------------------------------------------------------

usage :: [CommandControl] -> Doc
usage cs = vsep 
    [ "Usage: darcs COMMAND ..."
    , "Commands:" $$ usageHelper cs
    , vcat
      [ "Use 'darcs COMMAND --help' for help on a single command."
      , "Use 'darcs --version' to see the darcs version number."
      , "Use 'darcs --exact-version' to see a detailed darcs version."
      , "Use 'darcs help patterns' for help on patch matching."
      , "Use 'darcs help environment' for help on environment variables."
      , "Use 'darcs help manpage' to display help in the manpage format."
      , "Use 'darcs help markdown' to display help in the markdown format."
      ]
    , "Check bug reports at http://bugs.darcs.net/"
    ]

subusage :: DarcsCommand pf -> String
subusage super = renderString $ vsep
    [ header
    , subcommandsHelp
    , vcat $ map text $ formatOptions $ odesc stdCmdActions
    , text $ commandHelp super
    ]
  where
    usageHelp = hsep $ map text
        [ "Usage:"
        , commandProgramName super
        , commandName super
        , "SUBCOMMAND ..."
        ]
    header = usageHelp $$ text (commandDescription super)
    subcommandsHelp = case getSubcommands super of
        [] -> mempty
        subcommands -> usageHelper subcommands

usageHelper :: [CommandControl] -> Doc
usageHelper xs = vsep (groups xs)
  where
    groups [] = []
    groups (HiddenCommand _:cs) = groups cs
    groups (GroupName n:cs) =
      mempty : case groups cs of
        [] -> [text n]
        (g:gs) -> (text n $$ g) : gs
    groups (CommandData c:cs) =
      case groups cs of
        [] -> [cmdHelp c]
        (g:gs) -> (cmdHelp c $$ g) : gs

    cmdHelp c = text $ "  " ++
      padSpaces maxwidth (wrappedCommandName c) ++
      wrappedCommandDescription c

    padSpaces n s = s ++ replicate (n - length s) ' '

    maxwidth = maximum $ 15 : (map cwidth xs)

    cwidth (CommandData c) = length (wrappedCommandName c) + 2
    cwidth _               = 0

getCommandMiniHelp :: Maybe (DarcsCommand pf1) -> DarcsCommand pf2 -> String
getCommandMiniHelp msuper cmd = renderString $ vsep
    [ getCommandHelpCore msuper cmd
    , hsep $ map text
        [ "See"
        , commandProgramName cmd
        , "help"
        , maybe "" ((++ " ") . commandName) msuper ++ commandName cmd
        , "for details."
        ]
    ]

getCommandHelp :: Maybe (DarcsCommand pf1) -> DarcsCommand pf2 -> Doc
getCommandHelp msuper cmd = vsep
    [ getCommandHelpCore msuper cmd
    , subcommandsHelp
    , withHeading "Options:" basicOptionsHelp
    , withHeading "Advanced options:" advancedOptionsHelp
    , text $ commandHelp cmd
    ]
  where
    withHeading _ [] = mempty
    withHeading h ls = vcat (text h : map text ls)

    (basic, advanced) = commandAlloptions cmd
    -- call formatOptions with combined options so that
    -- both get the same formatting
    (basicOptionsHelp, advancedOptionsHelp) =
        splitAt (length basic) $ formatOptions (basic ++ advanced)

    subcommandsHelp =
      case msuper of
        Nothing ->
          case getSubcommands cmd of
            [] -> mempty
            subcommands -> usageHelper subcommands
        -- we don't want to list subcommands if we're already specifying them
        Just _ -> mempty

getCommandHelpCore :: Maybe (DarcsCommand pf1) -> DarcsCommand pf2 -> Doc
getCommandHelpCore msuper cmd = vcat
    [ hsep $
        [ "Usage:"
        , text $ commandProgramName cmd
        , maybe mempty (text . commandName) msuper
        , text $ commandName cmd
        , "[OPTION]..."
        ]
        ++ args_help
    , text $ commandDescription cmd
    ]
  where
    args_help = case cmd of
                    (DarcsCommand {}) -> map text $ commandExtraArgHelp cmd
                    _ -> []
