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

    A transactions report. Like an EntriesReport, but with more
    information such as a running balance.

    -}

    module Hledger.Reports.TransactionsReport (
      TransactionsReport,
      TransactionsReportItem,
      transactionsReport,
      transactionsReportByCommodity,
      triOrigTransaction,
      triDate,
      triAmount,
      triBalance,
      triCommodityAmount,
      triCommodityBalance,
      tests_TransactionsReport
    )
    where

    import Data.List
    import Data.List.Extra (nubSort)
    import Data.Ord

    import Hledger.Data
    import Hledger.Query
    import Hledger.Reports.ReportOptions
    import Hledger.Reports.AccountTransactionsReport
    import Hledger.Utils

    -- | A transactions report includes a list of transactions touching multiple accounts
    -- (posting-filtered and unfiltered variants), a running balance, and some
    -- other information helpful for rendering a register view (a flag
    -- indicating multiple other accounts and a display string describing
    -- them) with or without a notion of current account(s).
    -- Two kinds of report use this data structure, see transactionsReport
    -- and accountTransactionsReport below for details.
    type TransactionsReport = (String                   -- label for the balance column, eg "balance" or "total"
                              ,[TransactionsReportItem] -- line items, one per transaction
                              )
    type TransactionsReportItem = (Transaction -- the original journal transaction, unmodified
                                  ,Transaction -- the transaction as seen from a particular account, with postings maybe filtered
                                  ,Bool        -- is this a split, ie more than one other account posting
                                  ,String      -- a display string describing the other account(s), if any
                                  ,MixedAmount -- the amount posted to the current account(s) by the filtered postings (or total amount posted)
                                  ,MixedAmount -- the running total of item amounts, starting from zero;
                                               -- or with --historical, the running total including items
                                               -- (matched by the report query) preceding the report period
                                  )

    triOrigTransaction :: (a, b, c, d, e, f) -> a
    triOrigTransaction (torig :: a
    torig,_,_,_,_,_) = a
    torig
    triDate :: (a, Transaction, c, d, e, f) -> Day
    triDate (_,tacct :: Transaction
    tacct,_,_,_,_) = Transaction -> Day
    tdate Transaction
    tacct
    triAmount :: (a, b, c, d, e, f) -> e
    triAmount (_,_,_,_,a :: e
    a,_) = e
    a
    triBalance :: (a, b, c, d, e, f) -> f
    triBalance (_,_,_,_,_,a :: f
    a) = f
    a
    triCommodityAmount :: CommoditySymbol -> (a, b, c, d, MixedAmount, f) -> MixedAmount
    triCommodityAmount c :: CommoditySymbol
    c = CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity CommoditySymbol
    c  (MixedAmount -> MixedAmount)
    -> ((a, b, c, d, MixedAmount, f) -> MixedAmount)
    -> (a, b, c, d, MixedAmount, f)
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (a, b, c, d, MixedAmount, f) -> MixedAmount
    forall a b c d e f. (a, b, c, d, e, f) -> e
    triAmount
    triCommodityBalance :: CommoditySymbol -> (a, b, c, d, e, MixedAmount) -> MixedAmount
    triCommodityBalance c :: CommoditySymbol
    c = CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity CommoditySymbol
    c  (MixedAmount -> MixedAmount)
    -> ((a, b, c, d, e, MixedAmount) -> MixedAmount)
    -> (a, b, c, d, e, MixedAmount)
    -> MixedAmount
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (a, b, c, d, e, MixedAmount) -> MixedAmount
    forall a b c d e f. (a, b, c, d, e, f) -> f
    triBalance

    totallabel :: String
    totallabel   = "Period Total"

    -- | Select transactions from the whole journal. This is similar to a
    -- "postingsReport" except with transaction-based report items which
    -- are ordered most recent first. XXX Or an EntriesReport - use that instead ?
    -- This is used by hledger-web's journal view.
    transactionsReport :: ReportOpts -> Journal -> Query -> TransactionsReport
    transactionsReport :: ReportOpts -> Journal -> Query -> TransactionsReport
    transactionsReport opts :: ReportOpts
    opts j :: Journal
    j q :: Query
    q = (String
    totallabel, [AccountTransactionsReportItem]
    items)
       where
         -- XXX items' first element should be the full transaction with all postings
         items :: [AccountTransactionsReportItem]
    items = [AccountTransactionsReportItem] -> [AccountTransactionsReportItem]
    forall a. [a] -> [a]
    reverse ([AccountTransactionsReportItem]
     -> [AccountTransactionsReportItem])
    -> [AccountTransactionsReportItem]
    -> [AccountTransactionsReportItem]
    forall a b. (a -> b) -> a -> b
    $ Query
    -> Query
    -> MixedAmount
    -> (MixedAmount -> MixedAmount)
    -> [Transaction]
    -> [AccountTransactionsReportItem]
    accountTransactionsReportItems Query
    q Query
    None MixedAmount
    nullmixedamt MixedAmount -> MixedAmount
    forall a. a -> a
    id [Transaction]
    ts
         ts :: [Transaction]
    ts    = (Transaction -> Transaction -> Ordering)
    -> [Transaction] -> [Transaction]
    forall a. (a -> a -> Ordering) -> [a] -> [a]
    sortBy ((Transaction -> Day) -> Transaction -> Transaction -> Ordering
    forall a b. Ord a => (b -> a) -> b -> b -> Ordering
    comparing Transaction -> Day
    date) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $ (Transaction -> Bool) -> [Transaction] -> [Transaction]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Query
    q Query -> Transaction -> Bool
    `matchesTransaction`) ([Transaction] -> [Transaction]) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $ Journal -> [Transaction]
    jtxns (Journal -> [Transaction]) -> Journal -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $ ReportOpts -> Journal -> Journal
    journalSelectingAmountFromOpts ReportOpts
    opts Journal
    j
         date :: Transaction -> Day
    date  = ReportOpts -> Transaction -> Day
    transactionDateFn ReportOpts
    opts

    -- | Split a transactions report whose items may involve several commodities,
    -- into one or more single-commodity transactions reports.
    transactionsReportByCommodity :: TransactionsReport -> [(CommoditySymbol, TransactionsReport)]
    transactionsReportByCommodity :: TransactionsReport -> [(CommoditySymbol, TransactionsReport)]
    transactionsReportByCommodity tr :: TransactionsReport
    tr =
      [(CommoditySymbol
    c, CommoditySymbol -> TransactionsReport -> TransactionsReport
    filterTransactionsReportByCommodity CommoditySymbol
    c TransactionsReport
    tr) | CommoditySymbol
    c <- TransactionsReport -> [CommoditySymbol]
    forall (t :: * -> *) a a b c d f.
    Foldable t =>
    (a, t (a, b, c, d, MixedAmount, f)) -> [CommoditySymbol]
    transactionsReportCommodities TransactionsReport
    tr]
      where
        transactionsReportCommodities :: (a, t (a, b, c, d, MixedAmount, f)) -> [CommoditySymbol]
    transactionsReportCommodities (_,items :: t (a, b, c, d, MixedAmount, f)
    items) =
          [CommoditySymbol] -> [CommoditySymbol]
    forall a. Ord a => [a] -> [a]
    nubSort ([CommoditySymbol] -> [CommoditySymbol])
    -> ([Amount] -> [CommoditySymbol]) -> [Amount] -> [CommoditySymbol]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Amount -> CommoditySymbol) -> [Amount] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> CommoditySymbol
    acommodity ([Amount] -> [CommoditySymbol]) -> [Amount] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ ((a, b, c, d, MixedAmount, f) -> [Amount])
    -> t (a, b, c, d, MixedAmount, f) -> [Amount]
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap (MixedAmount -> [Amount]
    amounts (MixedAmount -> [Amount])
    -> ((a, b, c, d, MixedAmount, f) -> MixedAmount)
    -> (a, b, c, d, MixedAmount, f)
    -> [Amount]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (a, b, c, d, MixedAmount, f) -> MixedAmount
    forall a b c d e f. (a, b, c, d, e, f) -> e
    triAmount) t (a, b, c, d, MixedAmount, f)
    items

    -- Remove transaction report items and item amount (and running
    -- balance amount) components that don't involve the specified
    -- commodity. Other item fields such as the transaction are left unchanged.
    filterTransactionsReportByCommodity :: CommoditySymbol -> TransactionsReport -> TransactionsReport
    filterTransactionsReportByCommodity :: CommoditySymbol -> TransactionsReport -> TransactionsReport
    filterTransactionsReportByCommodity c :: CommoditySymbol
    c (label :: String
    label,items :: [AccountTransactionsReportItem]
    items) =
      (String
    label, [AccountTransactionsReportItem] -> [AccountTransactionsReportItem]
    forall a b c d.
    [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    fixTransactionsReportItemBalances ([AccountTransactionsReportItem]
     -> [AccountTransactionsReportItem])
    -> [AccountTransactionsReportItem]
    -> [AccountTransactionsReportItem]
    forall a b. (a -> b) -> a -> b
    $ [[AccountTransactionsReportItem]]
    -> [AccountTransactionsReportItem]
    forall (t :: * -> *) a. Foldable t => t [a] -> [a]
    concat [CommoditySymbol
    -> AccountTransactionsReportItem -> [AccountTransactionsReportItem]
    forall a b c d f.
    CommoditySymbol
    -> (a, b, c, d, MixedAmount, f) -> [(a, b, c, d, MixedAmount, f)]
    filterTransactionsReportItemByCommodity CommoditySymbol
    c AccountTransactionsReportItem
    i | AccountTransactionsReportItem
    i <- [AccountTransactionsReportItem]
    items])
      where
        filterTransactionsReportItemByCommodity :: CommoditySymbol
    -> (a, b, c, d, MixedAmount, f) -> [(a, b, c, d, MixedAmount, f)]
    filterTransactionsReportItemByCommodity c :: CommoditySymbol
    c (t :: a
    t,t2 :: b
    t2,s :: c
    s,o :: d
    o,a :: MixedAmount
    a,bal :: f
    bal)
          | CommoditySymbol
    c CommoditySymbol -> [CommoditySymbol] -> Bool
    forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
    `elem` [CommoditySymbol]
    cs = [(a, b, c, d, MixedAmount, f)
    item']
          | Bool
    otherwise   = []
          where
            cs :: [CommoditySymbol]
    cs = (Amount -> CommoditySymbol) -> [Amount] -> [CommoditySymbol]
    forall a b. (a -> b) -> [a] -> [b]
    map Amount -> CommoditySymbol
    acommodity ([Amount] -> [CommoditySymbol]) -> [Amount] -> [CommoditySymbol]
    forall a b. (a -> b) -> a -> b
    $ MixedAmount -> [Amount]
    amounts MixedAmount
    a
            item' :: (a, b, c, d, MixedAmount, f)
    item' = (a
    t,b
    t2,c
    s,d
    o,MixedAmount
    a',f
    bal)
            a' :: MixedAmount
    a' = CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity CommoditySymbol
    c MixedAmount
    a

        fixTransactionsReportItemBalances :: [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    fixTransactionsReportItemBalances [] = []
        fixTransactionsReportItemBalances [i :: (a, b, c, d, MixedAmount, MixedAmount)
    i] = [(a, b, c, d, MixedAmount, MixedAmount)
    i]
        fixTransactionsReportItemBalances items :: [(a, b, c, d, MixedAmount, MixedAmount)]
    items = [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    forall a. [a] -> [a]
    reverse ([(a, b, c, d, MixedAmount, MixedAmount)]
     -> [(a, b, c, d, MixedAmount, MixedAmount)])
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    forall a b. (a -> b) -> a -> b
    $ (a, b, c, d, MixedAmount, MixedAmount)
    i(a, b, c, d, MixedAmount, MixedAmount)
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    forall a. a -> [a] -> [a]
    :(MixedAmount
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    forall t a b c d f.
    Num t =>
    t -> [(a, b, c, d, t, f)] -> [(a, b, c, d, t, t)]
    go MixedAmount
    startbal [(a, b, c, d, MixedAmount, MixedAmount)]
    is)
          where
            i :: (a, b, c, d, MixedAmount, MixedAmount)
    i:is :: [(a, b, c, d, MixedAmount, MixedAmount)]
    is = [(a, b, c, d, MixedAmount, MixedAmount)]
    -> [(a, b, c, d, MixedAmount, MixedAmount)]
    forall a. [a] -> [a]
    reverse [(a, b, c, d, MixedAmount, MixedAmount)]
    items
            startbal :: MixedAmount
    startbal = CommoditySymbol -> MixedAmount -> MixedAmount
    filterMixedAmountByCommodity CommoditySymbol
    c (MixedAmount -> MixedAmount) -> MixedAmount -> MixedAmount
    forall a b. (a -> b) -> a -> b
    $ (a, b, c, d, MixedAmount, MixedAmount) -> MixedAmount
    forall a b c d e f. (a, b, c, d, e, f) -> f
    triBalance (a, b, c, d, MixedAmount, MixedAmount)
    i
            go :: t -> [(a, b, c, d, t, f)] -> [(a, b, c, d, t, t)]
    go _ [] = []
            go bal :: t
    bal ((t :: a
    t,t2 :: b
    t2,s :: c
    s,o :: d
    o,amt :: t
    amt,_):is :: [(a, b, c, d, t, f)]
    is) = (a
    t,b
    t2,c
    s,d
    o,t
    amt,t
    bal')(a, b, c, d, t, t) -> [(a, b, c, d, t, t)] -> [(a, b, c, d, t, t)]
    forall a. a -> [a] -> [a]
    :t -> [(a, b, c, d, t, f)] -> [(a, b, c, d, t, t)]
    go t
    bal' [(a, b, c, d, t, f)]
    is
              where bal' :: t
    bal' = t
    bal t -> t -> t
    forall a. Num a => a -> a -> a
    + t
    amt

    -- tests

    tests_TransactionsReport :: TestTree
    tests_TransactionsReport = String -> [TestTree] -> TestTree
    tests "TransactionsReport" [
     ]