    {-# LANGUAGE OverloadedStrings   #-}
    {-# LANGUAGE RecordWildCards     #-}
    {-# LANGUAGE ScopedTypeVariables #-}

    module Hledger.Reports.BudgetReport (
      BudgetGoal,
      BudgetTotal,
      BudgetAverage,
      BudgetCell,
      BudgetReportRow,
      BudgetReport,
      budgetReport,
      budgetReportAsTable,
      budgetReportAsText,
      budgetReportAsCsv,
      -- * Helpers
      combineBudgetAndActual,
      -- * Tests
      tests_BudgetReport
    )
    where

    import Control.Applicative ((<|>))
    import Control.Arrow ((***))
    import Data.Decimal (roundTo)
    import Data.Function (on)
    import Data.HashMap.Strict (HashMap)
    import qualified Data.HashMap.Strict as HM
    import Data.List (find, partition, transpose, foldl', maximumBy)
    import Data.List.Extra (nubSort)
    import Data.Maybe (fromMaybe, catMaybes, isJust)
    import Data.Map (Map)
    import qualified Data.Map as Map
    import qualified Data.Set as S
    import Data.Text (Text)
    import qualified Data.Text as T
    import qualified Data.Text.Lazy as TL
    import qualified Data.Text.Lazy.Builder as TB
    import Safe (minimumDef)
    --import System.Console.CmdArgs.Explicit as C
    --import Lucid as L
    import qualified Text.Tabular.AsciiWide as Tab

    import Hledger.Data
    import Hledger.Utils
    import Hledger.Reports.ReportOptions
    import Hledger.Reports.ReportTypes
    import Hledger.Reports.MultiBalanceReport
    import Data.Ord (comparing)
    import Control.Monad ((>=>))

    type BudgetGoal    = Change
    type BudgetTotal   = Total
    type BudgetAverage = Average

    -- | A budget report tracks expected and actual changes per account and subperiod.
    type BudgetCell = (Maybe Change, Maybe BudgetGoal)
    type BudgetReportRow = PeriodicReportRow DisplayName BudgetCell
    type BudgetReport    = PeriodicReport    DisplayName BudgetCell

    type BudgetDisplayCell = (WideBuilder, Maybe (WideBuilder, Maybe WideBuilder))
    type BudgetDisplayRow  = [BudgetDisplayCell]
    type BudgetShowMixed   = MixedAmount -> [WideBuilder]
    type BudgetPercBudget  = Change -> BudgetGoal -> [Maybe Percentage]

    -- | Calculate per-account, per-period budget (balance change) goals
    -- from all periodic transactions, calculate actual balance changes
    -- from the regular transactions, and compare these to get a 'BudgetReport'.
    -- Unbudgeted accounts may be hidden or renamed (see journalWithBudgetAccountNames).
    budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
    budgetReport :: ReportSpec -> BalancingOpts -> DateSpan -> Journal -> BudgetReport
    budgetReport ReportSpec
    rspec BalancingOpts
    bopts DateSpan
    reportspan Journal
    j = String -> BudgetReport -> BudgetReport
    forall a. Show a => String -> a -> a
    dbg4 String
    "sortedbudgetreport" BudgetReport
    budgetreport
      where
        -- Budget report demands ALTree mode to ensure subaccounts and subaccount budgets are properly handled
        -- and that reports with and without --empty make sense when compared side by side
        ropts :: ReportOpts
    ropts = (ReportSpec -> ReportOpts
    _rsReportOpts ReportSpec
    rspec){ accountlistmode_ :: AccountListMode
    accountlistmode_ = AccountListMode
    ALTree }
        showunbudgeted :: Bool
    showunbudgeted = ReportOpts -> Bool
    empty_ ReportOpts
    ropts
        budgetedaccts :: Set Text
    budgetedaccts =
          String -> Set Text -> Set Text
    forall a. Show a => String -> a -> a
    dbg3 String
    "budgetedacctsinperiod" (Set Text -> Set Text) -> Set Text -> Set Text
    forall a b. (a -> b) -> a -> b
    $
          [Text] -> Set Text
    forall a. Ord a => [a] -> Set a
    S.fromList ([Text] -> Set Text) -> [Text] -> Set Text
    forall a b. (a -> b) -> a -> b
    $
          [Text] -> [Text]
    expandAccountNames ([Text] -> [Text]) -> [Text] -> [Text]
    forall a b. (a -> b) -> a -> b
    $
          [Posting] -> [Text]
    accountNamesFromPostings ([Posting] -> [Text]) -> [Posting] -> [Text]
    forall a b. (a -> b) -> a -> b
    $
          (Transaction -> [Posting]) -> [Transaction] -> [Posting]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap Transaction -> [Posting]
    tpostings ([Transaction] -> [Posting]) -> [Transaction] -> [Posting]
    forall a b. (a -> b) -> a -> b
    $
          (PeriodicTransaction -> [Transaction])
    -> [PeriodicTransaction] -> [Transaction]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (\PeriodicTransaction
    pt -> Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
    runPeriodicTransaction Bool
    False PeriodicTransaction
    pt DateSpan
    reportspan) ([PeriodicTransaction] -> [Transaction])
    -> [PeriodicTransaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          Journal -> [PeriodicTransaction]
    jperiodictxns Journal
    j
        actualj :: Journal
    actualj = Set Text -> Bool -> Journal -> Journal
    journalWithBudgetAccountNames Set Text
    budgetedaccts Bool
    showunbudgeted Journal
    j
        budgetj :: Journal
    budgetj = BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
    journalAddBudgetGoalTransactions BalancingOpts
    bopts ReportOpts
    ropts DateSpan
    reportspan Journal
    j
        priceoracle :: PriceOracle
    priceoracle = Bool -> Journal -> PriceOracle
    journalPriceOracle (ReportOpts -> Bool
    infer_prices_ ReportOpts
    ropts) Journal
    j
        budgetgoalreport :: MultiBalanceReport
    budgetgoalreport@(PeriodicReport [DateSpan]
    _ [PeriodicReportRow DisplayName Change]
    budgetgoalitems PeriodicReportRow () Change
    budgetgoaltotals) =
            String -> MultiBalanceReport -> MultiBalanceReport
    forall a. Show a => String -> a -> a
    dbg5 String
    "budgetgoalreport" (MultiBalanceReport -> MultiBalanceReport)
    -> MultiBalanceReport -> MultiBalanceReport
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
    multiBalanceReportWith ReportSpec
    rspec{_rsReportOpts :: ReportOpts
    _rsReportOpts=ReportOpts
    ropts{empty_ :: Bool
    empty_=Bool
    True}} Journal
    budgetj PriceOracle
    priceoracle Set Text
    forall a. Monoid a => a
    mempty
        budgetedacctsseen :: Set Text
    budgetedacctsseen = [Text] -> Set Text
    forall a. Ord a => [a] -> Set a
    S.fromList ([Text] -> Set Text) -> [Text] -> Set Text
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName Change -> Text)
    -> [PeriodicReportRow DisplayName Change] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName Change -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName [PeriodicReportRow DisplayName Change]
    budgetgoalitems
        actualreport :: MultiBalanceReport
    actualreport@(PeriodicReport [DateSpan]
    actualspans [PeriodicReportRow DisplayName Change]
    _ PeriodicReportRow () Change
    _) =
            String -> MultiBalanceReport -> MultiBalanceReport
    forall a. Show a => String -> a -> a
    dbg5 String
    "actualreport"     (MultiBalanceReport -> MultiBalanceReport)
    -> MultiBalanceReport -> MultiBalanceReport
    forall a b. (a -> b) -> a -> b
    $ ReportSpec
    -> Journal -> PriceOracle -> Set Text -> MultiBalanceReport
    multiBalanceReportWith ReportSpec
    rspec{_rsReportOpts :: ReportOpts
    _rsReportOpts=ReportOpts
    ropts{empty_ :: Bool
    empty_=Bool
    True}} Journal
    actualj PriceOracle
    priceoracle Set Text
    budgetedacctsseen
        budgetgoalreport' :: MultiBalanceReport
    budgetgoalreport'
          -- If no interval is specified:
          -- budgetgoalreport's span might be shorter actualreport's due to periodic txns;
          -- it should be safe to replace it with the latter, so they combine well.
          | ReportOpts -> Interval
    interval_ ReportOpts
    ropts Interval -> Interval -> Bool
    forall a. Eq a => a -> a -> Bool
    == Interval
    NoInterval = [DateSpan]
    -> [PeriodicReportRow DisplayName Change]
    -> PeriodicReportRow () Change
    -> MultiBalanceReport
    forall a b.
    [DateSpan]
    -> [PeriodicReportRow a b]
    -> PeriodicReportRow () b
    -> PeriodicReport a b
    PeriodicReport [DateSpan]
    actualspans [PeriodicReportRow DisplayName Change]
    budgetgoalitems PeriodicReportRow () Change
    budgetgoaltotals
          | Bool
    otherwise = MultiBalanceReport
    budgetgoalreport
        budgetreport :: BudgetReport
    budgetreport = ReportOpts
    -> Journal
    -> MultiBalanceReport
    -> MultiBalanceReport
    -> BudgetReport
    combineBudgetAndActual ReportOpts
    ropts Journal
    j MultiBalanceReport
    budgetgoalreport' MultiBalanceReport
    actualreport

    -- | Use all (or all matched by --budget's argument) periodic transactions in the journal
    -- to generate budget goal transactions in the specified date span (and before, to support
    -- --historical. The precise start date is the natural start date of the largest interval
    -- of the active periodic transaction rules that is on or before the earlier of journal start date,
    -- report start date.)
    -- Budget goal transactions are similar to forecast transactions except their purpose
    -- and effect is to define balance change goals, per account and period, for BudgetReport.
    --
    journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
    journalAddBudgetGoalTransactions :: BalancingOpts -> ReportOpts -> DateSpan -> Journal -> Journal
    journalAddBudgetGoalTransactions BalancingOpts
    bopts ReportOpts
    ropts DateSpan
    reportspan Journal
    j =
      (String -> Journal)
    -> (Journal -> Journal) -> Either String Journal -> Journal
    forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
    either String -> Journal
    forall a. String -> a
    error' Journal -> Journal
    forall a. a -> a
    id (Either String Journal -> Journal)
    -> Either String Journal -> Journal
    forall a b. (a -> b) -> a -> b
    $  -- PARTIAL:
        (Journal -> Either String Journal
    journalApplyCommodityStyles (Journal -> Either String Journal)
    -> (Journal -> Either String Journal)
    -> Journal
    -> Either String Journal
    forall (m :: * -> *) a b c.
    Monad m =>
    (a -> m b) -> (b -> m c) -> a -> m c
    >=> BalancingOpts -> Journal -> Either String Journal
    journalBalanceTransactions BalancingOpts
    bopts) Journal
    j{ jtxns :: [Transaction]
    jtxns = [Transaction]
    budgetts }
      where
        budgetspan :: DateSpan
    budgetspan = String -> DateSpan -> DateSpan
    forall a. Show a => String -> a -> a
    dbg3 String
    "budget span" (DateSpan -> DateSpan) -> DateSpan -> DateSpan
    forall a b. (a -> b) -> a -> b
    $ Maybe EFDay -> Maybe EFDay -> DateSpan
    DateSpan (Day -> EFDay
    Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe Day
    mbudgetgoalsstartdate) (Day -> EFDay
    Exact (Day -> EFDay) -> Maybe Day -> Maybe EFDay
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> DateSpan -> Maybe Day
    spanEnd DateSpan
    reportspan)
          where
            mbudgetgoalsstartdate :: Maybe Day
    mbudgetgoalsstartdate =
              -- We want to also generate budget goal txns before the report start date, in case -H is used.
              -- What should the actual starting date for goal txns be ? This gets tricky.
              -- Consider a journal with a "~ monthly" periodic transaction rule, where the first transaction is on 1/5.
              -- Users will certainly expect a budget goal for january, but "~ monthly" generates transactions
              -- on the first of month, and starting from 1/5 would exclude 1/1.
              -- Secondly, consider a rule like "~ every february 2nd from 2020/01"; we should not start that
              -- before 2020-02-02.
              -- Hopefully the following algorithm produces intuitive behaviour in general:
              -- from the earlier of the journal start date and the report start date,
              -- move backward to the nearest natural start date of the largest period seen among the
              -- active periodic transactions, unless that is disallowed by a start date in the periodic rule.
              -- (Do we need to pay attention to an end date in the rule ? Don't think so.)
              -- (So with "~ monthly", the journal start date 1/5 is adjusted to 1/1.)
              case Maybe Day -> [Maybe Day] -> Maybe Day
    forall a. Ord a => a -> [a] -> a
    minimumDef Maybe Day
    forall a. Maybe a
    Nothing ([Maybe Day] -> Maybe Day) -> [Maybe Day] -> Maybe Day
    forall a b. (a -> b) -> a -> b
    $ (Maybe Day -> Bool) -> [Maybe Day] -> [Maybe Day]
    forall a. (a -> Bool) -> [a] -> [a]
    filter Maybe Day -> Bool
    forall a. Maybe a -> Bool
    isJust [Bool -> Journal -> Maybe Day
    journalStartDate Bool
    False Journal
    j, DateSpan -> Maybe Day
    spanStart DateSpan
    reportspan] of
                Maybe Day
    Nothing -> Maybe Day
    forall a. Maybe a
    Nothing
                Just Day
    d  -> Day -> Maybe Day
    forall a. a -> Maybe a
    Just Day
    d'
                  where
                    -- the interval and any date span of the periodic transaction with longest period
                    (Interval
    intervl, DateSpan
    spn) =
                      case [PeriodicTransaction]
    budgetpts of
                        []  -> (Int -> Interval
    Days Int
    1, DateSpan
    nulldatespan)
                        [PeriodicTransaction]
    pts -> (PeriodicTransaction -> Interval
    ptinterval PeriodicTransaction
    pt, PeriodicTransaction -> DateSpan
    ptspan PeriodicTransaction
    pt)
                          where pt :: PeriodicTransaction
    pt = (PeriodicTransaction -> PeriodicTransaction -> Ordering)
    -> [PeriodicTransaction] -> PeriodicTransaction
    forall (t :: * -> *) a.
    Foldable t =>
    (a -> a -> Ordering) -> t a -> a
    maximumBy ((PeriodicTransaction -> Interval)
    -> PeriodicTransaction -> PeriodicTransaction -> Ordering
    forall a b. Ord a => (b -> a) -> b -> b -> Ordering
    comparing PeriodicTransaction -> Interval
    ptinterval) [PeriodicTransaction]
    pts  -- PARTIAL: maximumBy won't fail
                    -- the natural start of this interval on or before the journal/report start
                    intervalstart :: Day
    intervalstart = Interval -> Day -> Day
    intervalBoundaryBefore Interval
    intervl Day
    d
                    -- the natural interval start before the journal/report start,
                    -- or the rule-specified start if later,
                    -- but no later than the journal/report start.
                    d' :: Day
    d' = Day -> Day -> Day
    forall a. Ord a => a -> a -> a
    min Day
    d (Day -> Day) -> Day -> Day
    forall a b. (a -> b) -> a -> b
    $ Day -> (Day -> Day) -> Maybe Day -> Day
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Day
    intervalstart (Day -> Day -> Day
    forall a. Ord a => a -> a -> a
    max Day
    intervalstart) (Maybe Day -> Day) -> Maybe Day -> Day
    forall a b. (a -> b) -> a -> b
    $ DateSpan -> Maybe Day
    spanStart DateSpan
    spn

        -- select periodic transactions matching a pattern
        -- (the argument of the (final) --budget option).
        -- XXX two limitations/wishes, requiring more extensive type changes:
        -- - give an error if pat is non-null and matches no periodic txns
        -- - allow a regexp or a full hledger query, not just a substring
        pat :: Text
    pat = Text -> Maybe Text -> Text
    forall a. a -> Maybe a -> a
    fromMaybe Text
    "" (Maybe Text -> Text) -> Maybe Text -> Text
    forall a b. (a -> b) -> a -> b
    $ String -> Maybe Text -> Maybe Text
    forall a. Show a => String -> a -> a
    dbg3 String
    "budget pattern" (Maybe Text -> Maybe Text) -> Maybe Text -> Maybe Text
    forall a b. (a -> b) -> a -> b
    $ Text -> Text
    T.toLower (Text -> Text) -> Maybe Text -> Maybe Text
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> ReportOpts -> Maybe Text
    budgetpat_ ReportOpts
    ropts
        budgetpts :: [PeriodicTransaction]
    budgetpts = [PeriodicTransaction
    pt | PeriodicTransaction
    pt <- Journal -> [PeriodicTransaction]
    jperiodictxns Journal
    j, Text
    pat Text -> Text -> Bool
    `T.isInfixOf` Text -> Text
    T.toLower (PeriodicTransaction -> Text
    ptdescription PeriodicTransaction
    pt)]
        budgetts :: [Transaction]
    budgetts =
          String -> [Transaction] -> [Transaction]
    forall a. Show a => String -> a -> a
    dbg5 String
    "budget goal txns" ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $
          [Transaction -> Transaction
    makeBudgetTxn Transaction
    t
          | PeriodicTransaction
    pt <- [PeriodicTransaction]
    budgetpts
          , Transaction
    t <- Bool -> PeriodicTransaction -> DateSpan -> [Transaction]
    runPeriodicTransaction Bool
    False PeriodicTransaction
    pt DateSpan
    budgetspan
          ]
        makeBudgetTxn :: Transaction -> Transaction
    makeBudgetTxn Transaction
    t = Transaction -> Transaction
    txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
    forall a b. (a -> b) -> a -> b
    $ Transaction
    t { tdescription :: Text
    tdescription = String -> Text
    T.pack String
    "Budget transaction" }

    -- | Adjust a journal's account names for budget reporting, in two ways:
    --
    -- 1. accounts with no budget goal anywhere in their ancestry are moved
    --    under the "unbudgeted" top level account.
    --
    -- 2. subaccounts with no budget goal are merged with their closest parent account
    --    with a budget goal, so that only budgeted accounts are shown.
    --    This can be disabled by -E/--empty.
    --
    journalWithBudgetAccountNames :: S.Set AccountName -> Bool -> Journal -> Journal
    journalWithBudgetAccountNames :: Set Text -> Bool -> Journal -> Journal
    journalWithBudgetAccountNames Set Text
    budgetedaccts Bool
    showunbudgeted Journal
    j =
      (Journal -> String) -> Journal -> Journal
    forall a. Show a => (a -> String) -> a -> a
    dbg5With ((String
    "budget account names: "String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++)(String -> String) -> (Journal -> String) -> Journal -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .[Text] -> String
    forall a. Show a => a -> String
    pshow([Text] -> String) -> (Journal -> [Text]) -> Journal -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .Journal -> [Text]
    journalAccountNamesUsed) (Journal -> Journal) -> Journal -> Journal
    forall a b. (a -> b) -> a -> b
    $
      Journal
    j { jtxns :: [Transaction]
    jtxns = Transaction -> Transaction
    remapTxn (Transaction -> Transaction) -> [Transaction] -> [Transaction]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Journal -> [Transaction]
    jtxns Journal
    j }
      where
        remapTxn :: Transaction -> Transaction
    remapTxn = Transaction -> Transaction
    txnTieKnot (Transaction -> Transaction)
    -> (Transaction -> Transaction) -> Transaction -> Transaction
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Posting -> Posting) -> Transaction -> Transaction
    transactionTransformPostings Posting -> Posting
    remapPosting
        remapPosting :: Posting -> Posting
    remapPosting Posting
    p = Posting
    p { paccount :: Text
    paccount = Text -> Text
    remapAccount (Text -> Text) -> Text -> Text
    forall a b. (a -> b) -> a -> b
    $ Posting -> Text
    paccount Posting
    p, poriginal :: Maybe Posting
    poriginal = Posting -> Maybe Posting
    poriginal Posting
    p Maybe Posting -> Maybe Posting -> Maybe Posting
    forall a. Maybe a -> Maybe a -> Maybe a
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
    <|> Posting -> Maybe Posting
    forall a. a -> Maybe a
    Just Posting
    p }
        remapAccount :: Text -> Text
    remapAccount Text
    a
          | Text
    a Text -> Set Text -> Bool
    forall a. Ord a => a -> Set a -> Bool
    `S.member` Set Text
    budgetedaccts = Text
    a
          | Just Text
    p <- Maybe Text
    budgetedparent   = if Bool
    showunbudgeted then Text
    a else Text
    p
          | Bool
    otherwise                  = if Bool
    showunbudgeted then Text
    u Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    acctsep Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    a else Text
    u
          where
            budgetedparent :: Maybe Text
    budgetedparent = (Text -> Bool) -> [Text] -> Maybe Text
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
    find (Text -> Set Text -> Bool
    forall a. Ord a => a -> Set a -> Bool
    `S.member` Set Text
    budgetedaccts) ([Text] -> Maybe Text) -> [Text] -> Maybe Text
    forall a b. (a -> b) -> a -> b
    $ Text -> [Text]
    parentAccountNames Text
    a
            u :: Text
    u = Text
    unbudgetedAccountName

    -- | Combine a per-account-and-subperiod report of budget goals, and one
    -- of actual change amounts, into a budget performance report.
    -- The two reports should have the same report interval, but need not
    -- have exactly the same account rows or date columns.
    -- (Cells in the combined budget report can be missing a budget goal,
    -- an actual amount, or both.) The combined report will include:
    --
    -- - consecutive subperiods at the same interval as the two reports,
    --   spanning the period of both reports
    --
    -- - all accounts mentioned in either report, sorted by account code or
    --   account name or amount as appropriate.
    --
    combineBudgetAndActual :: ReportOpts -> Journal -> MultiBalanceReport -> MultiBalanceReport -> BudgetReport
    combineBudgetAndActual :: ReportOpts
    -> Journal
    -> MultiBalanceReport
    -> MultiBalanceReport
    -> BudgetReport
    combineBudgetAndActual ReportOpts
    ropts Journal
    j
          (PeriodicReport [DateSpan]
    budgetperiods [PeriodicReportRow DisplayName Change]
    budgetrows (PeriodicReportRow ()
    _ [Change]
    budgettots Change
    budgetgrandtot Change
    budgetgrandavg))
          (PeriodicReport [DateSpan]
    actualperiods [PeriodicReportRow DisplayName Change]
    actualrows (PeriodicReportRow ()
    _ [Change]
    actualtots Change
    actualgrandtot Change
    actualgrandavg)) =
        [DateSpan]
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> PeriodicReportRow () (Maybe Change, Maybe Change)
    -> BudgetReport
    forall a b.
    [DateSpan]
    -> [PeriodicReportRow a b]
    -> PeriodicReportRow () b
    -> PeriodicReport a b
    PeriodicReport [DateSpan]
    periods [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    sortedrows PeriodicReportRow () (Maybe Change, Maybe Change)
    totalrow
      where
        periods :: [DateSpan]
    periods = [DateSpan] -> [DateSpan]
    forall a. Ord a => [a] -> [a]
    nubSort ([DateSpan] -> [DateSpan])
    -> ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (DateSpan -> Bool) -> [DateSpan] -> [DateSpan]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (DateSpan -> DateSpan -> Bool
    forall a. Eq a => a -> a -> Bool
    /= DateSpan
    nulldatespan) ([DateSpan] -> [DateSpan]) -> [DateSpan] -> [DateSpan]
    forall a b. (a -> b) -> a -> b
    $ [DateSpan]
    budgetperiods [DateSpan] -> [DateSpan] -> [DateSpan]
    forall a. [a] -> [a] -> [a]
    ++ [DateSpan]
    actualperiods

        -- first, combine any corresponding budget goals with actual changes
        rows1 :: [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows1 =
          [ DisplayName
    -> [(Maybe Change, Maybe Change)]
    -> (Maybe Change, Maybe Change)
    -> (Maybe Change, Maybe Change)
    -> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow DisplayName
    acct [(Maybe Change, Maybe Change)]
    amtandgoals (Maybe Change, Maybe Change)
    totamtandgoal (Maybe Change, Maybe Change)
    avgamtandgoal
          | PeriodicReportRow DisplayName
    acct [Change]
    actualamts Change
    actualtot Change
    actualavg <- [PeriodicReportRow DisplayName Change]
    actualrows
          , let mbudgetgoals :: Maybe ([Change], Change, Change)
    mbudgetgoals       = Text
    -> HashMap Text ([Change], Change, Change)
    -> Maybe ([Change], Change, Change)
    forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
    HM.lookup (DisplayName -> Text
    displayFull DisplayName
    acct) HashMap Text ([Change], Change, Change)
    budgetGoalsByAcct :: Maybe ([BudgetGoal], BudgetTotal, BudgetAverage)
          , let budgetmamts :: [Maybe Change]
    budgetmamts        = [Maybe Change]
    -> (([Change], Change, Change) -> [Maybe Change])
    -> Maybe ([Change], Change, Change)
    -> [Maybe Change]
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe (Maybe Change
    forall a. Maybe a
    Nothing Maybe Change -> [DateSpan] -> [Maybe Change]
    forall a b. a -> [b] -> [a]
    forall (f :: * -> *) a b. Functor f => a -> f b -> f a
    <$ [DateSpan]
    periods) ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
    forall a b. (a -> b) -> [a] -> [b]
    map Change -> Maybe Change
    forall a. a -> Maybe a
    Just ([Change] -> [Maybe Change])
    -> (([Change], Change, Change) -> [Change])
    -> ([Change], Change, Change)
    -> [Maybe Change]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([Change], Change, Change) -> [Change]
    forall {a} {b} {c}. (a, b, c) -> a
    first3) Maybe ([Change], Change, Change)
    mbudgetgoals :: [Maybe BudgetGoal]
          , let mbudgettot :: Maybe Change
    mbudgettot         = ([Change], Change, Change) -> Change
    forall {a} {b} {c}. (a, b, c) -> b
    second3 (([Change], Change, Change) -> Change)
    -> Maybe ([Change], Change, Change) -> Maybe Change
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe ([Change], Change, Change)
    mbudgetgoals :: Maybe BudgetTotal
          , let mbudgetavg :: Maybe Change
    mbudgetavg         = ([Change], Change, Change) -> Change
    forall {a} {b} {c}. (a, b, c) -> c
    third3 (([Change], Change, Change) -> Change)
    -> Maybe ([Change], Change, Change) -> Maybe Change
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Maybe ([Change], Change, Change)
    mbudgetgoals  :: Maybe BudgetAverage
          , let acctBudgetByPeriod :: Map DateSpan Change
    acctBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
    forall k a. Ord k => [(k, a)] -> Map k a
    Map.fromList [ (DateSpan
    p,Change
    budgetamt) | (DateSpan
    p, Just Change
    budgetamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [DateSpan]
    budgetperiods [Maybe Change]
    budgetmamts ] :: Map DateSpan BudgetGoal
          , let acctActualByPeriod :: Map DateSpan Change
    acctActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
    forall k a. Ord k => [(k, a)] -> Map k a
    Map.fromList [ (DateSpan
    p,Change
    actualamt) | (DateSpan
    p, Just Change
    actualamt) <- [DateSpan] -> [Maybe Change] -> [(DateSpan, Maybe Change)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [DateSpan]
    actualperiods ((Change -> Maybe Change) -> [Change] -> [Maybe Change]
    forall a b. (a -> b) -> [a] -> [b]
    map Change -> Maybe Change
    forall a. a -> Maybe a
    Just [Change]
    actualamts) ] :: Map DateSpan Change
          , let amtandgoals :: [(Maybe Change, Maybe Change)]
    amtandgoals        = [ (DateSpan -> Map DateSpan Change -> Maybe Change
    forall k a. Ord k => k -> Map k a -> Maybe a
    Map.lookup DateSpan
    p Map DateSpan Change
    acctActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
    forall k a. Ord k => k -> Map k a -> Maybe a
    Map.lookup DateSpan
    p Map DateSpan Change
    acctBudgetByPeriod) | DateSpan
    p <- [DateSpan]
    periods ] :: [BudgetCell]
          , let totamtandgoal :: (Maybe Change, Maybe Change)
    totamtandgoal      = (Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    actualtot, Maybe Change
    mbudgettot)
          , let avgamtandgoal :: (Maybe Change, Maybe Change)
    avgamtandgoal      = (Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    actualavg, Maybe Change
    mbudgetavg)
          ]
          where
            HashMap Text ([Change], Change, Change)
    budgetGoalsByAcct :: HashMap AccountName ([BudgetGoal], BudgetTotal, BudgetAverage) =
              [(Text, ([Change], Change, Change))]
    -> HashMap Text ([Change], Change, Change)
    forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
    HM.fromList [ (DisplayName -> Text
    displayFull DisplayName
    acct, ([Change]
    amts, Change
    tot, Change
    avg))
                             | PeriodicReportRow DisplayName
    acct [Change]
    amts Change
    tot Change
    avg <- [PeriodicReportRow DisplayName Change]
    budgetrows ]

        -- next, make rows for budget goals with no actual changes
        rows2 :: [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows2 =
          [ DisplayName
    -> [(Maybe Change, Maybe Change)]
    -> (Maybe Change, Maybe Change)
    -> (Maybe Change, Maybe Change)
    -> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow DisplayName
    acct [(Maybe Change, Maybe Change)]
    amtandgoals (Maybe Change, Maybe Change)
    forall {a}. (Maybe a, Maybe Change)
    totamtandgoal (Maybe Change, Maybe Change)
    forall {a}. (Maybe a, Maybe Change)
    avgamtandgoal
          | PeriodicReportRow DisplayName
    acct [Change]
    budgetgoals Change
    budgettot Change
    budgetavg <- [PeriodicReportRow DisplayName Change]
    budgetrows
          , DisplayName -> Text
    displayFull DisplayName
    acct Text -> [Text] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `notElem` (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> Text)
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows1
          , let acctBudgetByPeriod :: Map DateSpan Change
    acctBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
    forall k a. Ord k => [(k, a)] -> Map k a
    Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
    -> [(DateSpan, Change)] -> Map DateSpan Change
    forall a b. (a -> b) -> a -> b
    $ [DateSpan] -> [Change] -> [(DateSpan, Change)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [DateSpan]
    budgetperiods [Change]
    budgetgoals :: Map DateSpan BudgetGoal
          , let amtandgoals :: [(Maybe Change, Maybe Change)]
    amtandgoals        = [ (Maybe Change
    forall a. Maybe a
    Nothing, DateSpan -> Map DateSpan Change -> Maybe Change
    forall k a. Ord k => k -> Map k a -> Maybe a
    Map.lookup DateSpan
    p Map DateSpan Change
    acctBudgetByPeriod) | DateSpan
    p <- [DateSpan]
    periods ] :: [BudgetCell]
          , let totamtandgoal :: (Maybe a, Maybe Change)
    totamtandgoal      = (Maybe a
    forall a. Maybe a
    Nothing, Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    budgettot)
          , let avgamtandgoal :: (Maybe a, Maybe Change)
    avgamtandgoal      = (Maybe a
    forall a. Maybe a
    Nothing, Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    budgetavg)
          ]

        -- combine and re-sort rows
        -- TODO: add --sort-budget to sort by budget goal amount
        [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    sortedrows :: [BudgetReportRow] = [Text]
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    forall b.
    [Text]
    -> [PeriodicReportRow DisplayName b]
    -> [PeriodicReportRow DisplayName b]
    sortRowsLike ([PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [Text]
    forall {b}.
    [PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
    mbrsorted [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    unbudgetedrows [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [Text]
    forall {b}.
    [PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
    mbrsorted [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows') [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows
          where
            ([PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    unbudgetedrows, [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows') = (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> Bool)
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> ([PeriodicReportRow DisplayName (Maybe Change, Maybe Change)],
        [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)])
    forall a. (a -> Bool) -> [a] -> ([a], [a])
    partition ((Text -> Text -> Bool
    forall a. Eq a => a -> a -> Bool
    ==Text
    unbudgetedAccountName) (Text -> Bool)
    -> (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
        -> Text)
    -> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows
            mbrsorted :: [PeriodicReportRow DisplayName (Maybe Change, b)] -> [Text]
    mbrsorted = (PeriodicReportRow DisplayName Change -> Text)
    -> [PeriodicReportRow DisplayName Change] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map PeriodicReportRow DisplayName Change -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName ([PeriodicReportRow DisplayName Change] -> [Text])
    -> ([PeriodicReportRow DisplayName (Maybe Change, b)]
        -> [PeriodicReportRow DisplayName Change])
    -> [PeriodicReportRow DisplayName (Maybe Change, b)]
    -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ReportOpts
    -> Journal
    -> [PeriodicReportRow DisplayName Change]
    -> [PeriodicReportRow DisplayName Change]
    sortRows ReportOpts
    ropts Journal
    j ([PeriodicReportRow DisplayName Change]
     -> [PeriodicReportRow DisplayName Change])
    -> ([PeriodicReportRow DisplayName (Maybe Change, b)]
        -> [PeriodicReportRow DisplayName Change])
    -> [PeriodicReportRow DisplayName (Maybe Change, b)]
    -> [PeriodicReportRow DisplayName Change]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (PeriodicReportRow DisplayName (Maybe Change, b)
     -> PeriodicReportRow DisplayName Change)
    -> [PeriodicReportRow DisplayName (Maybe Change, b)]
    -> [PeriodicReportRow DisplayName Change]
    forall a b. (a -> b) -> [a] -> [b]
    map (((Maybe Change, b) -> Change)
    -> PeriodicReportRow DisplayName (Maybe Change, b)
    -> PeriodicReportRow DisplayName Change
    forall a b.
    (a -> b)
    -> PeriodicReportRow DisplayName a
    -> PeriodicReportRow DisplayName b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (((Maybe Change, b) -> Change)
     -> PeriodicReportRow DisplayName (Maybe Change, b)
     -> PeriodicReportRow DisplayName Change)
    -> ((Maybe Change, b) -> Change)
    -> PeriodicReportRow DisplayName (Maybe Change, b)
    -> PeriodicReportRow DisplayName Change
    forall a b. (a -> b) -> a -> b
    $ Change -> Maybe Change -> Change
    forall a. a -> Maybe a -> a
    fromMaybe Change
    nullmixedamt (Maybe Change -> Change)
    -> ((Maybe Change, b) -> Maybe Change)
    -> (Maybe Change, b)
    -> Change
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Maybe Change, b) -> Maybe Change
    forall a b. (a, b) -> a
    fst)
            rows :: [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows = [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows1 [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    forall a. [a] -> [a] -> [a]
    ++ [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    rows2

        totalrow :: PeriodicReportRow () (Maybe Change, Maybe Change)
    totalrow = ()
    -> [(Maybe Change, Maybe Change)]
    -> (Maybe Change, Maybe Change)
    -> (Maybe Change, Maybe Change)
    -> PeriodicReportRow () (Maybe Change, Maybe Change)
    forall a b. a -> [b] -> b -> b -> PeriodicReportRow a b
    PeriodicReportRow ()
            [ (DateSpan -> Map DateSpan Change -> Maybe Change
    forall k a. Ord k => k -> Map k a -> Maybe a
    Map.lookup DateSpan
    p Map DateSpan Change
    totActualByPeriod, DateSpan -> Map DateSpan Change -> Maybe Change
    forall k a. Ord k => k -> Map k a -> Maybe a
    Map.lookup DateSpan
    p Map DateSpan Change
    totBudgetByPeriod) | DateSpan
    p <- [DateSpan]
    periods ]
            ( Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    actualgrandtot, Change -> Maybe Change
    budget Change
    budgetgrandtot )
            ( Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    actualgrandavg, Change -> Maybe Change
    budget Change
    budgetgrandavg )
          where
            totBudgetByPeriod :: Map DateSpan Change
    totBudgetByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
    forall k a. Ord k => [(k, a)] -> Map k a
    Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
    -> [(DateSpan, Change)] -> Map DateSpan Change
    forall a b. (a -> b) -> a -> b
    $ [DateSpan] -> [Change] -> [(DateSpan, Change)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [DateSpan]
    budgetperiods [Change]
    budgettots :: Map DateSpan BudgetTotal
            totActualByPeriod :: Map DateSpan Change
    totActualByPeriod = [(DateSpan, Change)] -> Map DateSpan Change
    forall k a. Ord k => [(k, a)] -> Map k a
    Map.fromList ([(DateSpan, Change)] -> Map DateSpan Change)
    -> [(DateSpan, Change)] -> Map DateSpan Change
    forall a b. (a -> b) -> a -> b
    $ [DateSpan] -> [Change] -> [(DateSpan, Change)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [DateSpan]
    actualperiods [Change]
    actualtots :: Map DateSpan Change
            budget :: Change -> Maybe Change
    budget Change
    b = if Change -> Bool
    mixedAmountLooksZero Change
    b then Maybe Change
    forall a. Maybe a
    Nothing else Change -> Maybe Change
    forall a. a -> Maybe a
    Just Change
    b

    -- | Render a budget report as plain text suitable for console output.
    budgetReportAsText :: ReportOpts -> BudgetReport -> TL.Text
    budgetReportAsText :: ReportOpts -> BudgetReport -> Text
    budgetReportAsText ropts :: ReportOpts
    ropts@ReportOpts{Bool
    Int
    [Text]
    [Status]
    Maybe Int
    Maybe Text
    Maybe NormalSign
    Maybe ValuationType
    Maybe ConversionOp
    Interval
    Period
    StringFormat
    Layout
    AccountListMode
    BalanceAccumulation
    BalanceCalculation
    accountlistmode_ :: ReportOpts -> AccountListMode
    empty_ :: ReportOpts -> Bool
    infer_prices_ :: ReportOpts -> Bool
    interval_ :: ReportOpts -> Interval
    budgetpat_ :: ReportOpts -> Maybe Text
    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_ :: [Text]
    average_ :: Bool
    related_ :: Bool
    txn_dates_ :: Bool
    balancecalc_ :: BalanceCalculation
    balanceaccum_ :: BalanceAccumulation
    budgetpat_ :: Maybe Text
    accountlistmode_ :: AccountListMode
    drop_ :: Int
    declared_ :: Bool
    row_total_ :: Bool
    no_total_ :: Bool
    show_costs_ :: Bool
    sort_amount_ :: Bool
    percent_ :: Bool
    invert_ :: Bool
    normalbalance_ :: Maybe NormalSign
    color_ :: Bool
    transpose_ :: Bool
    layout_ :: Layout
    period_ :: ReportOpts -> Period
    statuses_ :: ReportOpts -> [Status]
    conversionop_ :: ReportOpts -> Maybe ConversionOp
    value_ :: ReportOpts -> Maybe ValuationType
    depth_ :: ReportOpts -> Maybe Int
    date2_ :: ReportOpts -> Bool
    no_elide_ :: ReportOpts -> Bool
    real_ :: ReportOpts -> Bool
    format_ :: ReportOpts -> StringFormat
    pretty_ :: ReportOpts -> Bool
    querystring_ :: ReportOpts -> [Text]
    average_ :: ReportOpts -> Bool
    related_ :: ReportOpts -> Bool
    txn_dates_ :: ReportOpts -> Bool
    balancecalc_ :: ReportOpts -> BalanceCalculation
    balanceaccum_ :: ReportOpts -> BalanceAccumulation
    drop_ :: ReportOpts -> Int
    declared_ :: ReportOpts -> Bool
    row_total_ :: ReportOpts -> Bool
    no_total_ :: ReportOpts -> Bool
    show_costs_ :: ReportOpts -> Bool
    sort_amount_ :: ReportOpts -> Bool
    percent_ :: ReportOpts -> Bool
    invert_ :: ReportOpts -> Bool
    normalbalance_ :: ReportOpts -> Maybe NormalSign
    color_ :: ReportOpts -> Bool
    transpose_ :: ReportOpts -> Bool
    layout_ :: ReportOpts -> Layout
    ..} BudgetReport
    budgetr = Builder -> Text
    TB.toLazyText (Builder -> Text) -> Builder -> Text
    forall a b. (a -> b) -> a -> b
    $
        Text -> Builder
    TB.fromText Text
    title Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Text -> Builder
    TB.fromText Text
    "\n\n" Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <>
          ReportOpts -> Table Text Text WideBuilder -> Builder
    balanceReportTableAsText ReportOpts
    ropts (ReportOpts -> BudgetReport -> Table Text Text WideBuilder
    budgetReportAsTable ReportOpts
    ropts BudgetReport
    budgetr)
      where
        title :: Text
    title = Text
    "Budget performance in " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> DateSpan -> Text
    showDateSpan (BudgetReport -> DateSpan
    forall a b. PeriodicReport a b -> DateSpan
    periodicReportSpan BudgetReport
    budgetr)
               Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> (case Maybe ConversionOp
    conversionop_ of
                     Just ConversionOp
    ToCost -> Text
    ", converted to cost"
                     Maybe ConversionOp
    _           -> Text
    "")
               Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> (case Maybe ValuationType
    value_ of
                     Just (AtThen Maybe Text
    _mc)   -> Text
    ", valued at posting date"
                     Just (AtEnd Maybe Text
    _mc)    -> Text
    ", valued at period ends"
                     Just (AtNow Maybe Text
    _mc)    -> Text
    ", current value"
                     Just (AtDate Day
    d Maybe Text
    _mc) -> Text
    ", valued at " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Day -> Text
    showDate Day
    d
                     Maybe ValuationType
    Nothing             -> Text
    "")
               Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    ":"

    -- | Build a 'Table' from a multi-column balance report.
    budgetReportAsTable :: ReportOpts -> BudgetReport -> Tab.Table Text Text WideBuilder
    budgetReportAsTable :: ReportOpts -> BudgetReport -> Table Text Text WideBuilder
    budgetReportAsTable
      ReportOpts{Bool
    Int
    [Text]
    [Status]
    Maybe Int
    Maybe Text
    Maybe NormalSign
    Maybe ValuationType
    Maybe ConversionOp
    Interval
    Period
    StringFormat
    Layout
    AccountListMode
    BalanceAccumulation
    BalanceCalculation
    accountlistmode_ :: ReportOpts -> AccountListMode
    empty_ :: ReportOpts -> Bool
    infer_prices_ :: ReportOpts -> Bool
    interval_ :: ReportOpts -> Interval
    budgetpat_ :: ReportOpts -> Maybe Text
    period_ :: ReportOpts -> Period
    statuses_ :: ReportOpts -> [Status]
    conversionop_ :: ReportOpts -> Maybe ConversionOp
    value_ :: ReportOpts -> Maybe ValuationType
    depth_ :: ReportOpts -> Maybe Int
    date2_ :: ReportOpts -> Bool
    no_elide_ :: ReportOpts -> Bool
    real_ :: ReportOpts -> Bool
    format_ :: ReportOpts -> StringFormat
    pretty_ :: ReportOpts -> Bool
    querystring_ :: ReportOpts -> [Text]
    average_ :: ReportOpts -> Bool
    related_ :: ReportOpts -> Bool
    txn_dates_ :: ReportOpts -> Bool
    balancecalc_ :: ReportOpts -> BalanceCalculation
    balanceaccum_ :: ReportOpts -> BalanceAccumulation
    drop_ :: ReportOpts -> Int
    declared_ :: ReportOpts -> Bool
    row_total_ :: ReportOpts -> Bool
    no_total_ :: ReportOpts -> Bool
    show_costs_ :: ReportOpts -> Bool
    sort_amount_ :: ReportOpts -> Bool
    percent_ :: ReportOpts -> Bool
    invert_ :: ReportOpts -> Bool
    normalbalance_ :: ReportOpts -> Maybe NormalSign
    color_ :: ReportOpts -> Bool
    transpose_ :: ReportOpts -> Bool
    layout_ :: ReportOpts -> Layout
    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_ :: [Text]
    average_ :: Bool
    related_ :: Bool
    txn_dates_ :: Bool
    balancecalc_ :: BalanceCalculation
    balanceaccum_ :: BalanceAccumulation
    budgetpat_ :: Maybe Text
    accountlistmode_ :: AccountListMode
    drop_ :: Int
    declared_ :: Bool
    row_total_ :: Bool
    no_total_ :: Bool
    show_costs_ :: Bool
    sort_amount_ :: Bool
    percent_ :: Bool
    invert_ :: Bool
    normalbalance_ :: Maybe NormalSign
    color_ :: Bool
    transpose_ :: Bool
    layout_ :: Layout
    ..}
      (PeriodicReport [DateSpan]
    spans [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    items PeriodicReportRow () (Maybe Change, Maybe Change)
    tr) =
        Table Text Text WideBuilder -> Table Text Text WideBuilder
    forall {rh} {a}. Table rh rh a -> Table rh rh a
    maybetransposetable (Table Text Text WideBuilder -> Table Text Text WideBuilder)
    -> Table Text Text WideBuilder -> Table Text Text WideBuilder
    forall a b. (a -> b) -> a -> b
    $
        Table Text Text WideBuilder -> Table Text Text WideBuilder
    forall {ch}. Table Text ch WideBuilder -> Table Text ch WideBuilder
    addtotalrow (Table Text Text WideBuilder -> Table Text Text WideBuilder)
    -> Table Text Text WideBuilder -> Table Text Text WideBuilder
    forall a b. (a -> b) -> a -> b
    $
        Header Text
    -> Header Text -> [[WideBuilder]] -> Table Text Text WideBuilder
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Tab.Table
          (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.NoLine ([Header Text] -> Header Text) -> [Header Text] -> Header Text
    forall a b. (a -> b) -> a -> b
    $ (Text -> Header Text) -> [Text] -> [Header Text]
    forall a b. (a -> b) -> [a] -> [b]
    map Text -> Header Text
    forall h. h -> Header h
    Tab.Header [Text]
    accts)
          (Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.NoLine ([Header Text] -> Header Text) -> [Header Text] -> Header Text
    forall a b. (a -> b) -> a -> b
    $ (Text -> Header Text) -> [Text] -> [Header Text]
    forall a b. (a -> b) -> [a] -> [b]
    map Text -> Header Text
    forall h. h -> Header h
    Tab.Header [Text]
    colheadings)
          [[WideBuilder]]
    rows
      where
        colheadings :: [Text]
    colheadings = [Text
    "Commodity" | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    == Layout
    LayoutBare]
                      [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ (DateSpan -> Text) -> [DateSpan] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map (BalanceAccumulation -> [DateSpan] -> DateSpan -> Text
    reportPeriodName BalanceAccumulation
    balanceaccum_ [DateSpan]
    spans) [DateSpan]
    spans
                      [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ [Text
    "  Total" | Bool
    row_total_]
                      [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ [Text
    "Average" | Bool
    average_]

        -- FIXME. Have to check explicitly for which to render here, since
        -- budgetReport sets accountlistmode to ALTree. Find a principled way to do
        -- this.
        renderacct :: PeriodicReportRow DisplayName a -> Text
    renderacct PeriodicReportRow DisplayName a
    row = case AccountListMode
    accountlistmode_ of
            AccountListMode
    ALTree -> Int -> Text -> Text
    T.replicate ((PeriodicReportRow DisplayName a -> Int
    forall a. PeriodicReportRow DisplayName a -> Int
    prrDepth PeriodicReportRow DisplayName a
    row Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    1)Int -> Int -> Int
    forall a. Num a => a -> a -> a
    *Int
    2) Text
    " " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> PeriodicReportRow DisplayName a -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrDisplayName PeriodicReportRow DisplayName a
    row
            AccountListMode
    ALFlat -> Int -> Text -> Text
    accountNameDrop (Int
    drop_) (Text -> Text) -> Text -> Text
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow DisplayName a -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName PeriodicReportRow DisplayName a
    row

        addtotalrow :: Table Text ch WideBuilder -> Table Text ch WideBuilder
    addtotalrow
          | Bool
    no_total_ = Table Text ch WideBuilder -> Table Text ch WideBuilder
    forall a. a -> a
    id
          | Bool
    otherwise = let rh :: Header Text
    rh = Properties -> [Header Text] -> Header Text
    forall h. Properties -> [Header h] -> Header h
    Tab.Group Properties
    Tab.NoLine ([Header Text] -> Header Text)
    -> (Header Text -> [Header Text]) -> Header Text -> Header Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Int -> Header Text -> [Header Text]
    forall a. Int -> a -> [a]
    replicate ([[WideBuilder]] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [[WideBuilder]]
    totalrows) (Header Text -> Header Text) -> Header Text -> Header Text
    forall a b. (a -> b) -> a -> b
    $ Text -> Header Text
    forall h. h -> Header h
    Tab.Header Text
    ""
                            ch :: Header [a]
    ch = [a] -> Header [a]
    forall h. h -> Header h
    Tab.Header [] -- ignored
                         in ((Table Text ch WideBuilder
     -> Table Text [Any] WideBuilder -> Table Text ch WideBuilder)
    -> Table Text [Any] WideBuilder
    -> Table Text ch WideBuilder
    -> Table Text ch WideBuilder
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip (Properties
    -> Table Text ch WideBuilder
    -> Table Text [Any] WideBuilder
    -> Table Text ch WideBuilder
    forall rh ch a ch2.
    Properties -> Table rh ch a -> Table rh ch2 a -> Table rh ch a
    Tab.concatTables Properties
    Tab.SingleLine) (Table Text [Any] WideBuilder
     -> Table Text ch WideBuilder -> Table Text ch WideBuilder)
    -> Table Text [Any] WideBuilder
    -> Table Text ch WideBuilder
    -> Table Text ch WideBuilder
    forall a b. (a -> b) -> a -> b
    $ Header Text
    -> Header [Any] -> [[WideBuilder]] -> Table Text [Any] WideBuilder
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Tab.Table Header Text
    rh Header [Any]
    forall {a}. Header [a]
    ch [[WideBuilder]]
    totalrows)

        maybetranspose :: [[a]] -> [[a]]
    maybetranspose
          | Bool
    transpose_ = [[a]] -> [[a]]
    forall a. [[a]] -> [[a]]
    transpose
          | Bool
    otherwise  = [[a]] -> [[a]]
    forall a. a -> a
    id

        maybetransposetable :: Table rh rh a -> Table rh rh a
    maybetransposetable
          | Bool
    transpose_ = \(Tab.Table Header rh
    rh Header rh
    ch [[a]]
    vals) -> Header rh -> Header rh -> [[a]] -> Table rh rh a
    forall rh ch a. Header rh -> Header ch -> [[a]] -> Table rh ch a
    Tab.Table Header rh
    ch Header rh
    rh ([[a]] -> [[a]]
    forall a. [[a]] -> [[a]]
    transpose [[a]]
    vals)
          | Bool
    otherwise  = Table rh rh a -> Table rh rh a
    forall a. a -> a
    id

        ([Text]
    accts, [[WideBuilder]]
    rows, [[WideBuilder]]
    totalrows) = ([Text]
    accts', [WideBuilder] -> [[WideBuilder]] -> [[WideBuilder]]
    forall {a}. [a] -> [[a]] -> [[a]]
    prependcs [WideBuilder]
    itemscs ([[BudgetDisplayCell]] -> [[WideBuilder]]
    padcells [[BudgetDisplayCell]]
    texts), [WideBuilder] -> [[WideBuilder]] -> [[WideBuilder]]
    forall {a}. [a] -> [[a]] -> [[a]]
    prependcs [WideBuilder]
    trcs ([[BudgetDisplayCell]] -> [[WideBuilder]]
    padtr [[BudgetDisplayCell]]
    trtexts))
          where
            shownitems :: [[(AccountName, WideBuilder, BudgetDisplayRow)]]
            shownitems :: [[(Text, WideBuilder, [BudgetDisplayCell])]]
    shownitems = ((PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> [(Text, WideBuilder, [BudgetDisplayCell])])
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [[(Text, WideBuilder, [BudgetDisplayCell])]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (\PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    i -> ((WideBuilder, [BudgetDisplayCell])
     -> (Text, WideBuilder, [BudgetDisplayCell]))
    -> [(WideBuilder, [BudgetDisplayCell])]
    -> [(Text, WideBuilder, [BudgetDisplayCell])]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (\(WideBuilder
    cs, [BudgetDisplayCell]
    cvals) -> (PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    renderacct PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    i, WideBuilder
    cs, [BudgetDisplayCell]
    cvals)) ([(WideBuilder, [BudgetDisplayCell])]
     -> [(Text, WideBuilder, [BudgetDisplayCell])])
    -> ([(Maybe Change, Maybe Change)]
        -> [(WideBuilder, [BudgetDisplayCell])])
    -> [(Maybe Change, Maybe Change)]
    -> [(Text, WideBuilder, [BudgetDisplayCell])]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    showrow ([(Maybe Change, Maybe Change)]
     -> [(Text, WideBuilder, [BudgetDisplayCell])])
    -> [(Maybe Change, Maybe Change)]
    -> [(Text, WideBuilder, [BudgetDisplayCell])]
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> [(Maybe Change, Maybe Change)]
    forall {a} {a}. PeriodicReportRow a a -> [a]
    rowToBudgetCells PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    i) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    items)
            ([Text]
    accts', [WideBuilder]
    itemscs, [[BudgetDisplayCell]]
    texts) = [(Text, WideBuilder, [BudgetDisplayCell])]
    -> ([Text], [WideBuilder], [[BudgetDisplayCell]])
    forall a b c. [(a, b, c)] -> ([a], [b], [c])
    unzip3 ([(Text, WideBuilder, [BudgetDisplayCell])]
     -> ([Text], [WideBuilder], [[BudgetDisplayCell]]))
    -> [(Text, WideBuilder, [BudgetDisplayCell])]
    -> ([Text], [WideBuilder], [[BudgetDisplayCell]])
    forall a b. (a -> b) -> a -> b
    $ [[(Text, WideBuilder, [BudgetDisplayCell])]]
    -> [(Text, WideBuilder, [BudgetDisplayCell])]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[(Text, WideBuilder, [BudgetDisplayCell])]]
    shownitems

            showntr    :: [[(WideBuilder, BudgetDisplayRow)]]
            showntr :: [[(WideBuilder, [BudgetDisplayCell])]]
    showntr    = [[(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    showrow ([(Maybe Change, Maybe Change)]
     -> [(WideBuilder, [BudgetDisplayCell])])
    -> [(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow () (Maybe Change, Maybe Change)
    -> [(Maybe Change, Maybe Change)]
    forall {a} {a}. PeriodicReportRow a a -> [a]
    rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
    tr]
            ([WideBuilder]
    trcs, [[BudgetDisplayCell]]
    trtexts)         = [(WideBuilder, [BudgetDisplayCell])]
    -> ([WideBuilder], [[BudgetDisplayCell]])
    forall a b. [(a, b)] -> ([a], [b])
    unzip  ([(WideBuilder, [BudgetDisplayCell])]
     -> ([WideBuilder], [[BudgetDisplayCell]]))
    -> [(WideBuilder, [BudgetDisplayCell])]
    -> ([WideBuilder], [[BudgetDisplayCell]])
    forall a b. (a -> b) -> a -> b
    $ [[(WideBuilder, [BudgetDisplayCell])]]
    -> [(WideBuilder, [BudgetDisplayCell])]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[(WideBuilder, [BudgetDisplayCell])]]
    showntr
            trwidths :: [(Int, Int, Int)]
    trwidths
              | Bool
    transpose_ = Int -> [(Int, Int, Int)] -> [(Int, Int, Int)]
    forall a. Int -> [a] -> [a]
    drop ([[BudgetDisplayCell]] -> Int
    forall a. [a] -> Int
    forall (t :: * -> *) a. Foldable t => t a -> Int
    length [[BudgetDisplayCell]]
    texts) [(Int, Int, Int)]
    widths
              | Bool
    otherwise = [(Int, Int, Int)]
    widths

            padcells :: [[BudgetDisplayCell]] -> [[WideBuilder]]
    padcells = [[WideBuilder]] -> [[WideBuilder]]
    forall a. [[a]] -> [[a]]
    maybetranspose ([[WideBuilder]] -> [[WideBuilder]])
    -> ([[BudgetDisplayCell]] -> [[WideBuilder]])
    -> [[BudgetDisplayCell]]
    -> [[WideBuilder]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([BudgetDisplayCell] -> [WideBuilder])
    -> [[BudgetDisplayCell]] -> [[WideBuilder]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((((Int, Int, Int), BudgetDisplayCell) -> WideBuilder)
    -> [((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (((Int, Int, Int) -> BudgetDisplayCell -> WideBuilder)
    -> ((Int, Int, Int), BudgetDisplayCell) -> WideBuilder
    forall a b c. (a -> b -> c) -> (a, b) -> c
    uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
    paddisplaycell) ([((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder])
    -> ([BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)])
    -> [BudgetDisplayCell]
    -> [WideBuilder]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(Int, Int, Int)]
    -> [BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [(Int, Int, Int)]
    widths)   ([[BudgetDisplayCell]] -> [[WideBuilder]])
    -> ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
    -> [[BudgetDisplayCell]]
    -> [[WideBuilder]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
    forall a. [[a]] -> [[a]]
    maybetranspose
            padtr :: [[BudgetDisplayCell]] -> [[WideBuilder]]
    padtr    = [[WideBuilder]] -> [[WideBuilder]]
    forall a. [[a]] -> [[a]]
    maybetranspose ([[WideBuilder]] -> [[WideBuilder]])
    -> ([[BudgetDisplayCell]] -> [[WideBuilder]])
    -> [[BudgetDisplayCell]]
    -> [[WideBuilder]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ([BudgetDisplayCell] -> [WideBuilder])
    -> [[BudgetDisplayCell]] -> [[WideBuilder]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((((Int, Int, Int), BudgetDisplayCell) -> WideBuilder)
    -> [((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (((Int, Int, Int) -> BudgetDisplayCell -> WideBuilder)
    -> ((Int, Int, Int), BudgetDisplayCell) -> WideBuilder
    forall a b c. (a -> b -> c) -> (a, b) -> c
    uncurry (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
    paddisplaycell) ([((Int, Int, Int), BudgetDisplayCell)] -> [WideBuilder])
    -> ([BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)])
    -> [BudgetDisplayCell]
    -> [WideBuilder]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [(Int, Int, Int)]
    -> [BudgetDisplayCell] -> [((Int, Int, Int), BudgetDisplayCell)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip [(Int, Int, Int)]
    trwidths) ([[BudgetDisplayCell]] -> [[WideBuilder]])
    -> ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
    -> [[BudgetDisplayCell]]
    -> [[WideBuilder]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
    forall a. [[a]] -> [[a]]
    maybetranspose

            -- commodities are shown with the amounts without `layout_ == LayoutBare`
            prependcs :: [a] -> [[a]] -> [[a]]
    prependcs [a]
    cs
              | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Layout
    LayoutBare = [[a]] -> [[a]]
    forall a. a -> a
    id
              | Bool
    otherwise = (a -> [a] -> [a]) -> [a] -> [[a]] -> [[a]]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith (:) [a]
    cs

        rowToBudgetCells :: PeriodicReportRow a a -> [a]
    rowToBudgetCells (PeriodicReportRow a
    _ [a]
    as a
    rowtot a
    rowavg) = [a]
    as
            [a] -> [a] -> [a]
    forall a. [a] -> [a] -> [a]
    ++ [a
    rowtot | Bool
    row_total_ Bool -> Bool -> Bool
    && Bool -> Bool
    not ([a] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [a]
    as)]
            [a] -> [a] -> [a]
    forall a. [a] -> [a] -> [a]
    ++ [a
    rowavg | Bool
    average_   Bool -> Bool -> Bool
    && Bool -> Bool
    not ([a] -> Bool
    forall a. [a] -> Bool
    forall (t :: * -> *) a. Foldable t => t a -> Bool
    null [a]
    as)]

        -- functions for displaying budget cells depending on `commodity-layout_` option
        rowfuncs :: [CommoditySymbol] -> (BudgetShowMixed, BudgetPercBudget)
        rowfuncs :: [Text] -> (BudgetShowMixed, BudgetPercBudget)
    rowfuncs [Text]
    cs = case Layout
    layout_ of
          LayoutWide Maybe Int
    width ->
               ( WideBuilder -> [WideBuilder]
    forall a. a -> [a]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (WideBuilder -> [WideBuilder])
    -> (Change -> WideBuilder) -> BudgetShowMixed
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . AmountDisplayOpts -> Change -> WideBuilder
    showMixedAmountB AmountDisplayOpts
    oneLine{displayColour :: Bool
    displayColour=Bool
    color_, displayMaxWidth :: Maybe Int
    displayMaxWidth=Maybe Int
    width}
               , \Change
    a -> Maybe Percentage -> [Maybe Percentage]
    forall a. a -> [a]
    forall (f :: * -> *) a. Applicative f => a -> f a
    pure (Maybe Percentage -> [Maybe Percentage])
    -> (Change -> Maybe Percentage) -> Change -> [Maybe Percentage]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Change -> Change -> Maybe Percentage
    percentage Change
    a)
          Layout
    _ -> ( AmountDisplayOpts -> BudgetShowMixed
    showMixedAmountLinesB AmountDisplayOpts
    noPrice{displayOrder :: Maybe [Text]
    displayOrder=[Text] -> Maybe [Text]
    forall a. a -> Maybe a
    Just [Text]
    cs, displayMinWidth :: Maybe Int
    displayMinWidth=Maybe Int
    forall a. Maybe a
    Nothing, displayColour :: Bool
    displayColour=Bool
    color_}
               , \Change
    a Change
    b -> (Text -> Maybe Percentage) -> [Text] -> [Maybe Percentage]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Change -> Change -> Text -> Maybe Percentage
    percentage' Change
    a Change
    b) [Text]
    cs)

        showrow :: [BudgetCell] -> [(WideBuilder, BudgetDisplayRow)]
        showrow :: [(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    showrow [(Maybe Change, Maybe Change)]
    row =
          let cs :: [Text]
    cs = [(Maybe Change, Maybe Change)] -> [Text]
    budgetCellsCommodities [(Maybe Change, Maybe Change)]
    row
              (BudgetShowMixed
    showmixed, BudgetPercBudget
    percbudget) = [Text] -> (BudgetShowMixed, BudgetPercBudget)
    rowfuncs [Text]
    cs
           in   [WideBuilder]
    -> [[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])]
    forall a b. [a] -> [b] -> [(a, b)]
    zip ((Text -> WideBuilder) -> [Text] -> [WideBuilder]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap Text -> WideBuilder
    wbFromText [Text]
    cs)
              ([[BudgetDisplayCell]] -> [(WideBuilder, [BudgetDisplayCell])])
    -> ([(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]])
    -> [(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[BudgetDisplayCell]] -> [[BudgetDisplayCell]]
    forall a. [[a]] -> [[a]]
    transpose
              ([[BudgetDisplayCell]] -> [[BudgetDisplayCell]])
    -> ([(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]])
    -> [(Maybe Change, Maybe Change)]
    -> [[BudgetDisplayCell]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((Maybe Change, Maybe Change) -> [BudgetDisplayCell])
    -> [(Maybe Change, Maybe Change)] -> [[BudgetDisplayCell]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (BudgetShowMixed
    -> BudgetPercBudget
    -> (Maybe Change, Maybe Change)
    -> [BudgetDisplayCell]
    showcell BudgetShowMixed
    showmixed BudgetPercBudget
    percbudget)
              ([(Maybe Change, Maybe Change)]
     -> [(WideBuilder, [BudgetDisplayCell])])
    -> [(Maybe Change, Maybe Change)]
    -> [(WideBuilder, [BudgetDisplayCell])]
    forall a b. (a -> b) -> a -> b
    $ [(Maybe Change, Maybe Change)]
    row

        budgetCellsCommodities :: [(Maybe Change, Maybe Change)] -> [Text]
    budgetCellsCommodities = Set Text -> [Text]
    forall a. Set a -> [a]
    S.toList (Set Text -> [Text])
    -> ([(Maybe Change, Maybe Change)] -> Set Text)
    -> [(Maybe Change, Maybe Change)]
    -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Set Text -> Set Text -> Set Text)
    -> Set Text -> [Set Text] -> Set Text
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl' Set Text -> Set Text -> Set Text
    forall a. Ord a => Set a -> Set a -> Set a
    S.union Set Text
    forall a. Monoid a => a
    mempty ([Set Text] -> Set Text)
    -> ([(Maybe Change, Maybe Change)] -> [Set Text])
    -> [(Maybe Change, Maybe Change)]
    -> Set Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((Maybe Change, Maybe Change) -> Set Text)
    -> [(Maybe Change, Maybe Change)] -> [Set Text]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Maybe Change, Maybe Change) -> Set Text
    budgetCellCommodities
        budgetCellCommodities :: BudgetCell -> S.Set CommoditySymbol
        budgetCellCommodities :: (Maybe Change, Maybe Change) -> Set Text
    budgetCellCommodities (Maybe Change
    am, Maybe Change
    bm) = Maybe Change -> Set Text
    f Maybe Change
    am Set Text -> Set Text -> Set Text
    forall a. Ord a => Set a -> Set a -> Set a
    `S.union` Maybe Change -> Set Text
    f Maybe Change
    bm
          where f :: Maybe Change -> Set Text
    f = Set Text -> (Change -> Set Text) -> Maybe Change -> Set Text
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Set Text
    forall a. Monoid a => a
    mempty Change -> Set Text
    maCommodities

        cellswidth :: [BudgetCell] -> [[(Int, Int, Int)]]
        cellswidth :: [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
    cellswidth [(Maybe Change, Maybe Change)]
    row =
          let cs :: [Text]
    cs = [(Maybe Change, Maybe Change)] -> [Text]
    budgetCellsCommodities [(Maybe Change, Maybe Change)]
    row
              (BudgetShowMixed
    showmixed, BudgetPercBudget
    percbudget) = [Text] -> (BudgetShowMixed, BudgetPercBudget)
    rowfuncs [Text]
    cs
              disp :: (Maybe Change, Maybe Change) -> [BudgetDisplayCell]
    disp = BudgetShowMixed
    -> BudgetPercBudget
    -> (Maybe Change, Maybe Change)
    -> [BudgetDisplayCell]
    showcell BudgetShowMixed
    showmixed BudgetPercBudget
    percbudget
              budgetpercwidth :: (WideBuilder, Maybe WideBuilder) -> (Int, Int)
    budgetpercwidth = WideBuilder -> Int
    wbWidth (WideBuilder -> Int)
    -> (Maybe WideBuilder -> Int)
    -> (WideBuilder, Maybe WideBuilder)
    -> (Int, Int)
    forall b c b' c'. (b -> c) -> (b' -> c') -> (b, b') -> (c, c')
    forall (a :: * -> * -> *) b c b' c'.
    Arrow a =>
    a b c -> a b' c' -> a (b, b') (c, c')
    *** Int -> (WideBuilder -> Int) -> Maybe WideBuilder -> Int
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Int
    0 WideBuilder -> Int
    wbWidth
              cellwidth :: BudgetDisplayCell -> (Int, Int, Int)
    cellwidth (WideBuilder
    am, Maybe (WideBuilder, Maybe WideBuilder)
    bm) = let (Int
    bw, Int
    pw) = (Int, Int)
    -> ((WideBuilder, Maybe WideBuilder) -> (Int, Int))
    -> Maybe (WideBuilder, Maybe WideBuilder)
    -> (Int, Int)
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe (Int
    0, Int
    0) (WideBuilder, Maybe WideBuilder) -> (Int, Int)
    budgetpercwidth Maybe (WideBuilder, Maybe WideBuilder)
    bm in (WideBuilder -> Int
    wbWidth WideBuilder
    am, Int
    bw, Int
    pw)
           in ((Maybe Change, Maybe Change) -> [(Int, Int, Int)])
    -> [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((BudgetDisplayCell -> (Int, Int, Int))
    -> [BudgetDisplayCell] -> [(Int, Int, Int)]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap BudgetDisplayCell -> (Int, Int, Int)
    cellwidth ([BudgetDisplayCell] -> [(Int, Int, Int)])
    -> ((Maybe Change, Maybe Change) -> [BudgetDisplayCell])
    -> (Maybe Change, Maybe Change)
    -> [(Int, Int, Int)]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Maybe Change, Maybe Change) -> [BudgetDisplayCell]
    disp) [(Maybe Change, Maybe Change)]
    row

        -- build a list of widths for each column. In the case of transposed budget
        -- reports, the total 'row' must be included in this list
        widths :: [(Int, Int, Int)]
    widths = [Int] -> [Int] -> [Int] -> [(Int, Int, Int)]
    forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
    zip3 [Int]
    actualwidths [Int]
    budgetwidths [Int]
    percentwidths
          where
            actualwidths :: [Int]
    actualwidths  = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map ([Int] -> Int
    forall a. Integral a => [a] -> a
    maximum' ([Int] -> Int)
    -> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map (Int, Int, Int) -> Int
    forall {a} {b} {c}. (a, b, c) -> a
    first3 ) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> a -> b
    $ [[(Int, Int, Int)]]
    cols
            budgetwidths :: [Int]
    budgetwidths  = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map ([Int] -> Int
    forall a. Integral a => [a] -> a
    maximum' ([Int] -> Int)
    -> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map (Int, Int, Int) -> Int
    forall {a} {b} {c}. (a, b, c) -> b
    second3) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> a -> b
    $ [[(Int, Int, Int)]]
    cols
            percentwidths :: [Int]
    percentwidths = ([(Int, Int, Int)] -> Int) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map ([Int] -> Int
    forall a. Integral a => [a] -> a
    maximum' ([Int] -> Int)
    -> ([(Int, Int, Int)] -> [Int]) -> [(Int, Int, Int)] -> Int
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . ((Int, Int, Int) -> Int) -> [(Int, Int, Int)] -> [Int]
    forall a b. (a -> b) -> [a] -> [b]
    map (Int, Int, Int) -> Int
    forall {a} {b} {c}. (a, b, c) -> c
    third3 ) ([[(Int, Int, Int)]] -> [Int]) -> [[(Int, Int, Int)]] -> [Int]
    forall a b. (a -> b) -> a -> b
    $ [[(Int, Int, Int)]]
    cols
            catcolumnwidths :: [[[a]]] -> [[a]]
    catcolumnwidths = ([[a]] -> [[a]] -> [[a]]) -> [[a]] -> [[[a]]] -> [[a]]
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl' (([a] -> [a] -> [a]) -> [[a]] -> [[a]] -> [[a]]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith [a] -> [a] -> [a]
    forall a. [a] -> [a] -> [a]
    (++)) ([[a]] -> [[[a]]] -> [[a]]) -> [[a]] -> [[[a]]] -> [[a]]
    forall a b. (a -> b) -> a -> b
    $ [a] -> [[a]]
    forall a. a -> [a]
    repeat []
            cols :: [[(Int, Int, Int)]]
    cols = [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]
    forall a. [[a]] -> [[a]]
    maybetranspose ([[(Int, Int, Int)]] -> [[(Int, Int, Int)]])
    -> [[(Int, Int, Int)]] -> [[(Int, Int, Int)]]
    forall a b. (a -> b) -> a -> b
    $ [[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]]
    forall {a}. [[[a]]] -> [[a]]
    catcolumnwidths ([[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]])
    -> [[[(Int, Int, Int)]]] -> [[(Int, Int, Int)]]
    forall a b. (a -> b) -> a -> b
    $ (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> [[(Int, Int, Int)]])
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [[[(Int, Int, Int)]]]
    forall a b. (a -> b) -> [a] -> [b]
    map ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
    cellswidth ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]])
    -> (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
        -> [(Maybe Change, Maybe Change)])
    -> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> [[(Int, Int, Int)]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> [(Maybe Change, Maybe Change)]
    forall {a} {a}. PeriodicReportRow a a -> [a]
    rowToBudgetCells) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    items [[[(Int, Int, Int)]]]
    -> [[[(Int, Int, Int)]]] -> [[[(Int, Int, Int)]]]
    forall a. [a] -> [a] -> [a]
    ++ [[(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
    cellswidth ([(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]])
    -> [(Maybe Change, Maybe Change)] -> [[(Int, Int, Int)]]
    forall a b. (a -> b) -> a -> b
    $ PeriodicReportRow () (Maybe Change, Maybe Change)
    -> [(Maybe Change, Maybe Change)]
    forall {a} {a}. PeriodicReportRow a a -> [a]
    rowToBudgetCells PeriodicReportRow () (Maybe Change, Maybe Change)
    tr]

        -- split a BudgetCell into BudgetDisplayCell's (one per commodity when applicable)
        showcell :: BudgetShowMixed -> BudgetPercBudget -> BudgetCell -> BudgetDisplayRow
        showcell :: BudgetShowMixed
    -> BudgetPercBudget
    -> (Maybe Change, Maybe Change)
    -> [BudgetDisplayCell]
    showcell BudgetShowMixed
    showmixed BudgetPercBudget
    percbudget (Maybe Change
    actual, Maybe Change
    mbudget) = [WideBuilder]
    -> [Maybe (WideBuilder, Maybe WideBuilder)] -> [BudgetDisplayCell]
    forall a b. [a] -> [b] -> [(a, b)]
    zip (BudgetShowMixed
    showmixed Change
    actual') [Maybe (WideBuilder, Maybe WideBuilder)]
    full
          where
            actual' :: Change
    actual' = Change -> Maybe Change -> Change
    forall a. a -> Maybe a -> a
    fromMaybe Change
    nullmixedamt Maybe Change
    actual

            budgetAndPerc :: Change -> [(WideBuilder, Maybe WideBuilder)]
    budgetAndPerc Change
    b =
              [WideBuilder]
    -> [Maybe WideBuilder] -> [(WideBuilder, Maybe WideBuilder)]
    forall a b. [a] -> [b] -> [(a, b)]
    zip (BudgetShowMixed
    showmixed Change
    b) ((Percentage -> WideBuilder)
    -> Maybe Percentage -> Maybe WideBuilder
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (Text -> WideBuilder
    wbFromText (Text -> WideBuilder)
    -> (Percentage -> Text) -> Percentage -> WideBuilder
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . String -> Text
    T.pack (String -> Text) -> (Percentage -> String) -> Percentage -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Percentage -> String
    forall a. Show a => a -> String
    show (Percentage -> String)
    -> (Percentage -> Percentage) -> Percentage -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Word8 -> Percentage -> Percentage
    forall i. Integral i => Word8 -> DecimalRaw i -> DecimalRaw i
    roundTo Word8
    0) (Maybe Percentage -> Maybe WideBuilder)
    -> [Maybe Percentage] -> [Maybe WideBuilder]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> BudgetPercBudget
    percbudget Change
    actual' Change
    b)

            full :: [Maybe (WideBuilder, Maybe WideBuilder)]
    full
              | Just Change
    b <- Maybe Change
    mbudget = (WideBuilder, Maybe WideBuilder)
    -> Maybe (WideBuilder, Maybe WideBuilder)
    forall a. a -> Maybe a
    Just ((WideBuilder, Maybe WideBuilder)
     -> Maybe (WideBuilder, Maybe WideBuilder))
    -> [(WideBuilder, Maybe WideBuilder)]
    -> [Maybe (WideBuilder, Maybe WideBuilder)]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Change -> [(WideBuilder, Maybe WideBuilder)]
    budgetAndPerc Change
    b
              | Bool
    otherwise         = Maybe (WideBuilder, Maybe WideBuilder)
    -> [Maybe (WideBuilder, Maybe WideBuilder)]
    forall a. a -> [a]
    repeat Maybe (WideBuilder, Maybe WideBuilder)
    forall a. Maybe a
    Nothing

        paddisplaycell :: (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
        paddisplaycell :: (Int, Int, Int) -> BudgetDisplayCell -> WideBuilder
    paddisplaycell (Int
    actualwidth, Int
    budgetwidth, Int
    percentwidth) (WideBuilder
    actual, Maybe (WideBuilder, Maybe WideBuilder)
    mbudget) = WideBuilder
    full
          where
            toPadded :: WideBuilder -> Builder
    toPadded (WideBuilder Builder
    b Int
    w) =
                (Text -> Builder
    TB.fromText (Text -> Builder) -> (Int -> Text) -> Int -> Builder
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Int -> Text -> Text) -> Text -> Int -> Text
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Int -> Text -> Text
    T.replicate Text
    " " (Int -> Builder) -> Int -> Builder
    forall a b. (a -> b) -> a -> b
    $ Int
    actualwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - Int
    w) Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    b

            (Int
    totalpercentwidth, Int
    totalbudgetwidth) =
              let totalpercentwidth' :: Int
    totalpercentwidth' = if Int
    percentwidth Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 then Int
    0 else Int
    percentwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    5
               in ( Int
    totalpercentwidth'
                  , if Int
    budgetwidth Int -> Int -> Bool
    forall a. Eq a => a -> a -> Bool
    == Int
    0 then Int
    0 else Int
    budgetwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    totalpercentwidth' Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    3
                  )

            -- | Display a padded budget string
            budgetb :: (WideBuilder, Maybe WideBuilder) -> Builder
    budgetb (WideBuilder
    budget, Maybe WideBuilder
    perc) =
              let perct :: Text
    perct = case Maybe WideBuilder
    perc of
                    Maybe WideBuilder
    Nothing  -> Int -> Text -> Text
    T.replicate Int
    totalpercentwidth Text
    " "
                    Just WideBuilder
    pct -> Int -> Text -> Text
    T.replicate (Int
    percentwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - WideBuilder -> Int
    wbWidth WideBuilder
    pct) Text
    " " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> WideBuilder -> Text
    wbToText WideBuilder
    pct Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    "% of "
               in Text -> Builder
    TB.fromText (Text -> Builder) -> Text -> Builder
    forall a b. (a -> b) -> a -> b
    $ Text
    " [" Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    perct Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Int -> Text -> Text
    T.replicate (Int
    budgetwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    - WideBuilder -> Int
    wbWidth WideBuilder
    budget) Text
    " " Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> WideBuilder -> Text
    wbToText WideBuilder
    budget Text -> Text -> Text
    forall a. Semigroup a => a -> a -> a
    <> Text
    "]"

            emptyBudget :: Builder
    emptyBudget = Text -> Builder
    TB.fromText (Text -> Builder) -> Text -> Builder
    forall a b. (a -> b) -> a -> b
    $ Int -> Text -> Text
    T.replicate Int
    totalbudgetwidth Text
    " "

            full :: WideBuilder
    full = (Builder -> Int -> WideBuilder) -> Int -> Builder -> WideBuilder
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Builder -> Int -> WideBuilder
    WideBuilder (Int
    actualwidth Int -> Int -> Int
    forall a. Num a => a -> a -> a
    + Int
    totalbudgetwidth) (Builder -> WideBuilder) -> Builder -> WideBuilder
    forall a b. (a -> b) -> a -> b
    $
                WideBuilder -> Builder
    toPadded WideBuilder
    actual Builder -> Builder -> Builder
    forall a. Semigroup a => a -> a -> a
    <> Builder
    -> ((WideBuilder, Maybe WideBuilder) -> Builder)
    -> Maybe (WideBuilder, Maybe WideBuilder)
    -> Builder
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Builder
    emptyBudget (WideBuilder, Maybe WideBuilder) -> Builder
    budgetb Maybe (WideBuilder, Maybe WideBuilder)
    mbudget

        -- | Calculate the percentage of actual change to budget goal to show, if any.
        -- If valuing at cost, both amounts are converted to cost before comparing.
        -- A percentage will not be shown if:
        -- - actual or goal are not the same, single, commodity
        -- - the goal is zero
        percentage :: Change -> BudgetGoal -> Maybe Percentage
        percentage :: Change -> Change -> Maybe Percentage
    percentage Change
    actual Change
    budget =
          case (Change -> [Amount]
    costedAmounts Change
    actual, Change -> [Amount]
    costedAmounts Change
    budget) of
            ([Amount
    a], [Amount
    b]) | (Amount -> Text
    acommodity Amount
    a Text -> Text -> Bool
    forall a. Eq a => a -> a -> Bool
    == Amount -> Text
    acommodity Amount
    b Bool -> Bool -> Bool
    || Amount -> Bool
    amountLooksZero Amount
    a) Bool -> Bool -> Bool
    && Bool -> Bool
    not (Amount -> Bool
    amountLooksZero Amount
    b)
                -> Percentage -> Maybe Percentage
    forall a. a -> Maybe a
    Just (Percentage -> Maybe Percentage) -> Percentage -> Maybe Percentage
    forall a b. (a -> b) -> a -> b
    $ Percentage
    100 Percentage -> Percentage -> Percentage
    forall a. Num a => a -> a -> a
    * Amount -> Percentage
    aquantity Amount
    a Percentage -> Percentage -> Percentage
    forall a. Fractional a => a -> a -> a
    / Amount -> Percentage
    aquantity Amount
    b
            ([Amount], [Amount])
    _   -> -- trace (pshow $ (maybecost actual, maybecost budget))  -- debug missing percentage
                   Maybe Percentage
    forall a. Maybe a
    Nothing
          where
            costedAmounts :: Change -> [Amount]
    costedAmounts = case Maybe ConversionOp
    conversionop_ of
                Just ConversionOp
    ToCost -> Change -> [Amount]
    amounts (Change -> [Amount]) -> (Change -> Change) -> Change -> [Amount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Change -> Change
    mixedAmountCost
                Maybe ConversionOp
    _           -> Change -> [Amount]
    amounts

        -- | Calculate the percentage of actual change to budget goal for a particular commodity
        percentage' :: Change -> BudgetGoal -> CommoditySymbol -> Maybe Percentage
        percentage' :: Change -> Change -> Text -> Maybe Percentage
    percentage' Change
    am Change
    bm Text
    c = case ((,) (Maybe Amount -> Maybe Amount -> (Maybe Amount, Maybe Amount))
    -> (Change -> Maybe Amount)
    -> Change
    -> Change
    -> (Maybe Amount, Maybe Amount)
    forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
    `on` (Amount -> Bool) -> [Amount] -> Maybe Amount
    forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
    find (Text -> Text -> Bool
    forall a. Eq a => a -> a -> Bool
    (==) Text
    c (Text -> Bool) -> (Amount -> Text) -> Amount -> Bool
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Amount -> Text
    acommodity) ([Amount] -> Maybe Amount)
    -> (Change -> [Amount]) -> Change -> Maybe Amount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Change -> [Amount]
    amounts) Change
    am Change
    bm of
            (Just Amount
    a, Just Amount
    b) -> Change -> Change -> Maybe Percentage
    percentage (Amount -> Change
    mixedAmount Amount
    a) (Amount -> Change
    mixedAmount Amount
    b)
            (Maybe Amount, Maybe Amount)
    _                -> Maybe Percentage
    forall a. Maybe a
    Nothing

    -- XXX generalise this with multiBalanceReportAsCsv ?
    -- | Render a budget report as CSV. Like multiBalanceReportAsCsv,
    -- but includes alternating actual and budget amount columns.
    budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[Text]]
    budgetReportAsCsv :: ReportOpts -> BudgetReport -> [[Text]]
    budgetReportAsCsv
      ReportOpts{Bool
    Int
    [Text]
    [Status]
    Maybe Int
    Maybe Text
    Maybe NormalSign
    Maybe ValuationType
    Maybe ConversionOp
    Interval
    Period
    StringFormat
    Layout
    AccountListMode
    BalanceAccumulation
    BalanceCalculation
    accountlistmode_ :: ReportOpts -> AccountListMode
    empty_ :: ReportOpts -> Bool
    infer_prices_ :: ReportOpts -> Bool
    interval_ :: ReportOpts -> Interval
    budgetpat_ :: ReportOpts -> Maybe Text
    period_ :: ReportOpts -> Period
    statuses_ :: ReportOpts -> [Status]
    conversionop_ :: ReportOpts -> Maybe ConversionOp
    value_ :: ReportOpts -> Maybe ValuationType
    depth_ :: ReportOpts -> Maybe Int
    date2_ :: ReportOpts -> Bool
    no_elide_ :: ReportOpts -> Bool
    real_ :: ReportOpts -> Bool
    format_ :: ReportOpts -> StringFormat
    pretty_ :: ReportOpts -> Bool
    querystring_ :: ReportOpts -> [Text]
    average_ :: ReportOpts -> Bool
    related_ :: ReportOpts -> Bool
    txn_dates_ :: ReportOpts -> Bool
    balancecalc_ :: ReportOpts -> BalanceCalculation
    balanceaccum_ :: ReportOpts -> BalanceAccumulation
    drop_ :: ReportOpts -> Int
    declared_ :: ReportOpts -> Bool
    row_total_ :: ReportOpts -> Bool
    no_total_ :: ReportOpts -> Bool
    show_costs_ :: ReportOpts -> Bool
    sort_amount_ :: ReportOpts -> Bool
    percent_ :: ReportOpts -> Bool
    invert_ :: ReportOpts -> Bool
    normalbalance_ :: ReportOpts -> Maybe NormalSign
    color_ :: ReportOpts -> Bool
    transpose_ :: ReportOpts -> Bool
    layout_ :: ReportOpts -> Layout
    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_ :: [Text]
    average_ :: Bool
    related_ :: Bool
    txn_dates_ :: Bool
    balancecalc_ :: BalanceCalculation
    balanceaccum_ :: BalanceAccumulation
    budgetpat_ :: Maybe Text
    accountlistmode_ :: AccountListMode
    drop_ :: Int
    declared_ :: Bool
    row_total_ :: Bool
    no_total_ :: Bool
    show_costs_ :: Bool
    sort_amount_ :: Bool
    percent_ :: Bool
    invert_ :: Bool
    normalbalance_ :: Maybe NormalSign
    color_ :: Bool
    transpose_ :: Bool
    layout_ :: Layout
    ..}
      (PeriodicReport [DateSpan]
    colspans [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    items PeriodicReportRow () (Maybe Change, Maybe Change)
    tr)
      = (if Bool
    transpose_ then [[Text]] -> [[Text]]
    forall a. [[a]] -> [[a]]
    transpose else [[Text]] -> [[Text]]
    forall a. a -> a
    id) ([[Text]] -> [[Text]]) -> [[Text]] -> [[Text]]
    forall a b. (a -> b) -> a -> b
    $

      -- heading row

      -- heading row
      (Text
    "Account" Text -> [Text] -> [Text]
    forall a. a -> [a] -> [a]
    :
      [Text
    "Commodity" | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    == Layout
    LayoutBare ]
       [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ (DateSpan -> [Text]) -> [DateSpan] -> [Text]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (\DateSpan
    spn -> [DateSpan -> Text
    showDateSpan DateSpan
    spn, Text
    "budget"]) [DateSpan]
    colspans
       [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ [[Text]] -> [Text]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[Text
    "Total"  ,Text
    "budget"] | Bool
    row_total_]
       [Text] -> [Text] -> [Text]
    forall a. [a] -> [a] -> [a]
    ++ [[Text]] -> [Text]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[Text
    "Average",Text
    "budget"] | Bool
    average_]
      ) [Text] -> [[Text]] -> [[Text]]
    forall a. a -> [a] -> [a]
    :

      -- account rows

      -- account rows
      (PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> [[Text]])
    -> [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    -> [[Text]]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap ((PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
     -> Text)
    -> PeriodicReportRow DisplayName (Maybe Change, Maybe Change)
    -> [[Text]]
    forall a.
    (PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
    -> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
    rowAsTexts PeriodicReportRow DisplayName (Maybe Change, Maybe Change) -> Text
    forall a. PeriodicReportRow DisplayName a -> Text
    prrFullName) [PeriodicReportRow DisplayName (Maybe Change, Maybe Change)]
    items

      -- totals row
      [[Text]] -> [[Text]] -> [[Text]]
    forall a. [a] -> [a] -> [a]
    ++ [[[Text]]] -> [[Text]]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [ (PeriodicReportRow () (Maybe Change, Maybe Change) -> Text)
    -> PeriodicReportRow () (Maybe Change, Maybe Change) -> [[Text]]
    forall a.
    (PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
    -> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
    rowAsTexts (Text -> PeriodicReportRow () (Maybe Change, Maybe Change) -> Text
    forall a b. a -> b -> a
    const Text
    "Total:") PeriodicReportRow () (Maybe Change, Maybe Change)
    tr | Bool -> Bool
    not Bool
    no_total_ ]

      where
        flattentuples :: [(a, a)] -> [a]
    flattentuples [(a, a)]
    tups = [[a]] -> [a]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[a
    a,a
    b] | (a
    a,a
    b) <- [(a, a)]
    tups]
        showNorm :: Maybe Change -> Text
    showNorm = Text -> (Change -> Text) -> Maybe Change -> Text
    forall b a. b -> (a -> b) -> Maybe a -> b
    maybe Text
    "" (WideBuilder -> Text
    wbToText (WideBuilder -> Text) -> (Change -> WideBuilder) -> Change -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . AmountDisplayOpts -> Change -> WideBuilder
    showMixedAmountB AmountDisplayOpts
    oneLine)

        rowAsTexts :: (PeriodicReportRow a BudgetCell -> Text)
                   -> PeriodicReportRow a BudgetCell
                   -> [[Text]]
        rowAsTexts :: forall a.
    (PeriodicReportRow a (Maybe Change, Maybe Change) -> Text)
    -> PeriodicReportRow a (Maybe Change, Maybe Change) -> [[Text]]
    rowAsTexts PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
    render row :: PeriodicReportRow a (Maybe Change, Maybe Change)
    row@(PeriodicReportRow a
    _ [(Maybe Change, Maybe Change)]
    as (Maybe Change
    rowtot,Maybe Change
    budgettot) (Maybe Change
    rowavg, Maybe Change
    budgetavg))
          | Layout
    layout_ Layout -> Layout -> Bool
    forall a. Eq a => a -> a -> Bool
    /= Layout
    LayoutBare = [PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
    render PeriodicReportRow a (Maybe Change, Maybe Change)
    row Text -> [Text] -> [Text]
    forall a. a -> [a] -> [a]
    : (Maybe Change -> Text) -> [Maybe Change] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap Maybe Change -> Text
    showNorm [Maybe Change]
    vals]
          | Bool
    otherwise =
                [[Text]] -> [[Text]]
    joinNames ([[Text]] -> [[Text]])
    -> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Text -> [Text] -> [Text]) -> [Text] -> [[Text]] -> [[Text]]
    forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
    zipWith (:) [Text]
    cs  -- add symbols and names
              ([[Text]] -> [[Text]])
    -> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . [[Text]] -> [[Text]]
    forall a. [[a]] -> [[a]]
    transpose                   -- each row becomes a list of Text quantities
              ([[Text]] -> [[Text]])
    -> ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Maybe Change -> [Text]) -> [Maybe Change] -> [[Text]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap ((WideBuilder -> Text) -> [WideBuilder] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap WideBuilder -> Text
    wbToText ([WideBuilder] -> [Text])
    -> (Maybe Change -> [WideBuilder]) -> Maybe Change -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . AmountDisplayOpts -> BudgetShowMixed
    showMixedAmountLinesB AmountDisplayOpts
    oneLine{displayOrder :: Maybe [Text]
    displayOrder=[Text] -> Maybe [Text]
    forall a. a -> Maybe a
    Just [Text]
    cs, displayMinWidth :: Maybe Int
    displayMinWidth=Maybe Int
    forall a. Maybe a
    Nothing}
                     BudgetShowMixed
    -> (Maybe Change -> Change) -> Maybe Change -> [WideBuilder]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    .Change -> Maybe Change -> Change
    forall a. a -> Maybe a -> a
    fromMaybe Change
    nullmixedamt)
              ([Maybe Change] -> [[Text]]) -> [Maybe Change] -> [[Text]]
    forall a b. (a -> b) -> a -> b
    $ [Maybe Change]
    vals
          where
            cs :: [Text]
    cs = Set Text -> [Text]
    forall a. Set a -> [a]
    S.toList (Set Text -> [Text])
    -> ([Change] -> Set Text) -> [Change] -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Set Text -> Set Text -> Set Text)
    -> Set Text -> [Set Text] -> Set Text
    forall b a. (b -> a -> b) -> b -> [a] -> b
    forall (t :: * -> *) b a.
    Foldable t =>
    (b -> a -> b) -> b -> t a -> b
    foldl' Set Text -> Set Text -> Set Text
    forall a. Ord a => Set a -> Set a -> Set a
    S.union Set Text
    forall a. Monoid a => a
    mempty ([Set Text] -> Set Text)
    -> ([Change] -> [Set Text]) -> [Change] -> Set Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Change -> Set Text) -> [Change] -> [Set Text]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap Change -> Set Text
    maCommodities ([Change] -> [Text]) -> [Change] -> [Text]
    forall a b. (a -> b) -> a -> b
    $ [Maybe Change] -> [Change]
    forall a. [Maybe a] -> [a]
    catMaybes [Maybe Change]
    vals
            vals :: [Maybe Change]
    vals = [(Maybe Change, Maybe Change)] -> [Maybe Change]
    forall {a}. [(a, a)] -> [a]
    flattentuples [(Maybe Change, Maybe Change)]
    as
                [Maybe Change] -> [Maybe Change] -> [Maybe Change]
    forall a. [a] -> [a] -> [a]
    ++ [[Maybe Change]] -> [Maybe Change]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[Maybe Change
    rowtot, Maybe Change
    budgettot] | Bool
    row_total_]
                [Maybe Change] -> [Maybe Change] -> [Maybe Change]
    forall a. [a] -> [a] -> [a]
    ++ [[Maybe Change]] -> [Maybe Change]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [[Maybe Change
    rowavg, Maybe Change
    budgetavg] | Bool
    average_]

            joinNames :: [[Text]] -> [[Text]]
    joinNames = ([Text] -> [Text]) -> [[Text]] -> [[Text]]
    forall a b. (a -> b) -> [a] -> [b]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap (PeriodicReportRow a (Maybe Change, Maybe Change) -> Text
    render PeriodicReportRow a (Maybe Change, Maybe Change)
    row Text -> [Text] -> [Text]
    forall a. a -> [a] -> [a]
    :)

    -- tests

    tests_BudgetReport :: TestTree
    tests_BudgetReport = String -> [TestTree] -> TestTree
    testGroup String
    "BudgetReport" [
     ]