    {-# LANGUAGE OverloadedStrings, RecordWildCards #-}
    {-|

    This module is used by the 'balancesheet', 'incomestatement', and
    'cashflow' commands to print out acocunt balances based on a specific
    "view", which consists of a title and multiple named queries that are
    aggregated and totaled.

    -}

    module Hledger.Cli.BalanceView (
      BalanceView(..)
     ,balanceviewmode
     ,balanceviewReport
    ) where

    import Control.Monad (unless)
    import Data.List (intercalate, foldl', isPrefixOf)
    import Data.Maybe (fromMaybe)
    import Data.Monoid (Sum(..), (<>))
    import System.Console.CmdArgs.Explicit as C
    import Text.Tabular as T

    import Hledger
    import Hledger.Cli.Balance
    import Hledger.Cli.CliOptions

    -- | Describes a view for the balance, which can consist of multiple
    -- separate named queries that are aggregated and totaled.
    data BalanceView = BalanceView {
          bvmode     :: String,                        -- ^ command line mode of the view
          bvaliases  :: [String],                      -- ^ command line aliases
          bvhelp     :: String,                        -- ^ command line help message
          bvtitle    :: String,                        -- ^ title of the view
          bvqueries  :: [(String, Journal -> Query)],  -- ^ named queries that make up the view
          bvtype     :: BalanceType                    -- ^ the type of balance this view shows.
                                                       --   This overrides user input.
    }

    balanceviewmode :: BalanceView -> Mode RawOpts
    balanceviewmode BalanceView{..} = (defCommandMode $ bvmode : bvaliases) {
      modeHelp = bvhelp `withAliases` bvaliases
     ,modeGroupFlags = C.Group {
         groupUnnamed = [
          flagNone ["change"] (\opts -> setboolopt "change" opts)
            ("show balance change in each period" ++ defType PeriodChange)
         ,flagNone ["cumulative"] (\opts -> setboolopt "cumulative" opts)
            ("show balance change accumulated across periods (in multicolumn reports)"
                ++ defType CumulativeChange
            )
         ,flagNone ["historical","H"] (\opts -> setboolopt "historical" opts)
            ("show historical ending balance in each period (includes postings before report start date)"
                ++ defType HistoricalBalance
            )
         ,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show accounts as a list"
         ,flagReq  ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "flat mode: omit N leading account name parts"
         ,flagNone ["no-total","N"] (\opts -> setboolopt "no-total" opts) "omit the final total row"
         ,flagNone ["tree"] (\opts -> setboolopt "tree" opts) "show accounts as a tree; amounts include subaccounts (default in simple reports)"
         ,flagNone ["average","A"] (\opts -> setboolopt "average" opts) "show a row average column (in multicolumn reports)"
         ,flagNone ["row-total","T"] (\opts -> setboolopt "row-total" opts) "show a row total column (in multicolumn reports)"
         ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't squash boring parent accounts (in tree mode)"
         ,flagReq  ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format (in simple reports)"
         ,flagNone ["pretty-tables"] (\opts -> setboolopt "pretty-tables" opts) "use unicode when displaying tables"
         ]
        ,groupHidden = []
        ,groupNamed = [generalflagsgroup1]
        }
     }
     where
       defType :: BalanceType -> String
       defType bt | bt == bvtype = " (default)"
                  | otherwise    = ""

    balanceviewQueryReport
        :: ReportOpts
        -> Query
        -> Journal
        -> String
        -> (Journal -> Query)
        -> ([String], Sum MixedAmount)
    balanceviewQueryReport ropts q0 j t q = ([view], Sum amt)
        where
          q' = And [q0, q j]
          rep@(_ , amt)
            -- For --historical/--cumulative, we must use multiBalanceReport.
            -- (This forces --no-elide.)
            -- See Balance.hs's implementation of 'balance' for more information
            | balancetype_ ropts `elem` [HistoricalBalance, CumulativeChange]
                = singleBalanceReport ropts q' j
            | otherwise
                = balanceReport ropts q' j
          view = intercalate "\n" [t <> ":", balanceReportAsText ropts rep]

    multiBalanceviewQueryReport
        :: ReportOpts
        -> Query
        -> Journal
        -> String
        -> (Journal -> Query)
        -> ([Table String String MixedAmount], [[MixedAmount]], Sum MixedAmount)
    multiBalanceviewQueryReport ropts q0 j t q = ([tabl], [coltotals], Sum tot)
        where
          singlesection = "Cash" `isPrefixOf` t -- TODO temp
          ropts' = ropts { no_total_ = singlesection && no_total_ ropts, empty_ = True }
          q' = And [q0, q j]
          MultiBalanceReport (dates, rows, (coltotals,tot,avg)) =
              multiBalanceReport ropts' q' j
          rows' | empty_ ropts = rows
                | otherwise    = filter (not . emptyRow) rows
            where
              emptyRow (_,_,_,amts,_,_) = all isZeroMixedAmount amts
          r = MultiBalanceReport (dates, rows', (coltotals, tot, avg))
          Table hLeft hTop dat = balanceReportAsTable ropts' r
          tabl = Table (T.Group SingleLine [Header t, hLeft]) hTop ([]:dat)

    -- | Prints out a balance report according to a given view
    balanceviewReport :: BalanceView -> CliOpts -> Journal -> IO ()
    balanceviewReport BalanceView{..} CliOpts{command_=cmd, reportopts_=ropts, rawopts_=raw} j = do
        currDay   <- getCurrentDay
        let q0 = queryFromOpts currDay ropts'
        let title = bvtitle ++ maybe "" (' ':) balanceclarification
        case interval_ ropts' of
          NoInterval -> do
            let (views, amt) =
                  foldMap (uncurry (balanceviewQueryReport ropts' q0 j))
                     bvqueries
            mapM_ putStrLn (title : "" : views)

            unless (no_total_ ropts' || cmd=="cashflow") . mapM_ putStrLn $ -- TODO temp
              [ "Total:"
              , "--------------------"
              , padleft 20 $ showMixedAmountWithoutPrice (getSum amt)
              ]
          _ -> do
            let (tabls, amts, Sum totsum)
                  = foldMap (uncurry (multiBalanceviewQueryReport ropts' q0 j)) bvqueries
                sumAmts = case amts of
                  a1:as -> foldl' (zipWith (+)) a1 as
                  []    -> []
                totavg  = totsum `divideMixedAmount`
                            fromIntegral (length sumAmts)
                mergedTabl = case tabls of
                  t1:ts -> foldl' merging t1 ts
                  []    -> T.empty
                totTabl
                  | no_total_ ropts' || length bvqueries == 1 =
                      mergedTabl
                  | otherwise =
                      mergedTabl
                      +====+
                      row "Total"
                          (sumAmts ++ (if row_total_ ropts' then [totsum] else [])
                                   ++ (if average_ ropts'   then [totavg] else [])
                          )
            putStrLn title
            putStrLn $ renderBalanceReportTable ropts totTabl
      where
        overwriteBalanceType =
          case reverse $ filter (`elem` ["change","cumulative","historical"]) $ map fst raw of
            "historical":_ -> Just HistoricalBalance
            "cumulative":_ -> Just CumulativeChange
            "change":_     -> Just PeriodChange
            _              -> Nothing
        balancetype = fromMaybe bvtype overwriteBalanceType
        -- we must clarify that the statements aren't actual income statements,
        -- etc. if the user overrides the balance type
        balanceclarification = flip fmap overwriteBalanceType $ \t ->
          case t of
            PeriodChange      -> "(Balance Changes)"
            CumulativeChange  -> "(Cumulative Ending Balances)"
            HistoricalBalance -> "(Historical Ending Balances)"
        ropts' = treeIfNotPeriod $
                   ropts { balancetype_ = balancetype }
        treeIfNotPeriod = case (balancetype, interval_ ropts) of
            -- For --historical/--cumulative, we must use multiBalanceReport.
            -- (This forces --no-elide.)
            -- These settings format the output in a way that we can convert to
            -- a normal balance report using singleBalanceReport.  See
            -- Balance.hs for more information.
          (HistoricalBalance, NoInterval) -> \o -> o { accountlistmode_ = ALTree }
          (CumulativeChange , NoInterval) -> \o -> o { accountlistmode_ = ALTree }
          _                               -> id
        merging (Table hLeft hTop dat) (Table hLeft' _ dat') =
            Table (T.Group DoubleLine [hLeft, hLeft']) hTop (dat ++ dat')