    {-# LANGUAGE FlexibleInstances   #-}
    {-# LANGUAGE OverloadedStrings   #-}
    {-# LANGUAGE NamedFieldPuns   #-}
    {-# LANGUAGE RecordWildCards     #-}
    {-# LANGUAGE ScopedTypeVariables #-}
    {-|

    Multi-column balance reports, used by the balance command.

    -}

    module Hledger.Reports.MultiBalanceReport (
      MultiBalanceReport,
      MultiBalanceReportRow,

      multiBalanceReport,
      multiBalanceReportWith,

      compoundBalanceReport,
      compoundBalanceReportWith,

      sortRows,
      sortRowsLike,

      -- * Helper functions
      makeReportQuery,
      getPostingsByColumn,
      getPostings,
      startingPostings,
      generateMultiBalanceReport,
      balanceReportTableAsText,

      -- -- * Tests
      tests_MultiBalanceReport
    )
    where

    import Control.Monad (guard)
    import Data.Bifunctor (second)
    import Data.Foldable (toList)
    import Data.List (sortOn, transpose)
    import Data.List.NonEmpty (NonEmpty(..))
    import Data.HashMap.Strict (HashMap)
    import qualified Data.HashMap.Strict as HM
    import Data.Map (Map)
    import qualified Data.Map as M
    import Data.Maybe (fromMaybe, isJust, mapMaybe)
    import Data.Ord (Down(..))
    import Data.Semigroup (sconcat)
    import Data.Set (Set)
    import qualified Data.Set as Set
    import Data.Time.Calendar (fromGregorian)
    import Safe (lastDef, minimumMay)

    import Data.Default (def)
    import qualified Data.Text as T
    import qualified Data.Text.Lazy.Builder as TB
    import qualified Text.Tabular.AsciiWide as Tab

    import Hledger.Data
    import Hledger.Query
    import Hledger.Utils hiding (dbg3,dbg4,dbg5)
    import qualified Hledger.Utils
    import Hledger.Reports.ReportOptions
    import Hledger.Reports.ReportTypes

    -- add a prefix to this function's debug output
    dbg3 :: [Char] -> a -> a
    dbg3 [Char]
    s = let p :: [Char]
    p = [Char]
    "multiBalanceReport" in [Char] -> a -> a
    forall a. Show a => [Char] -> a -> a
    Hledger.Utils.dbg3 ([Char]
    p[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    " "[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    s)
    dbg4 :: [Char] -> a -> a
    dbg4 [Char]
    s = let p :: [Char]
    p = [Char]
    "multiBalanceReport" in [Char] -> a -> a
    forall a. Show a => [Char] -> a -> a
    Hledger.Utils.dbg4 ([Char]
    p[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    " "[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    s)
    dbg5 :: [Char] -> a -> a
    dbg5 [Char]
    s = let p :: [Char]
    p = [Char]
    "multiBalanceReport" in [Char] -> a -> a
    forall a. Show a => [Char] -> a -> a
    Hledger.Utils.dbg5 ([Char]
    p[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    " "[Char] -> [Char] -> [Char]
    forall a. [a] -> [a] -> [a]
    ++[Char]
    s)

    -- | A multi balance report is a kind of periodic report, where the amounts
    -- correspond to balance changes or ending balances in a given period. It has:
    --
    -- 1. a list of each column's period (date span)
    --
    -- 2. a list of rows, each containing:
    --
    --   * the full account name, display name, and display depth
    --
    --   * A list of amounts, one for each column.
    --
    --   * the total of the row's amounts for a periodic report
    --
    --   * the average of the row's amounts
    --
    -- 3. the column totals, and the overall grand total (or zero for
    -- cumulative/historical reports) and grand average.

    type MultiBalanceReport    = PeriodicReport    DisplayName MixedAmount
    type MultiBalanceReportRow = PeriodicReportRow DisplayName MixedAmount

    -- type alias just to remind us which AccountNames might be depth-clipped, below.
    type ClippedAccountName = AccountName

    -- | Generate a multicolumn balance report for the matched accounts,
    -- showing the change of balance, accumulated balance, or historical balance
    -- in each of the specified periods. If the normalbalance_ option is set, it
    -- adjusts the sorting and sign of amounts (see ReportOpts and
    -- CompoundBalanceCommand). hledger's most powerful and useful report, used
    -- by the balance command (in multiperiod mode) and (via compoundBalanceReport)
    -- by the bs/cf/is commands.
    multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport
    multiBalanceReport :: ReportSpec -> Journal -> MultiBalanceReport
    multiBalanceReport ReportSpec
    rspec Journal
    j = ReportSpec
    -> Journal
    -> PriceOracle
    -> Set CommoditySymbol
    -> MultiBalanceReport
    multiBalanceReportWith ReportSpec
    rspec Journal
    j (Bool -> Journal -> PriceOracle
    journalPriceOracle Bool
    infer Journal
    j) Set CommoditySymbol
    forall a. Monoid a => a
    mempty
      where infer :: Bool
    infer = ReportOpts -> Bool
    infer_prices_ (ReportOpts -> Bool) -> ReportOpts -> Bool
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec

    -- | A helper for multiBalanceReport. This one takes some extra arguments,
    -- a 'PriceOracle' to be used for looking up market prices, and a set of
    -- 'AccountName's which should not be elided. Commands which run multiple
    -- reports (bs etc.) can generate the price oracle just once for efficiency,
    -- passing it to each report by calling this function directly.
    multiBalanceReportWith :: ReportSpec -> Journal -> PriceOracle -> Set AccountName -> MultiBalanceReport
    multiBalanceReportWith :: ReportSpec
    -> Journal
    -> PriceOracle
    -> Set CommoditySymbol
    -> MultiBalanceReport
    multiBalanceReportWith ReportSpec
    rspec' Journal
    j PriceOracle
    priceoracle Set CommoditySymbol
    unelidableaccts = MultiBalanceReport
    report
      where
        -- Queries, report/column dates.
        (DateSpan
    reportspan, [DateSpan]
    colspans) = Journal -> ReportSpec -> (DateSpan, [DateSpan])
    reportSpan Journal
    j ReportSpec
    rspec'
        rspec :: ReportSpec
    rspec = [Char] -> ReportSpec -> ReportSpec
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "reportopts" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery ReportSpec
    rspec' DateSpan
    reportspan

        -- Group postings into their columns.
        colps :: [(DateSpan, [Posting])]
    colps = [Char] -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "colps" ([(DateSpan, [Posting])] -> [(DateSpan, [Posting])])
    -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal -> PriceOracle -> [DateSpan] -> [(DateSpan, [Posting])]
    getPostingsByColumn ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle [DateSpan]
    colspans

        -- The matched accounts with a starting balance. All of these should appear
        -- in the report, even if they have no postings during the report period.
        startbals :: HashMap CommoditySymbol Account
    startbals = [Char]
    -> HashMap CommoditySymbol Account
    -> HashMap CommoditySymbol Account
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "startbals" (HashMap CommoditySymbol Account
     -> HashMap CommoditySymbol Account)
    -> HashMap CommoditySymbol Account
    -> HashMap CommoditySymbol Account
    forall a b. (a -> b) -> a -> b
    $
          ReportSpec
    -> Journal
    -> PriceOracle
    -> [Posting]
    -> HashMap CommoditySymbol Account
    startingBalances ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle ([Posting] -> HashMap CommoditySymbol Account)
    -> [Posting] -> HashMap CommoditySymbol Account
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    startingPostings ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan

        -- Generate and postprocess the report, negating balances and taking percentages if needed
        report :: MultiBalanceReport
    report = [Char] -> MultiBalanceReport -> MultiBalanceReport
    forall a. Show a => [Char] -> a -> a
    dbg4 [Char]
    "multiBalanceReportWith" (MultiBalanceReport -> MultiBalanceReport)
    -> MultiBalanceReport -> MultiBalanceReport
    forall a b. (a -> b) -> a -> b
    $
          ReportSpec
    -> Journal
    -> PriceOracle
    -> Set CommoditySymbol
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol Account
    -> MultiBalanceReport
    generateMultiBalanceReport ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle Set CommoditySymbol
    unelidableaccts [(DateSpan, [Posting])]
    colps HashMap CommoditySymbol Account
    startbals

    -- | Generate a compound balance report from a list of CBCSubreportSpec. This
    -- shares postings between the subreports.
    compoundBalanceReport :: ReportSpec -> Journal -> [CBCSubreportSpec a]
                          -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReport :: forall a.
    ReportSpec
    -> Journal
    -> [CBCSubreportSpec a]
    -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReport ReportSpec
    rspec Journal
    j = ReportSpec
    -> Journal
    -> PriceOracle
    -> [CBCSubreportSpec a]
    -> CompoundPeriodicReport a MixedAmount
    forall a.
    ReportSpec
    -> Journal
    -> PriceOracle
    -> [CBCSubreportSpec a]
    -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReportWith ReportSpec
    rspec Journal
    j (Bool -> Journal -> PriceOracle
    journalPriceOracle Bool
    infer Journal
    j)
      where infer :: Bool
    infer = ReportOpts -> Bool
    infer_prices_ (ReportOpts -> Bool) -> ReportOpts -> Bool
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec

    -- | A helper for compoundBalanceReport, similar to multiBalanceReportWith.
    compoundBalanceReportWith :: ReportSpec -> Journal -> PriceOracle
                              -> [CBCSubreportSpec a]
                              -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReportWith :: forall a.
    ReportSpec
    -> Journal
    -> PriceOracle
    -> [CBCSubreportSpec a]
    -> CompoundPeriodicReport a MixedAmount
    compoundBalanceReportWith ReportSpec
    rspec' Journal
    j PriceOracle
    priceoracle [CBCSubreportSpec a]
    subreportspecs = CompoundPeriodicReport a MixedAmount
    cbr
      where
        -- Queries, report/column dates.
        (DateSpan
    reportspan, [DateSpan]
    colspans) = Journal -> ReportSpec -> (DateSpan, [DateSpan])
    reportSpan Journal
    j ReportSpec
    rspec'
        rspec :: ReportSpec
    rspec = [Char] -> ReportSpec -> ReportSpec
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "reportopts" (ReportSpec -> ReportSpec) -> ReportSpec -> ReportSpec
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery ReportSpec
    rspec' DateSpan
    reportspan

        -- Group postings into their columns.
        colps :: [(DateSpan, [Posting])]
    colps = [Char] -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "colps" ([(DateSpan, [Posting])] -> [(DateSpan, [Posting])])
    -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal -> PriceOracle -> [DateSpan] -> [(DateSpan, [Posting])]
    getPostingsByColumn ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle [DateSpan]
    colspans

        -- The matched postings with a starting balance. All of these should appear
        -- in the report, even if they have no postings during the report period.
        startps :: [Posting]
    startps = [Char] -> [Posting] -> [Posting]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "startps" ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    startingPostings ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan

        subreports :: [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    subreports = (CBCSubreportSpec a
     -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool))
    -> [CBCSubreportSpec a]
    -> [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    forall a b. (a -> b) -> [a] -> [b]
    map CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    forall {a}.
    CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    generateSubreport [CBCSubreportSpec a]
    subreportspecs
          where
            generateSubreport :: CBCSubreportSpec a
    -> (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    generateSubreport CBCSubreportSpec{Bool
    CommoditySymbol
    Query
    ReportOpts -> ReportOpts
    MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreporttitle :: CommoditySymbol
    cbcsubreportquery :: Query
    cbcsubreportoptions :: ReportOpts -> ReportOpts
    cbcsubreporttransform :: MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreportincreasestotal :: Bool
    cbcsubreporttitle :: forall a. CBCSubreportSpec a -> CommoditySymbol
    cbcsubreportquery :: forall a. CBCSubreportSpec a -> Query
    cbcsubreportoptions :: forall a. CBCSubreportSpec a -> ReportOpts -> ReportOpts
    cbcsubreporttransform :: forall a.
    CBCSubreportSpec a
    -> MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreportincreasestotal :: forall a. CBCSubreportSpec a -> Bool
    ..} =
                ( CommoditySymbol
    cbcsubreporttitle
                -- Postprocess the report, negating balances and taking percentages if needed
                , MultiBalanceReport -> PeriodicReport a MixedAmount
    cbcsubreporttransform (MultiBalanceReport -> PeriodicReport a MixedAmount)
    -> MultiBalanceReport -> PeriodicReport a MixedAmount
    forall a b. (a -> b) -> a -> b
    $
                    ReportSpec
    -> Journal
    -> PriceOracle
    -> Set CommoditySymbol
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol Account
    -> MultiBalanceReport
    generateMultiBalanceReport ReportSpec
    rspecsub Journal
    j PriceOracle
    priceoracle Set CommoditySymbol
    forall a. Monoid a => a
    mempty [(DateSpan, [Posting])]
    colps' HashMap CommoditySymbol Account
    startbals'
                , Bool
    cbcsubreportincreasestotal
                )
              where
                ropts :: ReportOpts
    ropts = ReportOpts -> ReportOpts
    cbcsubreportoptions (ReportOpts -> ReportOpts) -> ReportOpts -> ReportOpts
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec
                -- Add a restriction to this subreport to the report query.
                -- XXX in non-thorough way, consider updateReportSpec ?
                rspecsub :: ReportSpec
    rspecsub = ReportSpec
    rspec{_rsReportOpts=ropts, _rsQuery=And [cbcsubreportquery, _rsQuery rspec]}
                -- Starting balances and column postings specific to this subreport.
                startbals' :: HashMap CommoditySymbol Account
    startbals' = ReportSpec
    -> Journal
    -> PriceOracle
    -> [Posting]
    -> HashMap CommoditySymbol Account
    startingBalances ReportSpec
    rspecsub Journal
    j PriceOracle
    priceoracle ([Posting] -> HashMap CommoditySymbol Account)
    -> [Posting] -> HashMap CommoditySymbol Account
    forall a b. (a -> b) -> a -> b
    $
                  (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((CommoditySymbol -> Maybe AccountType) -> Query -> Posting -> Bool
    matchesPostingExtra (Journal -> CommoditySymbol -> Maybe AccountType
    journalAccountType Journal
    j) Query
    cbcsubreportquery) [Posting]
    startps
                colps' :: [(DateSpan, [Posting])]
    colps' = ((DateSpan, [Posting]) -> (DateSpan, [Posting]))
    -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a b. (a -> b) -> [a] -> [b]
    map (([Posting] -> [Posting])
    -> (DateSpan, [Posting]) -> (DateSpan, [Posting])
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second (([Posting] -> [Posting])
     -> (DateSpan, [Posting]) -> (DateSpan, [Posting]))
    -> ([Posting] -> [Posting])
    -> (DateSpan, [Posting])
    -> (DateSpan, [Posting])
    forall a b. (a -> b) -> a -> b
    $ (Posting -> Bool) -> [Posting] -> [Posting]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((CommoditySymbol -> Maybe AccountType) -> Query -> Posting -> Bool
    matchesPostingExtra (Journal -> CommoditySymbol -> Maybe AccountType
    journalAccountType Journal
    j) Query
    cbcsubreportquery)) [(DateSpan, [Posting])]
    colps

        -- Sum the subreport totals by column. Handle these cases:
        -- - no subreports
        -- - empty subreports, having no subtotals (#588)
        -- - subreports with a shorter subtotals row than the others
        overalltotals :: PeriodicReportRow () MixedAmount
    overalltotals = case [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    subreports of
            []     -> ()
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow () [] MixedAmount
    nullmixedamt MixedAmount
    nullmixedamt
            ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    r:[(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    rs) -> NonEmpty (PeriodicReportRow () MixedAmount)
    -> PeriodicReportRow () MixedAmount
    forall a. Semigroup a => NonEmpty a -> a
    sconcat (NonEmpty (PeriodicReportRow () MixedAmount)
     -> PeriodicReportRow () MixedAmount)
    -> NonEmpty (PeriodicReportRow () MixedAmount)
    -> PeriodicReportRow () MixedAmount
    forall a b. (a -> b) -> a -> b
    $ ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
     -> PeriodicReportRow () MixedAmount)
    -> NonEmpty (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> NonEmpty (PeriodicReportRow () MixedAmount)
    forall a b. (a -> b) -> NonEmpty a -> NonEmpty b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    forall {a} {a}.
    (a, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    subreportTotal ((CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    r(CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    -> [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    -> NonEmpty (CommoditySymbol, PeriodicReport a MixedAmount, Bool)
    forall a. a -> [a] -> NonEmpty a
    :|[(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    rs)
          where
            subreportTotal :: (a, PeriodicReport a MixedAmount, Bool)
    -> PeriodicReportRow () MixedAmount
    subreportTotal (a
    _, PeriodicReport a MixedAmount
    sr, Bool
    increasestotal) =
                (if Bool
    increasestotal then PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a. a -> a
    id else (MixedAmount -> MixedAmount)
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b.
    (a -> b) -> PeriodicReportRow () a -> PeriodicReportRow () b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap MixedAmount -> MixedAmount
    maNegate) (PeriodicReportRow () MixedAmount
     -> PeriodicReportRow () MixedAmount)
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReport a MixedAmount -> PeriodicReportRow () MixedAmount
    forall a b. PeriodicReport a b -> PeriodicReportRow () b
    prTotals PeriodicReport a MixedAmount
    sr

        cbr :: CompoundPeriodicReport a MixedAmount
    cbr = CommoditySymbol
    -> [DateSpan]
    -> [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    -> PeriodicReportRow () MixedAmount
    -> CompoundPeriodicReport a MixedAmount
    forall a b.
    CommoditySymbol
    -> [DateSpan]
    -> [(CommoditySymbol, PeriodicReport a b, Bool)]
    -> PeriodicReportRow () b
    -> CompoundPeriodicReport a b
    CompoundPeriodicReport CommoditySymbol
    "" (((DateSpan, [Posting]) -> DateSpan)
    -> [(DateSpan, [Posting])] -> [DateSpan]
    forall a b. (a -> b) -> [a] -> [b]
    map (DateSpan, [Posting]) -> DateSpan
    forall a b. (a, b) -> a
    fst [(DateSpan, [Posting])]
    colps) [(CommoditySymbol, PeriodicReport a MixedAmount, Bool)]
    subreports PeriodicReportRow () MixedAmount
    overalltotals

    -- XXX seems refactorable
    -- | Calculate accounts' balances on the report start date, from these postings
    -- which should be all postings before that date, and possibly also from account declarations.
    startingBalances :: ReportSpec -> Journal -> PriceOracle -> [Posting]
                                 -> HashMap AccountName Account
    startingBalances :: ReportSpec
    -> Journal
    -> PriceOracle
    -> [Posting]
    -> HashMap CommoditySymbol Account
    startingBalances ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle [Posting]
    ps =
        Account -> DateSpan -> Map DateSpan Account -> Account
    forall k a. Ord k => a -> k -> Map k a -> a
    M.findWithDefault Account
    nullacct DateSpan
    emptydatespan
          (Map DateSpan Account -> Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol Account
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ReportSpec
    -> Journal
    -> PriceOracle
    -> HashMap CommoditySymbol Account
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol (Map DateSpan Account)
    calculateReportMatrix ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle HashMap CommoditySymbol Account
    forall a. Monoid a => a
    mempty [(DateSpan
    emptydatespan, [Posting]
    ps)]

    -- | Postings needed to calculate starting balances.
    --
    -- Balances at report start date, from all earlier postings which otherwise match the query.
    -- These balances are unvalued.
    -- TODO: Do we want to check whether to bother calculating these? isHistorical
    -- and startDate is not nothing, otherwise mempty? This currently gives a
    -- failure with some totals which are supposed to be 0 being blank.
    startingPostings :: ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    startingPostings :: ReportSpec -> Journal -> PriceOracle -> DateSpan -> [Posting]
    startingPostings rspec :: ReportSpec
    rspec@ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query,_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle DateSpan
    reportspan =
        ReportSpec -> Journal -> PriceOracle -> [Posting]
    getPostings ReportSpec
    rspec' Journal
    j PriceOracle
    priceoracle
      where
        rspec' :: ReportSpec
    rspec' = ReportSpec
    rspec{_rsQuery=startbalq,_rsReportOpts=ropts'}
        -- If we're re-valuing every period, we need to have the unvalued start
        -- balance, so we can do it ourselves later.
        ropts' :: ReportOpts
    ropts' = case ReportOpts -> Maybe ValuationType
    value_ ReportOpts
    ropts of
            Just (AtEnd Maybe CommoditySymbol
    _) -> ReportOpts
    ropts{period_=precedingperiod, value_=Nothing}
            Maybe ValuationType
    _              -> ReportOpts
    ropts{period_=precedingperiod}

        -- q projected back before the report start date.
        -- When there's no report start date, in case there are future txns (the hledger-ui case above),
        -- we use emptydatespan to make sure they aren't counted as starting balance.
        startbalq :: Query
    startbalq = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "startbalq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ [Query] -> Query
    And [Query
    datelessq, Query
    precedingspanq]
        datelessq :: Query
    datelessq = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "datelessq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ (Query -> Bool) -> Query -> Query
    filterQuery (Bool -> Bool
    not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> Bool
    queryIsDateOrDate2) Query
    query

        precedingperiod :: Period
    precedingperiod = DateSpan -> Period
    dateSpanAsPeriod (DateSpan -> Period) -> (Period -> DateSpan) -> Period -> Period
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . DateSpan -> DateSpan -> DateSpan
    spanIntersect DateSpan
    precedingspan (DateSpan -> DateSpan)
    -> (Period -> DateSpan) -> Period -> DateSpan
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .
                             Period -> DateSpan
    periodAsDateSpan (Period -> Period) -> Period -> Period
    forall a b. (a -> b) -> a -> b
    $ ReportOpts -> Period
    period_ ReportOpts
    ropts
        precedingspan :: DateSpan
    precedingspan = Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (Day -> EFDay
    Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> DateSpan -> Maybe Day
    spanStart DateSpan
    reportspan)
        precedingspanq :: Query
    precedingspanq = (if ReportOpts -> Bool
    date2_ ReportOpts
    ropts then DateSpan -> Query
    Date2 else DateSpan -> Query
    Date) (DateSpan -> Query) -> DateSpan -> Query
    forall a b. (a -> b) -> a -> b
    $ case DateSpan
    precedingspan of
            DateSpan Maybe EFDay
    Nothing Maybe EFDay
    Nothing -> DateSpan
    emptydatespan
            DateSpan
    a -> DateSpan
    a

    -- | Remove any date queries and insert queries from the report span.
    -- The user's query expanded to the report span
    -- if there is one (otherwise any date queries are left as-is, which
    -- handles the hledger-ui+future txns case above).
    makeReportQuery :: ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery :: ReportSpec -> DateSpan -> ReportSpec
    makeReportQuery ReportSpec
    rspec DateSpan
    reportspan
        | DateSpan
    reportspan DateSpan -> DateSpan -> Bool
    forall a. Eq a => a -> a -> Bool
    == DateSpan
    nulldatespan = ReportSpec
    rspec
        | Bool
    otherwise = ReportSpec
    rspec{_rsQuery=query}
      where
        query :: Query
    query            = Query -> Query
    simplifyQuery (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ [Query] -> Query
    And [Query -> Query
    dateless (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Query
    _rsQuery ReportSpec
    rspec, Query
    reportspandatesq]
        reportspandatesq :: Query
    reportspandatesq = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "reportspandatesq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ DateSpan -> Query
    dateqcons DateSpan
    reportspan
        dateless :: Query -> Query
    dateless         = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "dateless" (Query -> Query) -> (Query -> Query) -> Query -> Query
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Query -> Bool) -> Query -> Query
    filterQuery (Bool -> Bool
    not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> Bool
    queryIsDateOrDate2)
        dateqcons :: DateSpan -> Query
    dateqcons        = if ReportOpts -> Bool
    date2_ (ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec) then DateSpan -> Query
    Date2 else DateSpan -> Query
    Date

    -- | Group postings, grouped by their column
    getPostingsByColumn :: ReportSpec -> Journal -> PriceOracle -> [DateSpan] -> [(DateSpan, [Posting])]
    getPostingsByColumn :: ReportSpec
    -> Journal -> PriceOracle -> [DateSpan] -> [(DateSpan, [Posting])]
    getPostingsByColumn ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle [DateSpan]
    colspans =
        Bool
    -> (Posting -> Day)
    -> [DateSpan]
    -> [Posting]
    -> [(DateSpan, [Posting])]
    forall a.
    Bool -> (a -> Day) -> [DateSpan] -> [a] -> [(DateSpan, [a])]
    groupByDateSpan Bool
    True Posting -> Day
    getDate [DateSpan]
    colspans [Posting]
    ps
      where
        -- Postings matching the query within the report period.
        ps :: [Posting]
    ps = [Char] -> [Posting] -> [Posting]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "getPostingsByColumn" ([Posting] -> [Posting]) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> [Posting]
    getPostings ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle
        -- The date spans to be included as report columns.
        getDate :: Posting -> Day
    getDate = WhichDate -> Posting -> Day
    postingDateOrDate2 (ReportOpts -> WhichDate
    whichDate (ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec))

    -- | Gather postings matching the query within the report period.
    getPostings :: ReportSpec -> Journal -> PriceOracle -> [Posting]
    getPostings :: ReportSpec -> Journal -> PriceOracle -> [Posting]
    getPostings rspec :: ReportSpec
    rspec@ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query, _rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle =
        Journal -> [Posting]
    journalPostings (Journal -> [Posting]) -> Journal -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ ReportSpec -> Journal -> PriceOracle -> Journal
    journalValueAndFilterPostingsWith ReportSpec
    rspec' Journal
    j PriceOracle
    priceoracle
      where
        rspec' :: ReportSpec
    rspec' = ReportSpec
    rspec{_rsQuery=depthless, _rsReportOpts = ropts'}
        ropts' :: ReportOpts
    ropts' = if Maybe (Maybe CommoditySymbol) -> Bool
    forall a. Maybe a -> Bool
    isJust (ReportOpts -> Maybe (Maybe CommoditySymbol)
    valuationAfterSum ReportOpts
    ropts)
            then ReportOpts
    ropts{value_=Nothing, conversionop_=Just NoConversionOp}  -- If we're valuing after the sum, don't do it now
            else ReportOpts
    ropts

        -- The user's query with no depth limit, and expanded to the report span
        -- if there is one (otherwise any date queries are left as-is, which
        -- handles the hledger-ui+future txns case above).
        depthless :: Query
    depthless = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "depthless" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ (Query -> Bool) -> Query -> Query
    filterQuery (Bool -> Bool
    not (Bool -> Bool) -> (Query -> Bool) -> Query -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Query -> Bool
    queryIsDepth) Query
    query

    -- | From set of postings, eg for a single report column, calculate the balance change in each account.
    -- Accounts and amounts will be depth-clipped appropriately if a depth limit is in effect.
    --
    -- When --declared is used, accounts which have been declared with an account directive
    -- are also included, with a 0 balance change. But only leaf accounts, since non-leaf
    -- empty declared accounts are less useful in reports. This is primarily for hledger-ui.
    acctChanges :: ReportSpec -> Journal -> [Posting] -> HashMap ClippedAccountName Account
    acctChanges :: ReportSpec
    -> Journal -> [Posting] -> HashMap CommoditySymbol Account
    acctChanges ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query,_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts{AccountListMode
    accountlistmode_ :: AccountListMode
    accountlistmode_ :: ReportOpts -> AccountListMode
    accountlistmode_, Bool
    declared_ :: Bool
    declared_ :: ReportOpts -> Bool
    declared_}} Journal
    j [Posting]
    ps =
      [(CommoditySymbol, Account)] -> HashMap CommoditySymbol Account
    forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
    HM.fromList [(Account -> CommoditySymbol
    aname Account
    a, Account
    a) | Account
    a <- [Account]
    accts]
      where
        -- With --declared, add the query-matching declared accounts
        -- (as dummy postings so they are processed like the rest).
        -- This function is used for calculating both pre-start changes and column changes,
        -- and the declared accounts are really only needed for the former,
        -- but it's harmless to have them in the column changes as well.
        ps' :: [Posting]
    ps' = [Posting]
    ps [Posting] -> [Posting] -> [Posting]
    forall a. [a] -> [a] -> [a]
    ++ if Bool
    declared_ then [Posting]
    declaredacctps else []
          where
            declaredacctps :: [Posting]
    declaredacctps =
              [Posting
    nullposting{paccount=a}
              | CommoditySymbol
    a <- Journal -> [CommoditySymbol]
    journalLeafAccountNamesDeclared Journal
    j
              , (CommoditySymbol -> Maybe AccountType)
    -> (CommoditySymbol -> [Tag]) -> Query -> CommoditySymbol -> Bool
    matchesAccountExtra (Journal -> CommoditySymbol -> Maybe AccountType
    journalAccountType Journal
    j) (Journal -> CommoditySymbol -> [Tag]
    journalAccountTags Journal
    j) Query
    accttypetagsq CommoditySymbol
    a
              ]
              where
                accttypetagsq :: Query
    accttypetagsq  = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "accttypetagsq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $
                  (Query -> Bool) -> Query -> Query
    filterQueryOrNotQuery (\Query
    q -> Query -> Bool
    queryIsAcct Query
    q Bool -> Bool -> Bool
    || Query -> Bool
    queryIsType Query
    q Bool -> Bool -> Bool
    || Query -> Bool
    queryIsTag Query
    q) Query
    query

        filterbydepth :: [Account] -> [Account]
    filterbydepth = case AccountListMode
    accountlistmode_ of
          AccountListMode
    ALTree -> (Account -> Bool) -> [Account] -> [Account]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((Query
    depthq Query -> CommoditySymbol -> Bool
    `matchesAccount`) (CommoditySymbol -> Bool)
    -> (Account -> CommoditySymbol) -> Account -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account -> CommoditySymbol
    aname)    -- a tree - just exclude deeper accounts
          AccountListMode
    ALFlat -> Maybe Int -> [Account] -> [Account]
    clipAccountsAndAggregate (Query -> Maybe Int
    queryDepth Query
    depthq)  -- a list - aggregate deeper accounts at the depth limit
                    ([Account] -> [Account])
    -> ([Account] -> [Account]) -> [Account] -> [Account]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Account -> Bool) -> [Account] -> [Account]
    forall a. (a -> Bool) -> [a] -> [a]
    filter ((Int
    0Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <) (Int -> Bool) -> (Account -> Int) -> Account -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account -> Int
    anumpostings)                -- and exclude empty parent accounts
          where depthq :: Query
    depthq = [Char] -> Query -> Query
    forall a. Show a => [Char] -> a -> a
    dbg3 [Char]
    "depthq" (Query -> Query) -> Query -> Query
    forall a b. (a -> b) -> a -> b
    $ (Query -> Bool) -> Query -> Query
    filterQuery Query -> Bool
    queryIsDepth Query
    query

        accts :: [Account]
    accts = [Account] -> [Account]
    filterbydepth ([Account] -> [Account]) -> [Account] -> [Account]
    forall a b. (a -> b) -> a -> b
    $ Int -> [Account] -> [Account]
    forall a. Int -> [a] -> [a]
    drop Int
    1 ([Account] -> [Account]) -> [Account] -> [Account]
    forall a b. (a -> b) -> a -> b
    $ [Posting] -> [Account]
    accountsFromPostings [Posting]
    ps'

    -- | Gather the account balance changes into a regular matrix, then
    -- accumulate and value amounts, as specified by the report options.
    -- Makes sure all report columns have an entry.
    calculateReportMatrix :: ReportSpec -> Journal -> PriceOracle
                          -> HashMap ClippedAccountName Account
                          -> [(DateSpan, [Posting])]
                          -> HashMap ClippedAccountName (Map DateSpan Account)
    calculateReportMatrix :: ReportSpec
    -> Journal
    -> PriceOracle
    -> HashMap CommoditySymbol Account
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol (Map DateSpan Account)
    calculateReportMatrix rspec :: ReportSpec
    rspec@ReportSpec{_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle HashMap CommoditySymbol Account
    startbals [(DateSpan, [Posting])]
    colps =  -- PARTIAL:
        -- Ensure all columns have entries, including those with starting balances
        (CommoditySymbol -> Map DateSpan Account -> Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall k v1 v2. (k -> v1 -> v2) -> HashMap k v1 -> HashMap k v2
    HM.mapWithKey CommoditySymbol -> Map DateSpan Account -> Map DateSpan Account
    rowbals HashMap CommoditySymbol (Map DateSpan Account)
    allchanges
      where
        -- The valued row amounts to be displayed: per-period changes,
        -- zero-based cumulative totals, or
        -- starting-balance-based historical balances.
        rowbals :: CommoditySymbol -> Map DateSpan Account -> Map DateSpan Account
    rowbals CommoditySymbol
    name Map DateSpan Account
    unvaluedChanges = [Char] -> Map DateSpan Account -> Map DateSpan Account
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "rowbals" (Map DateSpan Account -> Map DateSpan Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall a b. (a -> b) -> a -> b
    $ case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
            BalanceAccumulation
    PerPeriod  -> Map DateSpan Account
    changes
            BalanceAccumulation
    Cumulative -> Map DateSpan Account
    cumulative
            BalanceAccumulation
    Historical -> Map DateSpan Account
    historical
          where
            -- changes to report on: usually just the valued changes themselves, but use the
            -- differences in the valued historical amount for CalcValueChange and CalcGain.
            changes :: Map DateSpan Account
    changes = case ReportOpts -> BalanceCalculation
    balancecalc_ ReportOpts
    ropts of
                BalanceCalculation
    CalcChange        -> (DateSpan -> Account -> Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall k a b. (k -> a -> b) -> Map k a -> Map k b
    M.mapWithKey DateSpan -> Account -> Account
    avalue Map DateSpan Account
    unvaluedChanges
                BalanceCalculation
    CalcBudget        -> (DateSpan -> Account -> Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall k a b. (k -> a -> b) -> Map k a -> Map k b
    M.mapWithKey DateSpan -> Account -> Account
    avalue Map DateSpan Account
    unvaluedChanges
                BalanceCalculation
    CalcValueChange   -> Account -> Map DateSpan Account -> Map DateSpan Account
    forall k. Account -> Map k Account -> Map k Account
    periodChanges Account
    valuedStart Map DateSpan Account
    historical
                BalanceCalculation
    CalcGain          -> Account -> Map DateSpan Account -> Map DateSpan Account
    forall k. Account -> Map k Account -> Map k Account
    periodChanges Account
    valuedStart Map DateSpan Account
    historical
                BalanceCalculation
    CalcPostingsCount -> (DateSpan -> Account -> Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall k a b. (k -> a -> b) -> Map k a -> Map k b
    M.mapWithKey DateSpan -> Account -> Account
    avalue Map DateSpan Account
    unvaluedChanges
            -- the historical balance is the valued cumulative sum of all unvalued changes
            historical :: Map DateSpan Account
    historical = (DateSpan -> Account -> Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall k a b. (k -> a -> b) -> Map k a -> Map k b
    M.mapWithKey DateSpan -> Account -> Account
    avalue (Map DateSpan Account -> Map DateSpan Account)
    -> Map DateSpan Account -> Map DateSpan Account
    forall a b. (a -> b) -> a -> b
    $ Account -> Map DateSpan Account -> Map DateSpan Account
    cumulativeSum Account
    startingBalance Map DateSpan Account
    unvaluedChanges
            -- since this is a cumulative sum of valued amounts, it should not be valued again
            cumulative :: Map DateSpan Account
    cumulative = Account -> Map DateSpan Account -> Map DateSpan Account
    cumulativeSum Account
    nullacct Map DateSpan Account
    changes
            startingBalance :: Account
    startingBalance = Account
    -> CommoditySymbol -> HashMap CommoditySymbol Account -> Account
    forall k v. (Eq k, Hashable k) => v -> k -> HashMap k v -> v
    HM.lookupDefault Account
    nullacct CommoditySymbol
    name HashMap CommoditySymbol Account
    startbals
            valuedStart :: Account
    valuedStart = DateSpan -> Account -> Account
    avalue (Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan Maybe EFDay
    forall a. Maybe a
    Nothing (Day -> EFDay
    Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe Day
    historicalDate)) Account
    startingBalance

        -- In each column, get each account's balance changes
        colacctchanges :: [(DateSpan, HashMap CommoditySymbol Account)]
    colacctchanges = [Char]
    -> [(DateSpan, HashMap CommoditySymbol Account)]
    -> [(DateSpan, HashMap CommoditySymbol Account)]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "colacctchanges" ([(DateSpan, HashMap CommoditySymbol Account)]
     -> [(DateSpan, HashMap CommoditySymbol Account)])
    -> [(DateSpan, HashMap CommoditySymbol Account)]
    -> [(DateSpan, HashMap CommoditySymbol Account)]
    forall a b. (a -> b) -> a -> b
    $ ((DateSpan, [Posting])
     -> (DateSpan, HashMap CommoditySymbol Account))
    -> [(DateSpan, [Posting])]
    -> [(DateSpan, HashMap CommoditySymbol Account)]
    forall a b. (a -> b) -> [a] -> [b]
    map (([Posting] -> HashMap CommoditySymbol Account)
    -> (DateSpan, [Posting])
    -> (DateSpan, HashMap CommoditySymbol Account)
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second (([Posting] -> HashMap CommoditySymbol Account)
     -> (DateSpan, [Posting])
     -> (DateSpan, HashMap CommoditySymbol Account))
    -> ([Posting] -> HashMap CommoditySymbol Account)
    -> (DateSpan, [Posting])
    -> (DateSpan, HashMap CommoditySymbol Account)
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal -> [Posting] -> HashMap CommoditySymbol Account
    acctChanges ReportSpec
    rspec Journal
    j) [(DateSpan, [Posting])]
    colps :: [(DateSpan, HashMap ClippedAccountName Account)]
        -- Transpose it to get each account's balance changes across all columns
        acctchanges :: HashMap CommoditySymbol (Map DateSpan Account)
    acctchanges = [Char]
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "acctchanges" (HashMap CommoditySymbol (Map DateSpan Account)
     -> HashMap CommoditySymbol (Map DateSpan Account))
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a b. (a -> b) -> a -> b
    $ [(DateSpan, HashMap CommoditySymbol Account)]
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a.
    [(DateSpan, HashMap CommoditySymbol a)]
    -> HashMap CommoditySymbol (Map DateSpan a)
    transposeMap [(DateSpan, HashMap CommoditySymbol Account)]
    colacctchanges :: HashMap AccountName (Map DateSpan Account)
        -- Fill out the matrix with zeros in empty cells
        allchanges :: HashMap CommoditySymbol (Map DateSpan Account)
    allchanges = ((Map DateSpan Account
    -> Map DateSpan Account -> Map DateSpan Account
    forall a. Semigroup a => a -> a -> a
    <>Map DateSpan Account
    zeros) (Map DateSpan Account -> Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> HashMap CommoditySymbol (Map DateSpan Account)
    acctchanges) HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a. Semigroup a => a -> a -> a
    <> (Map DateSpan Account
    zeros Map DateSpan Account
    -> HashMap CommoditySymbol Account
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a b.
    a -> HashMap CommoditySymbol b -> HashMap CommoditySymbol a
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ HashMap CommoditySymbol Account
    startbals)

        avalue :: DateSpan -> Account -> Account
    avalue = (MixedAmount -> MixedAmount) -> Account -> Account
    acctApplyBoth ((MixedAmount -> MixedAmount) -> Account -> Account)
    -> (DateSpan -> MixedAmount -> MixedAmount)
    -> DateSpan
    -> Account
    -> Account
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ReportOpts
    -> Journal -> PriceOracle -> DateSpan -> MixedAmount -> MixedAmount
    mixedAmountApplyValuationAfterSumFromOptsWith ReportOpts
    ropts Journal
    j PriceOracle
    priceoracle
        acctApplyBoth :: (MixedAmount -> MixedAmount) -> Account -> Account
    acctApplyBoth MixedAmount -> MixedAmount
    f Account
    a = Account
    a{aibalance = f $ aibalance a, aebalance = f $ aebalance a}
        historicalDate :: Maybe Day
    historicalDate = [Day] -> Maybe Day
    forall a. Ord a => [a] -> Maybe a
    minimumMay ([Day] -> Maybe Day) -> [Day] -> Maybe Day
    forall a b. (a -> b) -> a -> b
    $ (DateSpan -> Maybe Day) -> [DateSpan] -> [Day]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe DateSpan -> Maybe Day
    spanStart [DateSpan]
    colspans
        zeros :: Map DateSpan Account
    zeros = [(DateSpan, Account)] -> Map DateSpan Account
    forall k a. Ord k => [(k, a)] -> Map k a
    M.fromList [(DateSpan
    spn, Account
    nullacct) | DateSpan
    spn <- [DateSpan]
    colspans]
        colspans :: [DateSpan]
    colspans = ((DateSpan, [Posting]) -> DateSpan)
    -> [(DateSpan, [Posting])] -> [DateSpan]
    forall a b. (a -> b) -> [a] -> [b]
    map (DateSpan, [Posting]) -> DateSpan
    forall a b. (a, b) -> a
    fst [(DateSpan, [Posting])]
    colps

    -- | Lay out a set of postings grouped by date span into a regular matrix with rows
    -- given by AccountName and columns by DateSpan, then generate a MultiBalanceReport
    -- from the columns.
    generateMultiBalanceReport :: ReportSpec -> Journal -> PriceOracle -> Set AccountName
                               -> [(DateSpan, [Posting])] -> HashMap AccountName Account
                               -> MultiBalanceReport
    generateMultiBalanceReport :: ReportSpec
    -> Journal
    -> PriceOracle
    -> Set CommoditySymbol
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol Account
    -> MultiBalanceReport
    generateMultiBalanceReport rspec :: ReportSpec
    rspec@ReportSpec{_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Journal
    j PriceOracle
    priceoracle Set CommoditySymbol
    unelidableaccts [(DateSpan, [Posting])]
    colps0 HashMap CommoditySymbol Account
    startbals =
        MultiBalanceReport
    report
      where
        -- If doing --count, set all posting amounts to "1".
        colps :: [(DateSpan, [Posting])]
    colps =
          if ReportOpts -> BalanceCalculation
    balancecalc_ ReportOpts
    ropts BalanceCalculation -> BalanceCalculation -> Bool
    forall a. Eq a => a -> a -> Bool
    == BalanceCalculation
    CalcPostingsCount
          then ((DateSpan, [Posting]) -> (DateSpan, [Posting]))
    -> [(DateSpan, [Posting])] -> [(DateSpan, [Posting])]
    forall a b. (a -> b) -> [a] -> [b]
    map (([Posting] -> [Posting])
    -> (DateSpan, [Posting]) -> (DateSpan, [Posting])
    forall b c a. (b -> c) -> (a, b) -> (a, c)
    forall (p :: * -> * -> *) b c a.
    Bifunctor p =>
    (b -> c) -> p a b -> p a c
    second ((Posting -> Posting) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> MixedAmount) -> Posting -> Posting
    postingTransformAmount (MixedAmount -> MixedAmount -> MixedAmount
    forall a b. a -> b -> a
    const (MixedAmount -> MixedAmount -> MixedAmount)
    -> MixedAmount -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t Amount -> MixedAmount
    mixed [Quantity -> Amount
    num Quantity
    1])))) [(DateSpan, [Posting])]
    colps0
          else [(DateSpan, [Posting])]
    colps0

        -- Process changes into normal, cumulative, or historical amounts, plus value them
        matrix :: HashMap CommoditySymbol (Map DateSpan Account)
    matrix = ReportSpec
    -> Journal
    -> PriceOracle
    -> HashMap CommoditySymbol Account
    -> [(DateSpan, [Posting])]
    -> HashMap CommoditySymbol (Map DateSpan Account)
    calculateReportMatrix ReportSpec
    rspec Journal
    j PriceOracle
    priceoracle HashMap CommoditySymbol Account
    startbals [(DateSpan, [Posting])]
    colps

        -- All account names that will be displayed, possibly depth-clipped.
        displaynames :: HashMap CommoditySymbol DisplayName
    displaynames = [Char]
    -> HashMap CommoditySymbol DisplayName
    -> HashMap CommoditySymbol DisplayName
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "displaynames" (HashMap CommoditySymbol DisplayName
     -> HashMap CommoditySymbol DisplayName)
    -> HashMap CommoditySymbol DisplayName
    -> HashMap CommoditySymbol DisplayName
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Set CommoditySymbol
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol DisplayName
    displayedAccounts ReportSpec
    rspec Set CommoditySymbol
    unelidableaccts HashMap CommoditySymbol (Map DateSpan Account)
    matrix

        -- All the rows of the report.
        rows :: [PeriodicReportRow DisplayName MixedAmount]
    rows = [Char]
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "rows" ([PeriodicReportRow DisplayName MixedAmount]
     -> [PeriodicReportRow DisplayName MixedAmount])
    -> ([PeriodicReportRow DisplayName MixedAmount]
        -> [PeriodicReportRow DisplayName MixedAmount])
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (if ReportOpts -> Bool
    invert_ ReportOpts
    ropts then (PeriodicReportRow DisplayName MixedAmount
     -> PeriodicReportRow DisplayName MixedAmount)
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map ((MixedAmount -> MixedAmount)
    -> PeriodicReportRow DisplayName MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall a b.
    (a -> b)
    -> PeriodicReportRow DisplayName a
    -> PeriodicReportRow DisplayName b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap MixedAmount -> MixedAmount
    maNegate) else [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a. a -> a
    id)  -- Negate amounts if applicable
                 ([PeriodicReportRow DisplayName MixedAmount]
     -> [PeriodicReportRow DisplayName MixedAmount])
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a b. (a -> b) -> a -> b
    $ ReportOpts
    -> HashMap CommoditySymbol DisplayName
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> [PeriodicReportRow DisplayName MixedAmount]
    buildReportRows ReportOpts
    ropts HashMap CommoditySymbol DisplayName
    displaynames HashMap CommoditySymbol (Map DateSpan Account)
    matrix

        -- Calculate column totals
        totalsrow :: PeriodicReportRow () MixedAmount
    totalsrow = [Char]
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "totalsrow" (PeriodicReportRow () MixedAmount
     -> PeriodicReportRow () MixedAmount)
    -> PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b. (a -> b) -> a -> b
    $ ReportOpts
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> PeriodicReportRow () MixedAmount
    calculateTotalsRow ReportOpts
    ropts [PeriodicReportRow DisplayName MixedAmount]
    rows

        -- Sorted report rows.
        sortedrows :: [PeriodicReportRow DisplayName MixedAmount]
    sortedrows = [Char]
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "sortedrows" ([PeriodicReportRow DisplayName MixedAmount]
     -> [PeriodicReportRow DisplayName MixedAmount])
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a b. (a -> b) -> a -> b
    $ ReportOpts
    -> Journal
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortRows ReportOpts
    ropts Journal
    j [PeriodicReportRow DisplayName MixedAmount]
    rows

        -- Take percentages if needed
        report :: MultiBalanceReport
    report = ReportOpts -> MultiBalanceReport -> MultiBalanceReport
    reportPercent ReportOpts
    ropts (MultiBalanceReport -> MultiBalanceReport)
    -> MultiBalanceReport -> MultiBalanceReport
    forall a b. (a -> b) -> a -> b
    $ [DateSpan]
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> PeriodicReportRow () MixedAmount
    -> MultiBalanceReport
    forall a b.
    [DateSpan]
    -> [PeriodicReportRow a b]
    -> PeriodicReportRow () b
    -> PeriodicReport a b
    PeriodicReport (((DateSpan, [Posting]) -> DateSpan)
    -> [(DateSpan, [Posting])] -> [DateSpan]
    forall a b. (a -> b) -> [a] -> [b]
    map (DateSpan, [Posting]) -> DateSpan
    forall a b. (a, b) -> a
    fst [(DateSpan, [Posting])]
    colps) [PeriodicReportRow DisplayName MixedAmount]
    sortedrows PeriodicReportRow () MixedAmount
    totalsrow

    -- | Build the report rows.
    -- One row per account, with account name info, row amounts, row total and row average.
    -- Rows are unsorted.
    buildReportRows :: ReportOpts
                    -> HashMap AccountName DisplayName
                    -> HashMap AccountName (Map DateSpan Account)
                    -> [MultiBalanceReportRow]
    buildReportRows :: ReportOpts
    -> HashMap CommoditySymbol DisplayName
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> [PeriodicReportRow DisplayName MixedAmount]
    buildReportRows ReportOpts
    ropts HashMap CommoditySymbol DisplayName
    displaynames =
      HashMap CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a. HashMap CommoditySymbol a -> [a]
    forall (t :: * -> *) a. Foldable t => t a -> [a]
    toList (HashMap
       CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
     -> [PeriodicReportRow DisplayName MixedAmount])
    -> (HashMap CommoditySymbol (Map DateSpan Account)
        -> HashMap
             CommoditySymbol (PeriodicReportRow DisplayName MixedAmount))
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (CommoditySymbol
     -> Map DateSpan Account
     -> Maybe (PeriodicReportRow DisplayName MixedAmount))
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    forall k v1 v2.
    (k -> v1 -> Maybe v2) -> HashMap k v1 -> HashMap k v2
    HM.mapMaybeWithKey CommoditySymbol
    -> Map DateSpan Account
    -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    forall {t :: * -> *}.
    Foldable t =>
    CommoditySymbol
    -> t Account -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    mkRow  -- toList of HashMap's Foldable instance - does not sort consistently
      where
        mkRow :: CommoditySymbol
    -> t Account -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    mkRow CommoditySymbol
    name t Account
    accts = do
            DisplayName
    displayname <- CommoditySymbol
    -> HashMap CommoditySymbol DisplayName -> Maybe DisplayName
    forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
    HM.lookup CommoditySymbol
    name HashMap CommoditySymbol DisplayName
    displaynames
            PeriodicReportRow DisplayName MixedAmount
    -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    forall a. a -> Maybe a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (PeriodicReportRow DisplayName MixedAmount
     -> Maybe (PeriodicReportRow DisplayName MixedAmount))
    -> PeriodicReportRow DisplayName MixedAmount
    -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    forall a b. (a -> b) -> a -> b
    $ DisplayName
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow DisplayName
    displayname [MixedAmount]
    rowbals MixedAmount
    rowtot MixedAmount
    rowavg
          where
            rowbals :: [MixedAmount]
    rowbals = (Account -> MixedAmount) -> [Account] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map Account -> MixedAmount
    balance ([Account] -> [MixedAmount]) -> [Account] -> [MixedAmount]
    forall a b. (a -> b) -> a -> b
    $ t Account -> [Account]
    forall a. t a -> [a]
    forall (t :: * -> *) a. Foldable t => t a -> [a]
    toList t Account
    accts  -- toList of Map's Foldable instance - does sort by key
            -- The total and average for the row.
            -- These are always simply the sum/average of the displayed row amounts.
            -- Total for a cumulative/historical report is always the last column.
            rowtot :: MixedAmount
    rowtot = case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
                BalanceAccumulation
    PerPeriod -> [MixedAmount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum [MixedAmount]
    rowbals
                BalanceAccumulation
    _         -> MixedAmount -> [MixedAmount] -> MixedAmount
    forall a. a -> [a] -> a
    lastDef MixedAmount
    nullmixedamt [MixedAmount]
    rowbals
            rowavg :: MixedAmount
    rowavg = [MixedAmount] -> MixedAmount
    averageMixedAmounts [MixedAmount]
    rowbals
        balance :: Account -> MixedAmount
    balance = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of AccountListMode
    ALTree -> Account -> MixedAmount
    aibalance; AccountListMode
    ALFlat -> Account -> MixedAmount
    aebalance

    -- | Calculate accounts which are to be displayed in the report, as well as
    -- their name and depth
    displayedAccounts :: ReportSpec
                      -> Set AccountName
                      -> HashMap AccountName (Map DateSpan Account)
                      -> HashMap AccountName DisplayName
    displayedAccounts :: ReportSpec
    -> Set CommoditySymbol
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol DisplayName
    displayedAccounts ReportSpec{_rsQuery :: ReportSpec -> Query
    _rsQuery=Query
    query,_rsReportOpts :: ReportSpec -> ReportOpts
    _rsReportOpts=ReportOpts
    ropts} Set CommoditySymbol
    unelidableaccts HashMap CommoditySymbol (Map DateSpan Account)
    valuedaccts
        | Int
    qdepth Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 = CommoditySymbol
    -> DisplayName -> HashMap CommoditySymbol DisplayName
    forall k v. Hashable k => k -> v -> HashMap k v
    HM.singleton CommoditySymbol
    "..." (DisplayName -> HashMap CommoditySymbol DisplayName)
    -> DisplayName -> HashMap CommoditySymbol DisplayName
    forall a b. (a -> b) -> a -> b
    $ CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    "..." CommoditySymbol
    "..." Int
    1
        | Bool
    otherwise  = (CommoditySymbol -> Map DateSpan Account -> DisplayName)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol DisplayName
    forall k v1 v2. (k -> v1 -> v2) -> HashMap k v1 -> HashMap k v2
    HM.mapWithKey (\CommoditySymbol
    a Map DateSpan Account
    _ -> CommoditySymbol -> DisplayName
    displayedName CommoditySymbol
    a) HashMap CommoditySymbol (Map DateSpan Account)
    displayedAccts
      where
        -- Accounts which are to be displayed
        displayedAccts :: HashMap CommoditySymbol (Map DateSpan Account)
    displayedAccts = (if Int
    qdepth Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 then HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall a. a -> a
    id else (CommoditySymbol -> Map DateSpan Account -> Bool)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
    HM.filterWithKey CommoditySymbol -> Map DateSpan Account -> Bool
    forall {t :: * -> *}.
    Foldable t =>
    CommoditySymbol -> t Account -> Bool
    keep) HashMap CommoditySymbol (Map DateSpan Account)
    valuedaccts
          where
            keep :: CommoditySymbol -> t Account -> Bool
    keep CommoditySymbol
    name t Account
    amts = CommoditySymbol -> t Account -> Bool
    forall {t :: * -> *}.
    Foldable t =>
    CommoditySymbol -> t Account -> Bool
    isInteresting CommoditySymbol
    name t Account
    amts Bool -> Bool -> Bool
    || CommoditySymbol
    name CommoditySymbol -> HashMap CommoditySymbol Int -> Bool
    forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
    `HM.member` HashMap CommoditySymbol Int
    interestingParents

        displayedName :: CommoditySymbol -> DisplayName
    displayedName CommoditySymbol
    name = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALTree -> CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    name CommoditySymbol
    leaf (Int -> DisplayName) -> (Int -> Int) -> Int -> DisplayName
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> Int -> Int
    forall a. Ord a => a -> a -> a
    max Int
    0 (Int -> DisplayName) -> Int -> DisplayName
    forall a b. (a -> b) -> a -> b
    $ Int
    level Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    boringParents
            AccountListMode
    ALFlat -> CommoditySymbol -> CommoditySymbol -> Int -> DisplayName
    DisplayName CommoditySymbol
    name CommoditySymbol
    droppedName Int
    1
          where
            droppedName :: CommoditySymbol
    droppedName = Int -> CommoditySymbol -> CommoditySymbol
    accountNameDrop (ReportOpts -> Int
    drop_ ReportOpts
    ropts) CommoditySymbol
    name
            leaf :: CommoditySymbol
    leaf = [CommoditySymbol] -> CommoditySymbol
    accountNameFromComponents ([CommoditySymbol] -> CommoditySymbol)
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> CommoditySymbol
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [CommoditySymbol] -> [CommoditySymbol]
    forall a. [a] -> [a]
    reverse ([CommoditySymbol] -> [CommoditySymbol])
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (CommoditySymbol -> CommoditySymbol)
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map CommoditySymbol -> CommoditySymbol
    accountLeafName ([CommoditySymbol] -> CommoditySymbol)
    -> [CommoditySymbol] -> CommoditySymbol
    forall a b. (a -> b) -> a -> b
    $
                CommoditySymbol
    droppedName CommoditySymbol -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. a -> [a] -> [a]
    : (CommoditySymbol -> Bool) -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. (a -> Bool) -> [a] -> [a]
    takeWhile CommoditySymbol -> Bool
    notDisplayed [CommoditySymbol]
    parents

            level :: Int
    level = Int -> Int -> Int
    forall a. Ord a => a -> a -> a
    max Int
    0 (Int -> Int) -> Int -> Int
    forall a b. (a -> b) -> a -> b
    $ CommoditySymbol -> Int
    accountNameLevel CommoditySymbol
    name Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - ReportOpts -> Int
    drop_ ReportOpts
    ropts
            parents :: [CommoditySymbol]
    parents = Int -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. Int -> [a] -> [a]
    take (Int
    level Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1) ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ CommoditySymbol -> [CommoditySymbol]
    parentAccountNames CommoditySymbol
    name
            boringParents :: Int
    boringParents = if ReportOpts -> Bool
    no_elide_ ReportOpts
    ropts then Int
    0 else [CommoditySymbol] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length ([CommoditySymbol] -> Int) -> [CommoditySymbol] -> Int
    forall a b. (a -> b) -> a -> b
    $ (CommoditySymbol -> Bool) -> [CommoditySymbol] -> [CommoditySymbol]
    forall a. (a -> Bool) -> [a] -> [a]
    filter CommoditySymbol -> Bool
    notDisplayed [CommoditySymbol]
    parents
            notDisplayed :: CommoditySymbol -> Bool
    notDisplayed = Bool -> Bool
    not (Bool -> Bool)
    -> (CommoditySymbol -> Bool) -> CommoditySymbol -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (CommoditySymbol
    -> HashMap CommoditySymbol (Map DateSpan Account) -> Bool
    forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
    `HM.member` HashMap CommoditySymbol (Map DateSpan Account)
    displayedAccts)

        -- Accounts interesting for their own sake
        isInteresting :: CommoditySymbol -> t Account -> Bool
    isInteresting CommoditySymbol
    name t Account
    amts =
            Int
    d Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    <= Int
    qdepth                                  -- Throw out anything too deep
            Bool -> Bool -> Bool
    && ( CommoditySymbol
    name CommoditySymbol -> Set CommoditySymbol -> Bool
    forall a. Ord a => a -> Set a -> Bool
    `Set.member` Set CommoditySymbol
    unelidableaccts     -- Unelidable accounts should be kept unless too deep
               Bool -> Bool -> Bool
    ||(ReportOpts -> Bool
    empty_ ReportOpts
    ropts Bool -> Bool -> Bool
    && t Account -> Bool
    keepWhenEmpty t Account
    amts)  -- Keep empty accounts when called with --empty
               Bool -> Bool -> Bool
    || Bool -> Bool
    not ((Account -> MixedAmount) -> t Account -> Bool
    forall {t :: * -> *} {a}.
    Foldable t =>
    (a -> MixedAmount) -> t a -> Bool
    isZeroRow Account -> MixedAmount
    balance t Account
    amts)         -- Keep everything with a non-zero balance in the row
               )
          where
            d :: Int
    d = CommoditySymbol -> Int
    accountNameLevel CommoditySymbol
    name
            keepWhenEmpty :: t Account -> Bool
    keepWhenEmpty = case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
                AccountListMode
    ALFlat -> Bool -> t Account -> Bool
    forall a b. a -> b -> a
    const Bool
    True          -- Keep all empty accounts in flat mode
                AccountListMode
    ALTree -> (Account -> Bool) -> t Account -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all ([Account] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null ([Account] -> Bool) -> (Account -> [Account]) -> Account -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Account -> [Account]
    asubs)  -- Keep only empty leaves in tree mode
            balance :: Account -> MixedAmount
    balance = MixedAmount -> MixedAmount
    maybeStripPrices (MixedAmount -> MixedAmount)
    -> (Account -> MixedAmount) -> Account -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
                AccountListMode
    ALTree | Int
    d Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    qdepth -> Account -> MixedAmount
    aibalance
                AccountListMode
    _                    -> Account -> MixedAmount
    aebalance
              where maybeStripPrices :: MixedAmount -> MixedAmount
    maybeStripPrices = if ReportOpts -> Maybe ConversionOp
    conversionop_ ReportOpts
    ropts Maybe ConversionOp -> Maybe ConversionOp -> Bool
    forall a. Eq a => a -> a -> Bool
    == ConversionOp -> Maybe ConversionOp
    forall a. a -> Maybe a
    Just ConversionOp
    NoConversionOp then MixedAmount -> MixedAmount
    forall a. a -> a
    id else MixedAmount -> MixedAmount
    mixedAmountStripPrices

        -- Accounts interesting because they are a fork for interesting subaccounts
        interestingParents :: HashMap CommoditySymbol Int
    interestingParents = [Char]
    -> HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "interestingParents" (HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int)
    -> HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int
    forall a b. (a -> b) -> a -> b
    $ case ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts of
            AccountListMode
    ALTree -> (CommoditySymbol -> Int -> Bool)
    -> HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int
    forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
    HM.filterWithKey CommoditySymbol -> Int -> Bool
    hasEnoughSubs HashMap CommoditySymbol Int
    numSubs
            AccountListMode
    ALFlat -> HashMap CommoditySymbol Int
    forall a. Monoid a => a
    mempty
          where
            hasEnoughSubs :: CommoditySymbol -> Int -> Bool
    hasEnoughSubs CommoditySymbol
    name Int
    nsubs = Int
    nsubs Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    >= Int
    minSubs Bool -> Bool -> Bool
    && CommoditySymbol -> Int
    accountNameLevel CommoditySymbol
    name Int -> Int -> Bool
    forall a. Ord a => a -> a -> Bool
    > ReportOpts -> Int
    drop_ ReportOpts
    ropts
            minSubs :: Int
    minSubs = if ReportOpts -> Bool
    no_elide_ ReportOpts
    ropts then Int
    1 else Int
    2

        isZeroRow :: (a -> MixedAmount) -> t a -> Bool
    isZeroRow a -> MixedAmount
    balance = (a -> Bool) -> t a -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    all (MixedAmount -> Bool
    mixedAmountLooksZero (MixedAmount -> Bool) -> (a -> MixedAmount) -> a -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . a -> MixedAmount
    balance)
        qdepth :: Int
    qdepth = Int -> Maybe Int -> Int
    forall a. a -> Maybe a -> a
    fromMaybe Int
    forall a. Bounded a => a
    maxBound (Maybe Int -> Int) -> Maybe Int -> Int
    forall a b. (a -> b) -> a -> b
    $ Query -> Maybe Int
    queryDepth Query
    query
        numSubs :: HashMap CommoditySymbol Int
    numSubs = [CommoditySymbol] -> HashMap CommoditySymbol Int
    subaccountTallies ([CommoditySymbol] -> HashMap CommoditySymbol Int)
    -> (HashMap CommoditySymbol (Map DateSpan Account)
        -> [CommoditySymbol])
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . HashMap CommoditySymbol (Map DateSpan Account) -> [CommoditySymbol]
    forall k v. HashMap k v -> [k]
    HM.keys (HashMap CommoditySymbol (Map DateSpan Account)
     -> HashMap CommoditySymbol Int)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol Int
    forall a b. (a -> b) -> a -> b
    $ (CommoditySymbol -> Map DateSpan Account -> Bool)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    -> HashMap CommoditySymbol (Map DateSpan Account)
    forall k v. (k -> v -> Bool) -> HashMap k v -> HashMap k v
    HM.filterWithKey CommoditySymbol -> Map DateSpan Account -> Bool
    forall {t :: * -> *}.
    Foldable t =>
    CommoditySymbol -> t Account -> Bool
    isInteresting HashMap CommoditySymbol (Map DateSpan Account)
    valuedaccts

    -- | Sort the rows by amount or by account declaration order.
    sortRows :: ReportOpts -> Journal -> [MultiBalanceReportRow] -> [MultiBalanceReportRow]
    sortRows :: ReportOpts
    -> Journal
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortRows ReportOpts
    ropts Journal
    j
        | ReportOpts -> Bool
    sort_amount_ ReportOpts
    ropts, AccountListMode
    ALTree <- ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts = [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortTreeMBRByAmount
        | ReportOpts -> Bool
    sort_amount_ ReportOpts
    ropts, AccountListMode
    ALFlat <- ReportOpts -> AccountListMode
    accountlistmode_ ReportOpts
    ropts = [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortFlatMBRByAmount
        | Bool
    otherwise                                            = [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortMBRByAccountDeclaration
      where
        -- Sort the report rows, representing a tree of accounts, by row total at each level.
        -- Similar to sortMBRByAccountDeclaration/sortAccountNamesByDeclaration.
        sortTreeMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
        sortTreeMBRByAmount :: [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortTreeMBRByAmount [PeriodicReportRow DisplayName MixedAmount]
    rows = (CommoditySymbol
     -> Maybe (PeriodicReportRow DisplayName MixedAmount))
    -> [CommoditySymbol] -> [PeriodicReportRow DisplayName MixedAmount]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe (CommoditySymbol
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    -> Maybe (PeriodicReportRow DisplayName MixedAmount)
    forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
    `HM.lookup` HashMap CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    rowMap) [CommoditySymbol]
    sortedanames
          where
            accounttree :: Account
    accounttree = CommoditySymbol -> [CommoditySymbol] -> Account
    accountTree CommoditySymbol
    "root" ([CommoditySymbol] -> Account) -> [CommoditySymbol] -> Account
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol)
    -> [PeriodicReportRow DisplayName MixedAmount] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName [PeriodicReportRow DisplayName MixedAmount]
    rows
            rowMap :: HashMap CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    rowMap = [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
    HM.fromList ([(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
     -> HashMap
          CommoditySymbol (PeriodicReportRow DisplayName MixedAmount))
    -> [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, PeriodicReportRow DisplayName MixedAmount))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\PeriodicReportRow DisplayName MixedAmount
    row -> (PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName MixedAmount
    row, PeriodicReportRow DisplayName MixedAmount
    row)) [PeriodicReportRow DisplayName MixedAmount]
    rows
            -- Set the inclusive balance of an account from the rows, or sum the
            -- subaccounts if it's not present
            accounttreewithbals :: Account
    accounttreewithbals = (Account -> Account) -> Account -> Account
    mapAccounts Account -> Account
    setibalance Account
    accounttree
            setibalance :: Account -> Account
    setibalance Account
    a = Account
    a{aibalance = maybe (maSum . map aibalance $ asubs a) prrTotal $
                                              HM.lookup (aname a) rowMap}
            sortedaccounttree :: Account
    sortedaccounttree = NormalSign -> Account -> Account
    sortAccountTreeByAmount (NormalSign -> Maybe NormalSign -> NormalSign
    forall a. a -> Maybe a -> a
    fromMaybe NormalSign
    NormallyPositive (Maybe NormalSign -> NormalSign) -> Maybe NormalSign -> NormalSign
    forall a b. (a -> b) -> a -> b
    $ ReportOpts -> Maybe NormalSign
    normalbalance_ ReportOpts
    ropts) Account
    accounttreewithbals
            sortedanames :: [CommoditySymbol]
    sortedanames = (Account -> CommoditySymbol) -> [Account] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map Account -> CommoditySymbol
    aname ([Account] -> [CommoditySymbol]) -> [Account] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ Int -> [Account] -> [Account]
    forall a. Int -> [a] -> [a]
    drop Int
    1 ([Account] -> [Account]) -> [Account] -> [Account]
    forall a b. (a -> b) -> a -> b
    $ Account -> [Account]
    flattenAccounts Account
    sortedaccounttree

        -- Sort the report rows, representing a flat account list, by row total (and then account name).
        sortFlatMBRByAmount :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
        sortFlatMBRByAmount :: [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortFlatMBRByAmount = case NormalSign -> Maybe NormalSign -> NormalSign
    forall a. a -> Maybe a -> a
    fromMaybe NormalSign
    NormallyPositive (Maybe NormalSign -> NormalSign) -> Maybe NormalSign -> NormalSign
    forall a b. (a -> b) -> a -> b
    $ ReportOpts -> Maybe NormalSign
    normalbalance_ ReportOpts
    ropts of
            NormalSign
    NormallyPositive -> (PeriodicReportRow DisplayName MixedAmount
     -> (Down MixedAmount, CommoditySymbol))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (\PeriodicReportRow DisplayName MixedAmount
    r -> (MixedAmount -> Down MixedAmount
    forall a. a -> Down a
    Down (MixedAmount -> Down MixedAmount)
    -> MixedAmount -> Down MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow DisplayName MixedAmount -> MixedAmount
    forall {a}. PeriodicReportRow a MixedAmount -> MixedAmount
    amt PeriodicReportRow DisplayName MixedAmount
    r, PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName MixedAmount
    r))
            NormalSign
    NormallyNegative -> (PeriodicReportRow DisplayName MixedAmount
     -> (MixedAmount, CommoditySymbol))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall b a. Ord b => (a -> b) -> [a] -> [a]
    sortOn (\PeriodicReportRow DisplayName MixedAmount
    r -> (PeriodicReportRow DisplayName MixedAmount -> MixedAmount
    forall {a}. PeriodicReportRow a MixedAmount -> MixedAmount
    amt PeriodicReportRow DisplayName MixedAmount
    r, PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName MixedAmount
    r))
          where amt :: PeriodicReportRow a MixedAmount -> MixedAmount
    amt = MixedAmount -> MixedAmount
    mixedAmountStripPrices (MixedAmount -> MixedAmount)
    -> (PeriodicReportRow a MixedAmount -> MixedAmount)
    -> PeriodicReportRow a MixedAmount
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodicReportRow a MixedAmount -> MixedAmount
    forall a b. PeriodicReportRow a b -> b
    prrTotal

        -- Sort the report rows by account declaration order then account name.
        sortMBRByAccountDeclaration :: [MultiBalanceReportRow] -> [MultiBalanceReportRow]
        sortMBRByAccountDeclaration :: [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    sortMBRByAccountDeclaration [PeriodicReportRow DisplayName MixedAmount]
    rows = [CommoditySymbol]
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall b.
    [CommoditySymbol]
    -> [PeriodicReportRow DisplayName b]
    -> [PeriodicReportRow DisplayName b]
    sortRowsLike [CommoditySymbol]
    sortedanames [PeriodicReportRow DisplayName MixedAmount]
    rows
          where
            sortedanames :: [CommoditySymbol]
    sortedanames = Journal -> Bool -> [CommoditySymbol] -> [CommoditySymbol]
    sortAccountNamesByDeclaration Journal
    j (ReportOpts -> Bool
    tree_ ReportOpts
    ropts) ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol)
    -> [PeriodicReportRow DisplayName MixedAmount] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName [PeriodicReportRow DisplayName MixedAmount]
    rows

    -- | Build the report totals row.
    --
    -- Calculate the column totals. These are always the sum of column amounts.
    calculateTotalsRow :: ReportOpts -> [MultiBalanceReportRow] -> PeriodicReportRow () MixedAmount
    calculateTotalsRow :: ReportOpts
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> PeriodicReportRow () MixedAmount
    calculateTotalsRow ReportOpts
    ropts [PeriodicReportRow DisplayName MixedAmount]
    rows =
        ()
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow () [MixedAmount]
    coltotals MixedAmount
    grandtotal MixedAmount
    grandaverage
      where
        isTopRow :: PeriodicReportRow DisplayName a -> Bool
    isTopRow PeriodicReportRow DisplayName a
    row = ReportOpts -> Bool
    flat_ ReportOpts
    ropts Bool -> Bool -> Bool
    || Bool -> Bool
    not ((CommoditySymbol -> Bool) -> [CommoditySymbol] -> Bool
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
    any (CommoditySymbol
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    -> Bool
    forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
    `HM.member` HashMap CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    rowMap) [CommoditySymbol]
    parents)
          where parents :: [CommoditySymbol]
    parents = [CommoditySymbol] -> [CommoditySymbol]
    forall a. HasCallStack => [a] -> [a]
    init ([CommoditySymbol] -> [CommoditySymbol])
    -> (CommoditySymbol -> [CommoditySymbol])
    -> CommoditySymbol
    -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . CommoditySymbol -> [CommoditySymbol]
    expandAccountName (CommoditySymbol -> [CommoditySymbol])
    -> CommoditySymbol -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow DisplayName a -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName a
    row
        rowMap :: HashMap CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    rowMap = [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
    HM.fromList ([(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
     -> HashMap
          CommoditySymbol (PeriodicReportRow DisplayName MixedAmount))
    -> [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    -> HashMap
         CommoditySymbol (PeriodicReportRow DisplayName MixedAmount)
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, PeriodicReportRow DisplayName MixedAmount))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, PeriodicReportRow DisplayName MixedAmount)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\PeriodicReportRow DisplayName MixedAmount
    row -> (PeriodicReportRow DisplayName MixedAmount -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName MixedAmount
    row, PeriodicReportRow DisplayName MixedAmount
    row)) [PeriodicReportRow DisplayName MixedAmount]
    rows

        colamts :: [[MixedAmount]]
    colamts = [[MixedAmount]] -> [[MixedAmount]]
    forall a. [[a]] -> [[a]]
    transpose ([[MixedAmount]] -> [[MixedAmount]])
    -> ([PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]])
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [[MixedAmount]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (PeriodicReportRow DisplayName MixedAmount -> [MixedAmount])
    -> [PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount -> [MixedAmount]
    forall a b. PeriodicReportRow a b -> [b]
    prrAmounts ([PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]])
    -> [PeriodicReportRow DisplayName MixedAmount] -> [[MixedAmount]]
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName MixedAmount -> Bool)
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a. (a -> Bool) -> [a] -> [a]
    filter PeriodicReportRow DisplayName MixedAmount -> Bool
    forall {a}. PeriodicReportRow DisplayName a -> Bool
    isTopRow [PeriodicReportRow DisplayName MixedAmount]
    rows

        [MixedAmount]
    coltotals :: [MixedAmount] = [Char] -> [MixedAmount] -> [MixedAmount]
    forall a. Show a => [Char] -> a -> a
    dbg5 [Char]
    "coltotals" ([MixedAmount] -> [MixedAmount]) -> [MixedAmount] -> [MixedAmount]
    forall a b. (a -> b) -> a -> b
    $ ([MixedAmount] -> MixedAmount) -> [[MixedAmount]] -> [MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map [MixedAmount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum [[MixedAmount]]
    colamts

        -- Calculate the grand total and average. These are always the sum/average
        -- of the column totals.
        -- Total for a cumulative/historical report is always the last column.
        grandtotal :: MixedAmount
    grandtotal = case ReportOpts -> BalanceAccumulation
    balanceaccum_ ReportOpts
    ropts of
            BalanceAccumulation
    PerPeriod -> [MixedAmount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t MixedAmount -> MixedAmount
    maSum [MixedAmount]
    coltotals
            BalanceAccumulation
    _         -> MixedAmount -> [MixedAmount] -> MixedAmount
    forall a. a -> [a] -> a
    lastDef MixedAmount
    nullmixedamt [MixedAmount]
    coltotals
        grandaverage :: MixedAmount
    grandaverage = [MixedAmount] -> MixedAmount
    averageMixedAmounts [MixedAmount]
    coltotals

    -- | Map the report rows to percentages if needed
    reportPercent :: ReportOpts -> MultiBalanceReport -> MultiBalanceReport
    reportPercent :: ReportOpts -> MultiBalanceReport -> MultiBalanceReport
    reportPercent ReportOpts
    ropts report :: MultiBalanceReport
    report@(PeriodicReport [DateSpan]
    spans [PeriodicReportRow DisplayName MixedAmount]
    rows PeriodicReportRow () MixedAmount
    totalrow)
      | ReportOpts -> Bool
    percent_ ReportOpts
    ropts = [DateSpan]
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> PeriodicReportRow () MixedAmount
    -> MultiBalanceReport
    forall a b.
    [DateSpan]
    -> [PeriodicReportRow a b]
    -> PeriodicReportRow () b
    -> PeriodicReport a b
    PeriodicReport [DateSpan]
    spans ((PeriodicReportRow DisplayName MixedAmount
     -> PeriodicReportRow DisplayName MixedAmount)
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [PeriodicReportRow DisplayName MixedAmount]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall {a}.
    PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
    percentRow [PeriodicReportRow DisplayName MixedAmount]
    rows) (PeriodicReportRow () MixedAmount
    -> PeriodicReportRow () MixedAmount
    forall {a}.
    PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
    percentRow PeriodicReportRow () MixedAmount
    totalrow)
      | Bool
    otherwise      = MultiBalanceReport
    report
      where
        percentRow :: PeriodicReportRow a MixedAmount -> PeriodicReportRow a MixedAmount
    percentRow (PeriodicReportRow a
    name [MixedAmount]
    rowvals MixedAmount
    rowtotal MixedAmount
    rowavg) =
          a
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow a MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow a
    name
            ((MixedAmount -> MixedAmount -> MixedAmount)
    -> [MixedAmount] -> [MixedAmount] -> [MixedAmount]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith MixedAmount -> MixedAmount -> MixedAmount
    perdivide [MixedAmount]
    rowvals ([MixedAmount] -> [MixedAmount]) -> [MixedAmount] -> [MixedAmount]
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow () MixedAmount -> [MixedAmount]
    forall a b. PeriodicReportRow a b -> [b]
    prrAmounts PeriodicReportRow () MixedAmount
    totalrow)
            (MixedAmount -> MixedAmount -> MixedAmount
    perdivide MixedAmount
    rowtotal (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow () MixedAmount -> MixedAmount
    forall a b. PeriodicReportRow a b -> b
    prrTotal PeriodicReportRow () MixedAmount
    totalrow)
            (MixedAmount -> MixedAmount -> MixedAmount
    perdivide MixedAmount
    rowavg (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow () MixedAmount -> MixedAmount
    forall a b. PeriodicReportRow a b -> b
    prrAverage PeriodicReportRow () MixedAmount
    totalrow)

    -- | Transpose a Map of HashMaps to a HashMap of Maps.
    --
    -- Makes sure that all DateSpans are present in all rows.
    transposeMap :: [(DateSpan, HashMap AccountName a)]
                 -> HashMap AccountName (Map DateSpan a)
    transposeMap :: forall a.
    [(DateSpan, HashMap CommoditySymbol a)]
    -> HashMap CommoditySymbol (Map DateSpan a)
    transposeMap = ((DateSpan, HashMap CommoditySymbol a)
     -> HashMap CommoditySymbol (Map DateSpan a)
     -> HashMap CommoditySymbol (Map DateSpan a))
    -> HashMap CommoditySymbol (Map DateSpan a)
    -> [(DateSpan, HashMap CommoditySymbol a)]
    -> HashMap CommoditySymbol (Map DateSpan a)
    forall a b. (a -> b -> b) -> b -> [a] -> b
    forall (t :: * -> *) a b.
    Foldable t =>
    (a -> b -> b) -> b -> t a -> b
    foldr ((DateSpan
     -> HashMap CommoditySymbol a
     -> HashMap CommoditySymbol (Map DateSpan a)
     -> HashMap CommoditySymbol (Map DateSpan a))
    -> (DateSpan, HashMap CommoditySymbol a)
    -> HashMap CommoditySymbol (Map DateSpan a)
    -> HashMap CommoditySymbol (Map DateSpan a)
    forall a b c. (a -> b -> c) -> (a, b) -> c
    uncurry DateSpan
    -> HashMap CommoditySymbol a
    -> HashMap CommoditySymbol (Map DateSpan a)
    -> HashMap CommoditySymbol (Map DateSpan a)
    forall {k} {p} {v}.
    (Hashable k, Ord p) =>
    p -> HashMap k v -> HashMap k (Map p v) -> HashMap k (Map p v)
    addSpan) HashMap CommoditySymbol (Map DateSpan a)
    forall a. Monoid a => a
    mempty
      where
        addSpan :: p -> HashMap k v -> HashMap k (Map p v) -> HashMap k (Map p v)
    addSpan p
    spn HashMap k v
    acctmap HashMap k (Map p v)
    seen = (k -> v -> HashMap k (Map p v) -> HashMap k (Map p v))
    -> HashMap k (Map p v) -> HashMap k v -> HashMap k (Map p v)
    forall k v a. (k -> v -> a -> a) -> a -> HashMap k v -> a
    HM.foldrWithKey (p -> k -> v -> HashMap k (Map p v) -> HashMap k (Map p v)
    forall {k} {p} {p}.
    (Hashable k, Ord p) =>
    p -> k -> p -> HashMap k (Map p p) -> HashMap k (Map p p)
    addAcctSpan p
    spn) HashMap k (Map p v)
    seen HashMap k v
    acctmap

        addAcctSpan :: p -> k -> p -> HashMap k (Map p p) -> HashMap k (Map p p)
    addAcctSpan p
    spn k
    acct p
    a = (Maybe (Map p p) -> Maybe (Map p p))
    -> k -> HashMap k (Map p p) -> HashMap k (Map p p)
    forall k v.
    (Eq k, Hashable k) =>
    (Maybe v -> Maybe v) -> k -> HashMap k v -> HashMap k v
    HM.alter Maybe (Map p p) -> Maybe (Map p p)
    f k
    acct
          where f :: Maybe (Map p p) -> Maybe (Map p p)
    f = Map p p -> Maybe (Map p p)
    forall a. a -> Maybe a
    Just (Map p p -> Maybe (Map p p))
    -> (Maybe (Map p p) -> Map p p)
    -> Maybe (Map p p)
    -> Maybe (Map p p)
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . p -> p -> Map p p -> Map p p
    forall k a. Ord k => k -> a -> Map k a -> Map k a
    M.insert p
    spn p
    a (Map p p -> Map p p)
    -> (Maybe (Map p p) -> Map p p) -> Maybe (Map p p) -> Map p p
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Map p p -> Maybe (Map p p) -> Map p p
    forall a. a -> Maybe a -> a
    fromMaybe Map p p
    forall a. Monoid a => a
    mempty

    -- | A sorting helper: sort a list of things (eg report rows) keyed by account name
    -- to match the provided ordering of those same account names.
    sortRowsLike :: [AccountName] -> [PeriodicReportRow DisplayName b] -> [PeriodicReportRow DisplayName b]
    sortRowsLike :: forall b.
    [CommoditySymbol]
    -> [PeriodicReportRow DisplayName b]
    -> [PeriodicReportRow DisplayName b]
    sortRowsLike [CommoditySymbol]
    sortedas [PeriodicReportRow DisplayName b]
    rows = (CommoditySymbol -> Maybe (PeriodicReportRow DisplayName b))
    -> [CommoditySymbol] -> [PeriodicReportRow DisplayName b]
    forall a b. (a -> Maybe b) -> [a] -> [b]
    mapMaybe (CommoditySymbol
    -> HashMap CommoditySymbol (PeriodicReportRow DisplayName b)
    -> Maybe (PeriodicReportRow DisplayName b)
    forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
    `HM.lookup` HashMap CommoditySymbol (PeriodicReportRow DisplayName b)
    rowMap) [CommoditySymbol]
    sortedas
      where rowMap :: HashMap CommoditySymbol (PeriodicReportRow DisplayName b)
    rowMap = [(CommoditySymbol, PeriodicReportRow DisplayName b)]
    -> HashMap CommoditySymbol (PeriodicReportRow DisplayName b)
    forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
    HM.fromList ([(CommoditySymbol, PeriodicReportRow DisplayName b)]
     -> HashMap CommoditySymbol (PeriodicReportRow DisplayName b))
    -> [(CommoditySymbol, PeriodicReportRow DisplayName b)]
    -> HashMap CommoditySymbol (PeriodicReportRow DisplayName b)
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName b
     -> (CommoditySymbol, PeriodicReportRow DisplayName b))
    -> [PeriodicReportRow DisplayName b]
    -> [(CommoditySymbol, PeriodicReportRow DisplayName b)]
    forall a b. (a -> b) -> [a] -> [b]
    map (\PeriodicReportRow DisplayName b
    row -> (PeriodicReportRow DisplayName b -> CommoditySymbol
    forall a. PeriodicReportRow DisplayName a -> CommoditySymbol
    prrFullName PeriodicReportRow DisplayName b
    row, PeriodicReportRow DisplayName b
    row)) [PeriodicReportRow DisplayName b]
    rows

    -- | Given a list of account names, find all forking parent accounts, i.e.
    -- those which fork between different branches
    subaccountTallies :: [AccountName] -> HashMap AccountName Int
    subaccountTallies :: [CommoditySymbol] -> HashMap CommoditySymbol Int
    subaccountTallies = (CommoditySymbol
     -> HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int)
    -> HashMap CommoditySymbol Int
    -> [CommoditySymbol]
    -> HashMap CommoditySymbol Int
    forall a b. (a -> b -> b) -> b -> [a] -> b
    forall (t :: * -> *) a b.
    Foldable t =>
    (a -> b -> b) -> b -> t a -> b
    foldr CommoditySymbol
    -> HashMap CommoditySymbol Int -> HashMap CommoditySymbol Int
    forall {v}.
    Num v =>
    CommoditySymbol
    -> HashMap CommoditySymbol v -> HashMap CommoditySymbol v
    incrementParent HashMap CommoditySymbol Int
    forall a. Monoid a => a
    mempty ([CommoditySymbol] -> HashMap CommoditySymbol Int)
    -> ([CommoditySymbol] -> [CommoditySymbol])
    -> [CommoditySymbol]
    -> HashMap CommoditySymbol Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [CommoditySymbol] -> [CommoditySymbol]
    expandAccountNames
      where incrementParent :: CommoditySymbol
    -> HashMap CommoditySymbol v -> HashMap CommoditySymbol v
    incrementParent CommoditySymbol
    a = (v -> v -> v)
    -> CommoditySymbol
    -> v
    -> HashMap CommoditySymbol v
    -> HashMap CommoditySymbol v
    forall k v.
    (Eq k, Hashable k) =>
    (v -> v -> v) -> k -> v -> HashMap k v -> HashMap k v
    HM.insertWith v -> v -> v
    forall a. Num a => a -> a -> a
    (+) (CommoditySymbol -> CommoditySymbol
    parentAccountName CommoditySymbol
    a) v
    1

    -- | A helper: what percentage is the second mixed amount of the first ?
    -- Keeps the sign of the first amount.
    -- Uses unifyMixedAmount to unify each argument and then divides them.
    -- Both amounts should be in the same, single commodity.
    -- This can call error if the arguments are not right.
    perdivide :: MixedAmount -> MixedAmount -> MixedAmount
    perdivide :: MixedAmount -> MixedAmount -> MixedAmount
    perdivide MixedAmount
    a MixedAmount
    b = MixedAmount -> Maybe MixedAmount -> MixedAmount
    forall a. a -> Maybe a -> a
    fromMaybe ([Char] -> MixedAmount
    forall a. [Char] -> a
    error' [Char]
    errmsg) (Maybe MixedAmount -> MixedAmount)
    -> Maybe MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ do  -- PARTIAL:
        Amount
    a' <- MixedAmount -> Maybe Amount
    unifyMixedAmount MixedAmount
    a
        Amount
    b' <- MixedAmount -> Maybe Amount
    unifyMixedAmount MixedAmount
    b
        Bool -> Maybe ()
    forall (f :: * -> *). Alternative f => Bool -> f ()
    guard (Bool -> Maybe ()) -> Bool -> Maybe ()
    forall a b. (a -> b) -> a -> b
    $ Amount -> Bool
    amountIsZero Amount
    a' Bool -> Bool -> Bool
    || Amount -> Bool
    amountIsZero Amount
    b' Bool -> Bool -> Bool
    || Amount -> CommoditySymbol
    acommodity Amount
    a' CommoditySymbol -> CommoditySymbol -> Bool
    forall a. Eq a => a -> a -> Bool
    == Amount -> CommoditySymbol
    acommodity Amount
    b'
        MixedAmount -> Maybe MixedAmount
    forall a. a -> Maybe a
    forall (m :: * -> *) a. Monad m => a -> m a
    return (MixedAmount -> Maybe MixedAmount)
    -> MixedAmount -> Maybe MixedAmount
    forall a b. (a -> b) -> a -> b
    $ [Amount] -> MixedAmount
    forall (t :: * -> *). Foldable t => t Amount -> MixedAmount
    mixed [Quantity -> Amount
    per (Quantity -> Amount) -> Quantity -> Amount
    forall a b. (a -> b) -> a -> b
    $ if Amount -> Quantity
    aquantity Amount
    b' Quantity -> Quantity -> Bool
    forall a. Eq a => a -> a -> Bool
    == Quantity
    0 then Quantity
    0 else Amount -> Quantity
    aquantity Amount
    a' Quantity -> Quantity -> Quantity
    forall a. Fractional a => a -> a -> a
    / Quantity -> Quantity
    forall a. Num a => a -> a
    abs (Amount -> Quantity
    aquantity Amount
    b') Quantity -> Quantity -> Quantity
    forall a. Num a => a -> a -> a
    * Quantity
    100]
      where errmsg :: [Char]
    errmsg = [Char]
    "Cannot calculate percentages if accounts have different commodities (Hint: Try --cost, -V or similar flags.)"

    -- Add the values of two accounts. Should be right-biased, since it's used
    -- in scanl, so other properties (such as anumpostings) stay in the right place
    sumAcct :: Account -> Account -> Account
    sumAcct :: Account -> Account -> Account
    sumAcct Account{aibalance :: Account -> MixedAmount
    aibalance=MixedAmount
    i1,aebalance :: Account -> MixedAmount
    aebalance=MixedAmount
    e1} a :: Account
    a@Account{aibalance :: Account -> MixedAmount
    aibalance=MixedAmount
    i2,aebalance :: Account -> MixedAmount
    aebalance=MixedAmount
    e2} =
        Account
    a{aibalance = i1 `maPlus` i2, aebalance = e1 `maPlus` e2}

    -- Subtract the values in one account from another. Should be left-biased.
    subtractAcct :: Account -> Account -> Account
    subtractAcct :: Account -> Account -> Account
    subtractAcct a :: Account
    a@Account{aibalance :: Account -> MixedAmount
    aibalance=MixedAmount
    i1,aebalance :: Account -> MixedAmount
    aebalance=MixedAmount
    e1} Account{aibalance :: Account -> MixedAmount
    aibalance=MixedAmount
    i2,aebalance :: Account -> MixedAmount
    aebalance=MixedAmount
    e2} =
        Account
    a{aibalance = i1 `maMinus` i2, aebalance = e1 `maMinus` e2}

    -- | Extract period changes from a cumulative list
    periodChanges :: Account -> Map k Account -> Map k Account
    periodChanges :: forall k. Account -> Map k Account -> Map k Account
    periodChanges Account
    start Map k Account
    amtmap =
        [(k, Account)] -> Map k Account
    forall k a. [(k, a)] -> Map k a
    M.fromDistinctAscList ([(k, Account)] -> Map k Account)
    -> ([Account] -> [(k, Account)]) -> [Account] -> Map k Account
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [k] -> [Account] -> [(k, Account)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [k]
    dates ([Account] -> Map k Account) -> [Account] -> Map k Account
    forall a b. (a -> b) -> a -> b
    $ (Account -> Account -> Account)
    -> [Account] -> [Account] -> [Account]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith Account -> Account -> Account
    subtractAcct [Account]
    amts (Account
    startAccount -> [Account] -> [Account]
    forall a. a -> [a] -> [a]
    :[Account]
    amts)
      where ([k]
    dates, [Account]
    amts) = [(k, Account)] -> ([k], [Account])
    forall a b. [(a, b)] -> ([a], [b])
    unzip ([(k, Account)] -> ([k], [Account]))
    -> [(k, Account)] -> ([k], [Account])
    forall a b. (a -> b) -> a -> b
    $ Map k Account -> [(k, Account)]
    forall k a. Map k a -> [(k, a)]
    M.toAscList Map k Account
    amtmap

    -- | Calculate a cumulative sum from a list of period changes.
    cumulativeSum :: Account -> Map DateSpan Account -> Map DateSpan Account
    cumulativeSum :: Account -> Map DateSpan Account -> Map DateSpan Account
    cumulativeSum Account
    start = (Account, Map DateSpan Account) -> Map DateSpan Account
    forall a b. (a, b) -> b
    snd ((Account, Map DateSpan Account) -> Map DateSpan Account)
    -> (Map DateSpan Account -> (Account, Map DateSpan Account))
    -> Map DateSpan Account
    -> Map DateSpan Account
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Account -> Account -> (Account, Account))
    -> Account
    -> Map DateSpan Account
    -> (Account, Map DateSpan Account)
    forall a b c k. (a -> b -> (a, c)) -> a -> Map k b -> (a, Map k c)
    M.mapAccum (\Account
    a Account
    b -> let s :: Account
    s = Account -> Account -> Account
    sumAcct Account
    a Account
    b in (Account
    s, Account
    s)) Account
    start

    -- | Given a table representing a multi-column balance report (for example,
    -- made using 'balanceReportAsTable'), render it in a format suitable for
    -- console output. Amounts with more than two commodities will be elided
    -- unless --no-elide is used.
    balanceReportTableAsText :: ReportOpts -> Tab.Table T.Text T.Text WideBuilder -> TB.Builder
    balanceReportTableAsText :: ReportOpts
    -> Table CommoditySymbol CommoditySymbol WideBuilder -> Builder
    balanceReportTableAsText ReportOpts{Bool
    Int
    [CommoditySymbol]
    [Status]
    Maybe Int
    Maybe CommoditySymbol
    Maybe NormalSign
    Maybe ValuationType
    Maybe ConversionOp
    Interval
    Period
    StringFormat
    Layout
    AccountListMode
    BalanceAccumulation
    BalanceCalculation
    infer_prices_ :: ReportOpts -> Bool
    value_ :: ReportOpts -> Maybe ValuationType
    period_ :: ReportOpts -> Period
    date2_ :: ReportOpts -> Bool
    conversionop_ :: ReportOpts -> Maybe ConversionOp
    accountlistmode_ :: ReportOpts -> AccountListMode
    declared_ :: ReportOpts -> Bool
    balanceaccum_ :: ReportOpts -> BalanceAccumulation
    balancecalc_ :: ReportOpts -> BalanceCalculation
    invert_ :: ReportOpts -> Bool
    drop_ :: ReportOpts -> Int
    no_elide_ :: ReportOpts -> Bool
    empty_ :: ReportOpts -> Bool
    sort_amount_ :: ReportOpts -> Bool
    normalbalance_ :: ReportOpts -> Maybe NormalSign
    percent_ :: ReportOpts -> Bool
    period_ :: Period
    interval_ :: Interval
    statuses_ :: [Status]
    conversionop_ :: Maybe ConversionOp
    value_ :: Maybe ValuationType
    infer_prices_ :: Bool
    depth_ :: Maybe Int
    date2_ :: Bool
    empty_ :: Bool
    no_elide_ :: Bool
    real_ :: Bool
    format_ :: StringFormat
    pretty_ :: Bool
    querystring_ :: [CommoditySymbol]
    average_ :: Bool
    related_ :: Bool
    txn_dates_ :: Bool
    balancecalc_ :: BalanceCalculation
    balanceaccum_ :: BalanceAccumulation
    budgetpat_ :: Maybe CommoditySymbol
    accountlistmode_ :: AccountListMode
    drop_ :: Int
    declared_ :: Bool
    row_total_ :: Bool
    no_total_ :: Bool
    summary_only_ :: Bool
    show_costs_ :: Bool
    sort_amount_ :: Bool
    percent_ :: Bool
    invert_ :: Bool
    normalbalance_ :: Maybe NormalSign
    color_ :: Bool
    transpose_ :: Bool
    layout_ :: Layout
    interval_ :: ReportOpts -> Interval
    statuses_ :: ReportOpts -> [Status]
    depth_ :: ReportOpts -> Maybe Int
    real_ :: ReportOpts -> Bool
    format_ :: ReportOpts -> StringFormat
    pretty_ :: ReportOpts -> Bool
    querystring_ :: ReportOpts -> [CommoditySymbol]
    average_ :: ReportOpts -> Bool
    related_ :: ReportOpts -> Bool
    txn_dates_ :: ReportOpts -> Bool
    budgetpat_ :: ReportOpts -> Maybe CommoditySymbol
    row_total_ :: ReportOpts -> Bool
    no_total_ :: ReportOpts -> Bool
    summary_only_ :: ReportOpts -> Bool
    show_costs_ :: ReportOpts -> Bool
    color_ :: ReportOpts -> Bool
    transpose_ :: ReportOpts -> Bool
    layout_ :: ReportOpts -> Layout
    ..} =
        TableOpts
    -> ([CommoditySymbol] -> [Cell])
    -> ((CommoditySymbol, [WideBuilder]) -> (Cell, [Cell]))
    -> Table CommoditySymbol CommoditySymbol WideBuilder
    -> Builder
    forall a ch rh.
    Show a =>
    TableOpts
    -> ([ch] -> [Cell])
    -> ((rh, [a]) -> (Cell, [Cell]))
    -> Table rh ch a
    -> Builder
    Tab.renderTableByRowsB TableOpts
    forall a. Default a => a
    def{Tab.tableBorders=False, Tab.prettyTable=pretty_} [CommoditySymbol] -> [Cell]
    renderCh (CommoditySymbol, [WideBuilder]) -> (Cell, [Cell])
    renderRow
      where
        renderCh :: [CommoditySymbol] -> [Cell]
    renderCh
          | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Layout
    LayoutBare Bool -> Bool -> Bool
    || Bool
    transpose_ = (CommoditySymbol -> Cell) -> [CommoditySymbol] -> [Cell]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Align -> CommoditySymbol -> Cell
    Tab.textCell Align
    Tab.TopRight)
          | Bool
    otherwise = ((CommoditySymbol -> Cell) -> CommoditySymbol -> Cell)
    -> [CommoditySymbol -> Cell] -> [CommoditySymbol] -> [Cell]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith (CommoditySymbol -> Cell) -> CommoditySymbol -> Cell
    forall a b. (a -> b) -> a -> b
    ($) (Align -> CommoditySymbol -> Cell
    Tab.textCell Align
    Tab.TopLeft (CommoditySymbol -> Cell)
    -> [CommoditySymbol -> Cell] -> [CommoditySymbol -> Cell]
    forall a. a -> [a] -> [a]
    : (CommoditySymbol -> Cell) -> [CommoditySymbol -> Cell]
    forall a. a -> [a]
    repeat (Align -> CommoditySymbol -> Cell
    Tab.textCell Align
    Tab.TopRight))

        renderRow :: (CommoditySymbol, [WideBuilder]) -> (Cell, [Cell])
    renderRow (CommoditySymbol
    rh, [WideBuilder]
    row)
          | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Layout
    LayoutBare Bool -> Bool -> Bool
    || Bool
    transpose_ =
              (Align -> CommoditySymbol -> Cell
    Tab.textCell Align
    Tab.TopLeft CommoditySymbol
    rh, (WideBuilder -> Cell) -> [WideBuilder] -> [Cell]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Align -> [WideBuilder] -> Cell
    Tab.Cell Align
    Tab.TopRight ([WideBuilder] -> Cell)
    -> (WideBuilder -> [WideBuilder]) -> WideBuilder -> Cell
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . WideBuilder -> [WideBuilder]
    forall a. a -> [a]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure) [WideBuilder]
    row)
          | Bool
    otherwise =
              (Align -> CommoditySymbol -> Cell
    Tab.textCell Align
    Tab.TopLeft CommoditySymbol
    rh, (([WideBuilder] -> Cell) -> [WideBuilder] -> Cell)
    -> [[WideBuilder] -> Cell] -> [[WideBuilder]] -> [Cell]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith ([WideBuilder] -> Cell) -> [WideBuilder] -> Cell
    forall a b. (a -> b) -> a -> b
    ($) (Align -> [WideBuilder] -> Cell
    Tab.Cell Align
    Tab.TopLeft ([WideBuilder] -> Cell)
    -> [[WideBuilder] -> Cell] -> [[WideBuilder] -> Cell]
    forall a. a -> [a] -> [a]
    : ([WideBuilder] -> Cell) -> [[WideBuilder] -> Cell]
    forall a. a -> [a]
    repeat (Align -> [WideBuilder] -> Cell
    Tab.Cell Align
    Tab.TopRight)) ((WideBuilder -> [WideBuilder]) -> [WideBuilder] -> [[WideBuilder]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap WideBuilder -> [WideBuilder]
    forall a. a -> [a]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure [WideBuilder]
    row))

    -- tests

    tests_MultiBalanceReport :: TestTree
    tests_MultiBalanceReport = [Char] -> [TestTree] -> TestTree
    testGroup [Char]
    "MultiBalanceReport" [

      let
        amt0 :: Amount
    amt0 = Amount {acommodity :: CommoditySymbol
    acommodity=CommoditySymbol
    "$", aquantity :: Quantity
    aquantity=Quantity
    0, aprice :: Maybe AmountPrice
    aprice=Maybe AmountPrice
    forall a. Maybe a
    Nothing,
          astyle :: AmountStyle
    astyle=AmountStyle {ascommodityside :: Side
    ascommodityside = Side
    L, ascommodityspaced :: Bool
    ascommodityspaced = Bool
    False, asdigitgroups :: Maybe DigitGroupStyle
    asdigitgroups = Maybe DigitGroupStyle
    forall a. Maybe a
    Nothing,
          asdecimalmark :: Maybe Char
    asdecimalmark = Char -> Maybe Char
    forall a. a -> Maybe a
    Just Char
    '.', asprecision :: AmountPrecision
    asprecision = Word8 -> AmountPrecision
    Precision Word8
    2, asrounding :: Rounding
    asrounding = Rounding
    NoRounding}}
        (ReportSpec
    rspec,Journal
    journal) gives :: (ReportSpec, Journal)
    -> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
    -> IO ()
    `gives` ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
    r = do
          let rspec' :: ReportSpec
    rspec' = ReportSpec
    rspec{_rsQuery=And [queryFromFlags $ _rsReportOpts rspec, _rsQuery rspec]}
              ([PeriodicReportRow DisplayName MixedAmount]
    eitems, MixedAmount
    etotal) = ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
    r
              (PeriodicReport [DateSpan]
    _ [PeriodicReportRow DisplayName MixedAmount]
    aitems PeriodicReportRow () MixedAmount
    atotal) = ReportSpec -> Journal -> MultiBalanceReport
    multiBalanceReport ReportSpec
    rspec' Journal
    journal
              showw :: PeriodicReportRow DisplayName MixedAmount
    -> (CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
        [Char])
    showw (PeriodicReportRow DisplayName
    a [MixedAmount]
    lAmt MixedAmount
    amt MixedAmount
    amt')
                  = (DisplayName -> CommoditySymbol
    displayFull DisplayName
    a, DisplayName -> CommoditySymbol
    displayName DisplayName
    a, DisplayName -> Int
    displayDepth DisplayName
    a, (MixedAmount -> [Char]) -> [MixedAmount] -> [[Char]]
    forall a b. (a -> b) -> [a] -> [b]
    map MixedAmount -> [Char]
    showMixedAmountDebug [MixedAmount]
    lAmt, MixedAmount -> [Char]
    showMixedAmountDebug MixedAmount
    amt, MixedAmount -> [Char]
    showMixedAmountDebug MixedAmount
    amt')
          ((PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
         [Char]))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
         [Char])]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount
    -> (CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
        [Char])
    showw [PeriodicReportRow DisplayName MixedAmount]
    aitems) [(CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char], [Char])]
    -> [(CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
         [Char])]
    -> IO ()
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
    @?= ((PeriodicReportRow DisplayName MixedAmount
     -> (CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
         [Char]))
    -> [PeriodicReportRow DisplayName MixedAmount]
    -> [(CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
         [Char])]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName MixedAmount
    -> (CommoditySymbol, CommoditySymbol, Int, [[Char]], [Char],
        [Char])
    showw [PeriodicReportRow DisplayName MixedAmount]
    eitems)
          MixedAmount -> [Char]
    showMixedAmountDebug (PeriodicReportRow () MixedAmount -> MixedAmount
    forall a b. PeriodicReportRow a b -> b
    prrTotal PeriodicReportRow () MixedAmount
    atotal) [Char] -> [Char] -> IO ()
    forall a. (Eq a, Show a, HasCallStack) => a -> a -> IO ()
    @?= MixedAmount -> [Char]
    showMixedAmountDebug MixedAmount
    etotal -- we only check the sum of the totals
      in
       [Char] -> [TestTree] -> TestTree
    testGroup [Char]
    "multiBalanceReport" [
          [Char] -> IO () -> TestTree
    testCase [Char]
    "null journal"  (IO () -> TestTree) -> IO () -> TestTree
    forall a b. (a -> b) -> a -> b
    $
          (ReportSpec
    defreportspec, Journal
    nulljournal) (ReportSpec, Journal)
    -> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
    -> IO ()
    `gives` ([], MixedAmount
    nullmixedamt)

         ,[Char] -> IO () -> TestTree
    testCase [Char]
    "with -H on a populated period"  (IO () -> TestTree) -> IO () -> TestTree
    forall a b. (a -> b) -> a -> b
    $
          (ReportSpec
    defreportspec{_rsReportOpts=defreportopts{period_= PeriodBetween (fromGregorian 2008 1 1) (fromGregorian 2008 1 2), balanceaccum_=Historical}}, Journal
    samplejournal) (ReportSpec, Journal)
    -> ([PeriodicReportRow DisplayName MixedAmount], MixedAmount)
    -> IO ()
    `gives`
           (
            [ DisplayName
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow (CommoditySymbol -> DisplayName
    flatDisplayName CommoditySymbol
    "assets:bank:checking") [Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    1]    (Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    1)    (Amount -> MixedAmount
    mixedAmount Amount
    amt0{aquantity=1})
            , DisplayName
    -> [MixedAmount]
    -> MixedAmount
    -> MixedAmount
    -> PeriodicReportRow DisplayName MixedAmount
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow (CommoditySymbol -> DisplayName
    flatDisplayName CommoditySymbol
    "income:salary")        [Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd (-Quantity
    1)] (Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd (-Quantity
    1)) (Amount -> MixedAmount
    mixedAmount Amount
    amt0{aquantity=(-1)})
            ],
            Amount -> MixedAmount
    mixedAmount (Amount -> MixedAmount) -> Amount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ Quantity -> Amount
    usd Quantity
    0)

         -- ,testCase "a valid history on an empty period"  $
         --  (defreportopts{period_= PeriodBetween (fromGregorian 2008 1 2) (fromGregorian 2008 1 3), balanceaccum_=Historical}, samplejournal) `gives`
         --   (
         --    [
         --     ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",mixedAmount amt0 {aquantity=1})
         --    ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",mixedAmount amt0 {aquantity=(-1)})
         --    ],
         --    mixedAmount usd0)

         -- ,testCase "a valid history on an empty period (more complex)"  $
         --  (defreportopts{period_= PeriodBetween (fromGregorian 2009 1 1) (fromGregorian 2009 1 2), balanceaccum_=Historical}, samplejournal) `gives`
         --   (
         --    [
         --    ("assets:bank:checking","checking",3, [mamountp' "$1.00"], mamountp' "$1.00",mixedAmount amt0 {aquantity=1})
         --    ,("assets:bank:saving","saving",3, [mamountp' "$1.00"], mamountp' "$1.00",mixedAmount amt0 {aquantity=1})
         --    ,("assets:cash","cash",2, [mamountp' "$-2.00"], mamountp' "$-2.00",mixedAmount amt0 {aquantity=(-2)})
         --    ,("expenses:food","food",2, [mamountp' "$1.00"], mamountp' "$1.00",mixedAmount amt0 {aquantity=(1)})
         --    ,("expenses:supplies","supplies",2, [mamountp' "$1.00"], mamountp' "$1.00",mixedAmount amt0 {aquantity=(1)})
         --    ,("income:gifts","gifts",2, [mamountp' "$-1.00"], mamountp' "$-1.00",mixedAmount amt0 {aquantity=(-1)})
         --    ,("income:salary","salary",2, [mamountp' "$-1.00"], mamountp' "$-1.00",mixedAmount amt0 {aquantity=(-1)})
         --    ],
         --    mixedAmount usd0)
        ]
     ]