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

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(..)
    , commandName
    , commandDescription
    , 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 ]

-- 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 help COMMAND' or 'darcs COMMAND --help' for help on a single command."
      , "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."
      , "Use 'darcs --version' to see the darcs version number."
      , "Use 'darcs --exact-version' to see a detailed darcs version."
      ]
    , "Check bug reports at http://bugs.darcs.net/"
    ]

subusage :: DarcsCommand -> Doc
subusage super = vsep
    [ superUsage super $$ text (commandDescription super)
    , usageHelper (getSubcommands super)
    , "Options:"
    , vcat $ map text $ formatOptions $ odesc stdCmdActions
    , commandHelp super
    ]

superUsage :: DarcsCommand -> Doc
superUsage super = hsep $ map text
    [ "Usage:"
    , commandProgramName super
    , commandName super
    , "SUBCOMMAND [OPTION]..."
    ]

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 (commandName c) ++
      commandDescription c

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

    maxwidth = maximum $ 15 : (map cwidth xs)

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

getCommandMiniHelp :: Maybe DarcsCommand -> DarcsCommand -> 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 -> DarcsCommand -> Doc
getCommandHelp msuper cmd = vsep
    [ getCommandHelpCore msuper cmd
    , subcommandsHelp
    , withHeading "Options:" basicOptionsHelp
    , withHeading "Advanced options:" advancedOptionsHelp
    , 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 -> usageHelper (getSubcommands cmd)
        -- we don't want to list subcommands if we're already specifying them
        Just _ -> mempty

getSuperCommandHelp :: DarcsCommand -> Doc
getSuperCommandHelp super =
  vsep [superUsage super, usageHelper (getSubcommands super), commandHelp super]

getCommandHelpCore :: Maybe DarcsCommand -> DarcsCommand -> 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
                    _ -> []