{-|

The @accounts@ command lists account names:

- in flat mode (default), it lists the full names of accounts posted to by matched postings,
  clipped to the specified depth, possibly with leading components dropped.

- in tree mode, it shows the indented short names of accounts posted to by matched postings,
  and their parents, to the specified depth.

-}

{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}

module Hledger.Cli.Commands.Accounts (
  accountsmode
 ,accounts
) where

#if !(MIN_VERSION_base(4,11,0))
import Data.Monoid
#endif
import Data.List
import qualified Data.Text as T
import qualified Data.Text.IO as T
import System.Console.CmdArgs.Explicit as C

import Hledger
import Hledger.Cli.CliOptions


-- | Command line options for this command.
accountsmode = (defCommandMode $ ["accounts"] ++ aliases) {
  modeHelp = "show account names" `withAliases` aliases
 ,modeHelpSuffix = [
     "This command lists account names, either declared with account directives"
    ,"(--declared), posted to (--used), or both (default)."
    ,"With query arguments, only matched account names and account names"
    ,"referenced by matched postings are shown."
    ,"It shows a flat list by default. With `--tree`, it uses indentation to"
    ,"show the account hierarchy."
    ,"In flat mode you can add `--drop N` to omit the first few account name components."
    ,"Account names can be depth-clipped with `--depth N` or depth:N."
   ]
 ,modeGroupFlags = C.Group {
     groupUnnamed = [
      flagNone ["declared"] (\opts -> setboolopt "declared" opts) "show account names declared with account directives"
     ,flagNone ["used"] (\opts -> setboolopt "used" opts) "show account names referenced by transactions"
     ,flagNone ["tree"] (\opts -> setboolopt "tree" opts) "show short account names, as a tree"
     ,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, as a list (default)"
     ,flagReq  ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "flat mode: omit N leading account name parts"
     ]
    ,groupHidden = []
    ,groupNamed = [generalflagsgroup1]
    }
 }
  where aliases = ["a"]

-- | The accounts command.
accounts :: CliOpts -> Journal -> IO ()
accounts CliOpts{rawopts_=rawopts, reportopts_=ropts} j = do

  -- 1. identify the accounts we'll show
  d <- getCurrentDay
  let tree     = tree_ ropts
      declared = boolopt "declared" rawopts
      used     = boolopt "used"     rawopts
      q        = queryFromOpts d ropts
      -- a depth limit will clip and exclude account names later, but we don't want to exclude accounts at this stage
      nodepthq = dbg1 "nodepthq" $ filterQuery (not . queryIsDepth) q
      -- just the acct: part of the query will be reapplied later, after clipping
      acctq    = dbg1 "acctq" $ filterQuery queryIsAcct q
      depth    = dbg1 "depth" $ queryDepth $ filterQuery queryIsDepth q
      matcheddeclaredaccts = dbg1 "matcheddeclaredaccts" $ filter (matchesAccount nodepthq) $ jdeclaredaccounts j
      matchedusedaccts     = dbg5 "matchedusedaccts" $ map paccount $ journalPostings $ filterJournalPostings nodepthq j
      accts                = dbg5 "accts to show" $ -- no need to nub/sort, accountTree will
        if | declared     && not used -> matcheddeclaredaccts
           | not declared && used     -> matchedusedaccts
           | otherwise                -> matcheddeclaredaccts ++ matchedusedaccts

  -- 2. sort them by declaration order and name, at each level of their tree structure
      sortedaccts = sortAccountNamesByDeclaration j tree accts

  -- 3. if there's a depth limit, depth-clip and remove any no longer useful items 
      clippedaccts =
        dbg1 "clippedaccts" $
        filter (matchesAccount acctq) $  -- clipping can leave accounts that no longer match the query, remove such
        nub $                          -- clipping can leave duplicates (adjacent, hopefully)
        filter (not . T.null) $        -- depth:0 can leave nulls
        map (clipAccountName depth) $  -- clip at depth if specified 
        sortedaccts

  -- 4. print what remains as a list or tree, maybe applying --drop in the former case 
  mapM_ (T.putStrLn . render) clippedaccts
    where
      render a
        | tree_ ropts = T.replicate (2 * (accountNameLevel a - 1)) " " <> accountLeafName a
        | otherwise   = accountNameDrop (drop_ ropts) a