packages feed

Hastructure-0.50.0: src/Cashflow.hs

{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE DeriveAnyClass       #-}
{-# LANGUAGE DataKinds       #-}

module Cashflow (CashFlowFrame(..),Principals,Interests,Amount
                ,combine,mergePoolCf,sumTsCF,tsSetLoss,tsSetRecovery
                ,sizeCashFlowFrame,aggTsByDates,emptyCashFlowFrame
                ,mflowInterest,mflowPrincipal,mflowRecovery,mflowPrepayment
                ,mflowRental,mflowRate,sumPoolFlow,splitTrs,aggregateTsByDate
                ,mflowDefault,mflowLoss
                ,getDatesCashFlowFrame
                ,lookupSource,lookupSourceM,combineTss
                ,mflowBegBalance,tsDefaultBal
                ,mflowBorrowerNum,mflowPrepaymentPenalty,tsRowBalance
                ,emptyTsRow,mflowAmortAmount
                ,tsTotalCash, setPrepaymentPenalty, setPrepaymentPenaltyFlow
                ,getDate,getTxnLatestAsOf,totalPrincipal
                ,mflowWeightAverageBalance,tsDate
                ,totalLoss,totalDefault,totalRecovery,firstDate
                ,shiftCfToStartDate,cfInsertHead,buildBegTsRow,insertBegTsRow
                ,tsCumDefaultBal,tsCumDelinqBal,tsCumLossBal,tsCumRecoveriesBal
                ,TsRow(..),cfAt,cutoffTrs,patchCumulative,extendTxns,dropTailEmptyTxns
                ,cashflowTxn,clawbackInt,scaleTsRow,mflowFeePaid, currentCumulativeStat, patchCumulativeAtInit
                ,mergeCf,buildStartTsRow
                ,txnCumulativeStats,consolidateCashFlow, cfBeginStatus, getBegBalCashFlowFrame
                ,splitCashFlowFrameByDate, mergePoolCf2, buildBegBal, extendCashFlow, patchBalance
                ,splitPoolCashflowByDate
                ,getAllDatesCashFlowFrame,splitCf, cutoffCashflow
                ,AssetCashflow,PoolCashflow
                ,emptyCashflow,isEmptyRow2
                ) where

import Data.Time (Day)
import Data.Fixed
import Lib (weightedBy,toDate,getIntervalFactors,daysBetween,paySeqLiabilitiesAmt)
import Util (mulBR,mulBInt,mulIR,lastOf)
import DateUtil ( splitByDate )
import Types
import qualified Data.Map as Map
import qualified Data.Time as T
import qualified Data.List as L
import Data.Maybe 

import Data.Aeson hiding (json)
import Language.Haskell.TH
import GHC.Generics
import Data.Aeson.TH
import Data.Aeson.Types
import Text.Printf

import Debug.Trace
import qualified Control.Lens as Map
import Control.Applicative (liftA2)
import Data.OpenApi (HasPatch(patch), HasXml (xml))
import Control.DeepSeq (NFData,rnf)
import Data.Text.Internal.Encoding.Fusion (streamUtf16BE)

import qualified Text.Tabular as TT
import qualified Text.Tabular.AsciiArt as A
import Control.Lens hiding (element)
import Control.Lens.TH

debug = flip trace

type Delinquent = Balance
type Amounts = [Float]
type Principals = [Principal]
type Interests = [Interest]
type Prepayments = [Prepayment]
type Recoveries = [Recovery]
type Rates = [Rate]

type CumulativeStat = (CumPrincipal,CumPrepay,CumDelinq,CumDefault,CumRecovery,CumLoss)
type AssetCashflow = CashFlowFrame
type PoolCashflow = (AssetCashflow, Maybe [AssetCashflow])
emptyCashflow = CashFlowFrame (0,epocDate,Nothing) []


instance Monoid CashFlowFrame where
  mempty = emptyCashflow

instance Semigroup CashFlowFrame where
  CashFlowFrame (begBal1, begDate1, mAccInt1) ts1 <> CashFlowFrame (begBal2, begDate2, mAccInt2) ts2 
    = CashFlowFrame (begBal1,begDate1,mAccInt1) (ts1 <> ts2)

opStats :: (Balance -> Balance -> Balance) -> Maybe CumulativeStat -> Maybe CumulativeStat -> Maybe CumulativeStat
opStats op (Just (a1,b1,c1,d1,e1,f1)) (Just (a2,b2,c3,d2,e2,f2)) = Just (op a1 a2,op b1 b2,op c1 c3,op d1 d2,op e1 e2,op f1 f2)
opStats op Nothing Nothing = Nothing
opStats op (Just a) Nothing = Just a
opStats op Nothing (Just a) = Just a

sumStats :: Maybe CumulativeStat -> Maybe CumulativeStat -> Maybe CumulativeStat
-- sumStats (a1,b1,c1,d1,e1,f1) (a2,b2,c3,d2,e2,f2) = (a1+a2,b1+b2,c1+c3,d1+d2,e1+e2,f1+f2)
sumStats s1 s2 = opStats (+) s1 s2

subStats :: Maybe CumulativeStat -> Maybe CumulativeStat -> Maybe CumulativeStat
-- subStats (a1,b1,c1,d1,e1,f1) (a2,b2,c3,d2,e2,f2) = (a1-a2,b1-b2,c1-c3,d1-d2,e1-e2,f1-f2)
subStats s1 s2 = opStats (-) s1 s2

maxStats :: Maybe CumulativeStat -> Maybe CumulativeStat -> Maybe CumulativeStat
-- maxStats (a1,b1,c1,d1,e1,f1) (a2,b2,c3,d2,e2,f2) = (max a1 a2,max b1 b2,max c1 c3,max d1 d2,max e1 e2,max f1 f2)
maxStats s1 s2 = opStats max s1 s2

splitStats :: Rational -> CumulativeStat -> CumulativeStat
splitStats r st1@(a1,b1,c1,d1,e1,f1) = ((`mulBR` r) a1,(`mulBR` r) b1,(`mulBR` r) c1,(`mulBR` r) d1,(`mulBR` r) e1,(`mulBR` r) f1)

type Depreciation = Balance
type NewDepreciation = Balance 
type AccuredFee = Balance
type FeePaid = Balance

startOfTime = T.fromGregorian 1900 1 1

data TsRow = CashFlow Date Amount
           | BondFlow Date Balance Principal Interest
           | MortgageFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat)
           | MortgageDelinqFlow Date Balance Principal Interest Prepayment Delinquent Default Recovery Loss IRate (Maybe BorrowerNum) (Maybe PrepaymentPenalty) (Maybe CumulativeStat)
           | LoanFlow Date Balance Principal Interest Prepayment Default Recovery Loss IRate (Maybe CumulativeStat)
           | LeaseFlow Date Balance Rental Default
           | FixedFlow Date Balance NewDepreciation Depreciation Balance Balance -- unit cash 
           | ReceivableFlow Date Balance AccuredFee Principal FeePaid Default Recovery Loss (Maybe CumulativeStat) 
           deriving(Show,Eq,Ord,Generic,NFData)

instance Semigroup TsRow where 
  CashFlow d1 a1 <> (CashFlow d2 a2) = CashFlow (max d1 d2) (a1 + a2)
  BondFlow d1 b1 p1 i1 <> (BondFlow d2 b2 p2 i2) = BondFlow (max d1 d2) (b1 + b2) (p1 + p2) (i1 + i2)
  MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 pn1 st1 <> MortgageFlow d2 b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 pn2 st2
    = MortgageFlow (max d1 d2) (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1 + los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2])))  (liftA2 (+) mbn1 mbn2)   (liftA2 (+) pn1 pn2)  (sumStats st1 st2)
  MortgageDelinqFlow d1 b1 p1 i1 prep1 delinq1 def1 rec1 los1 rat1 mbn1 pn1 st1 <> MortgageDelinqFlow d2 b2 p2 i2 prep2 delinq2 def2 rec2 los2 rat2 mbn2 pn2 st2
    = MortgageDelinqFlow (max d1 d2) (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (delinq1 + delinq2) (def1 + def2) (rec1 + rec2) (los1 + los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) (liftA2 (+) mbn1 mbn2) (liftA2 (+) pn1 pn2) (sumStats st1 st2)
  LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 st1 <> LoanFlow d2 b2 p2 i2 prep2 def2 rec2 los2 rat2 st2
    = LoanFlow (max d1 d2) (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1 + los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) (sumStats st1 st2)
  LeaseFlow d1 b1 r1 def1 <> LeaseFlow d2 b2 r2 def2
    = LeaseFlow (max d1 d2) (b1 + b2) (r1 + r2) (def1 + def2)
  FixedFlow d1 b1 ndep1 dep1 c1 a1 <> FixedFlow d2 b2 ndep2 dep2 c2 a2 
    = FixedFlow (max d1 d2) (b1 + b2) (ndep1 + ndep2) (dep1 + dep2) (c1 + c2) (a1 + a2)
  ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 st1 <> ReceivableFlow d2 b2 af2 p2 fp2 def2 rec2 los2 st2
    = ReceivableFlow (max d1 d2) (b1 + b2) (af1 + af2) (p1 + p2) (fp1 + fp2) (def1 + def2) (rec1 + rec2) (los1 + los2) (sumStats st1 st2)
  a <> b = error $ "TsRow Semigroup not supported "++show a++" "++show b


instance TimeSeries TsRow where 
    getDate (CashFlow x _) = x
    getDate (BondFlow x  _ _ _) = x
    getDate (MortgageFlow x _ _ _ _ _ _ _ _ _ _ _) = x
    getDate (MortgageDelinqFlow x _ _ _ _ _ _ _ _ _ _ _ _) = x
    getDate (LoanFlow x _ _ _ _ _ _ _ _ _) = x
    getDate (LeaseFlow x _ _ _) = x
    getDate (FixedFlow x _ _ _ _ _ ) = x
    getDate (ReceivableFlow x _ _ _ _ _ _ _ _) = x


scaleTsRow :: Rational -> TsRow -> TsRow
scaleTsRow r (CashFlow d a) = CashFlow d (fromRational r * a)
scaleTsRow r (BondFlow d b p i) = BondFlow d (fromRational r * b) (fromRational r * p) (fromRational r * i)
scaleTsRow r (MortgageFlow d b p i prep def rec los rat mbn pp st) 
  = MortgageFlow d 
     (fromRational r * b) 
     (fromRational r * p) 
     (fromRational r * i) 
     (fromRational r * prep) 
     (fromRational r * def) 
     (fromRational r * rec) 
     (fromRational r * los) 
     rat 
     mbn 
     pp 
     (splitStats r <$> st)
scaleTsRow r (MortgageDelinqFlow d b p i prep delinq def rec los rat mbn pp st) 
  = MortgageDelinqFlow d 
      (fromRational r * b)
      (fromRational r * p)
      (fromRational r * i)
      (fromRational r * prep)
      (fromRational r * delinq)
      (fromRational r * def) 
      (fromRational r * rec) 
      (fromRational r * los) 
      rat 
      mbn 
      pp
      (splitStats r <$> st)
scaleTsRow r (LoanFlow d b p i prep def rec los rat st) 
  = LoanFlow d (fromRational r * b) (fromRational r * p) (fromRational r * i) (fromRational r * prep) (fromRational r * def) (fromRational r * rec) (fromRational r * los) rat ((splitStats r) <$> st)
scaleTsRow r (LeaseFlow d b rental def) = LeaseFlow d (fromRational r * b) (fromRational r * rental) (fromRational r * def)
scaleTsRow r (FixedFlow d b ndep dep c a) = FixedFlow d (fromRational r * b) (fromRational r * ndep) (fromRational r * dep) (fromRational r * c) (fromRational r * a)
scaleTsRow r (ReceivableFlow d b af p fp def rec los st) = ReceivableFlow d (fromRational r * b) (fromRational r * af) (fromRational r * p) (fromRational r * fp) (fromRational r * def) (fromRational r * rec) (fromRational r * los) ((splitStats r) <$> st)


type BeginBalance = Balance
type AccuredInterest = Maybe Balance
type BeginDate = Date
type BeginStatus = (BeginBalance, BeginDate, AccuredInterest)

data CashFlowFrame = CashFlowFrame BeginStatus [TsRow]
                   | MultiCashFlowFrame (Map.Map String [CashFlowFrame])
                   deriving (Eq,Generic,Ord)

cfBeginStatus :: Lens' CashFlowFrame BeginStatus
cfBeginStatus = lens getter setter
  where 
    getter (CashFlowFrame st _) = st
    setter (CashFlowFrame _ tsRows) st = CashFlowFrame st tsRows


instance Show CashFlowFrame where
  show (CashFlowFrame st []) = "Empty CashflowFrame"++ show st
  -- show (CashFlowFrame st txns) = concat $ L.intersperse "\n" [ show txn | txn <- txns ]
  show (CashFlowFrame st txns) = 
    let 
        ds = [ show d | d <- getDates txns]
        rowHeader = [TT.Header h | h <- ds ]
        getCs (CashFlow {}) = ["Amount"]
        getCs (BondFlow {}) = ["Balance", "Principal", "Interest"]
        getCs (MortgageFlow {}) = ["Balance", "Principal", "Interest", "Prepayment", "Default", "Recovery", "Loss", "IRate", "BorrowerNum", "PrepaymentPenalty", "CumulativeStat"]
        getCs (MortgageDelinqFlow {}) = [ "Balance", "Principal", "Interest", "Prepayment", "Delinquent", "Default", "Recovery", "Loss", "IRate", "BorrowerNum", "PrepaymentPenalty", "CumulativeStat"]
        getCs (LoanFlow {}) = ["Balance", "Principal", "Interest", "Prepayment", "Default", "Recovery", "Loss", "IRate", "CumulativeStat"]
        getCs (LeaseFlow {}) = [ "Balance", "Rental", "Default"]
        getCs (FixedFlow {}) = [ "Balance", "NewDepreciation", "Depreciation", "Balance", "Amount"]
        getCs (ReceivableFlow {}) = [ "Balance", "AccuredFee", "Principal", "FeePaid", "Default", "Recovery", "Loss", "CumulativeStat"]
        colHeader = [TT.Header c | c <- getCs (head txns) ]
        getRs (CashFlow d a) = [show a]
        getRs (BondFlow d b p i) = [ show b, show p, show i]
        getRs (MortgageFlow d b p i prep def rec los rat mbn pp st) = [ show b, show p, show i, show prep, show def, show rec, show los, show rat, show mbn, show pp, show st]
        getRs (MortgageDelinqFlow d b p i prep delinq def rec los rat mbn pp st) = [ show b, show p, show i, show prep, show delinq, show def, show rec, show los, show rat, show mbn, show pp, show st]
        getRs (LoanFlow d b p i prep def rec los rat st) = [ show b, show p, show i, show prep, show def, show rec, show los, show rat, show st]
        getRs (LeaseFlow d b r def) = [ show b, show r, show def]
        getRs (FixedFlow d b ndep dep c a) = [ show b, show ndep, show dep, show c, show a]
        getRs (ReceivableFlow d b af p fp def rec los st) = [ show b, show af, show p, show fp, show def, show rec, show los, show st]
        values = [ getRs txn  | txn <- txns ]
        tbl = TT.Table (TT.Group TT.SingleLine rowHeader) (TT.Group TT.SingleLine colHeader) values
    in 
        show st <> "\n" <> A.render id id id tbl

instance NFData CashFlowFrame where 
  rnf (CashFlowFrame st txns) = rnf st `seq` rnf txns
  rnf (MultiCashFlowFrame m) = rnf m

sizeCashFlowFrame :: CashFlowFrame -> Int
sizeCashFlowFrame (CashFlowFrame _ ts) = length ts

emptyCashFlowFrame :: CashFlowFrame -> Bool 
emptyCashFlowFrame (CashFlowFrame _ []) = True
emptyCashFlowFrame (CashFlowFrame _ _) = False

getDatesCashFlowFrame :: CashFlowFrame -> [Date]
getDatesCashFlowFrame (CashFlowFrame _ ts) = getDates ts

getAllDatesCashFlowFrame :: CashFlowFrame -> [Date]
getAllDatesCashFlowFrame (CashFlowFrame (_,d,_) ts) = d : getDates ts

getBegBalCashFlowFrame :: CashFlowFrame -> Balance
getBegBalCashFlowFrame (CashFlowFrame _ []) = 0
getBegBalCashFlowFrame (CashFlowFrame _ (cf:cfs)) = mflowBegBalance cf

cfAt :: CashFlowFrame -> Int -> Maybe TsRow
cfAt (CashFlowFrame _ trs) idx 
  | (idx < 0) || (idx >= length trs) = Nothing
  | otherwise = Just (trs!!idx)

cfInsertHead :: TsRow -> CashFlowFrame -> CashFlowFrame
cfInsertHead tr (CashFlowFrame st trs) = CashFlowFrame st $ tr:trs


splitCashFlowFrameByDate :: CashFlowFrame -> Date -> SplitType  -> (CashFlowFrame,CashFlowFrame)
splitCashFlowFrameByDate (CashFlowFrame status txns) d st
  = let 
      (ls,rs) = splitByDate txns d st
      newStatus = case rs of 
                    [] -> (0, d, Nothing)
                    (r:_) -> (buildBegBal rs, d, Nothing)
    in 
      (CashFlowFrame status ls,CashFlowFrame newStatus rs)


splitPoolCashflowByDate :: PoolCashflow -> Date -> SplitType -> (PoolCashflow,PoolCashflow)
splitPoolCashflowByDate (poolCF, mAssetCfs) d st
  = let 
      (lPoolCF,rPoolCF) = splitCashFlowFrameByDate poolCF d st
      mAssetSplited = (\xs -> [ splitCashFlowFrameByDate x d st | x <- xs ]) <$> mAssetCfs
      assetCfs = (\xs -> [ (lCf, rCf) | (lCf,rCf) <- xs ]) <$> mAssetSplited 
      lAssetCfs = (fst <$>) <$> assetCfs
      rAssetCfs = (snd <$>) <$> assetCfs
    in 
      ((lPoolCF, lAssetCfs) , (rPoolCF, rAssetCfs))


getTxnLatestAsOf :: CashFlowFrame -> Date -> Maybe TsRow
getTxnLatestAsOf (CashFlowFrame _ txn) d = L.find (\x -> getDate x <= d) $ reverse txn

addTs :: TsRow -> TsRow -> TsRow     
-- ^ left cashflow is ealier ,right one is later,combine both and yield cashflow with earlier date
addTs (CashFlow d1 a1 ) (CashFlow _ a2 ) = CashFlow d1 (a1 + a2)
addTs (BondFlow d1 b1 p1 i1 ) tr@(BondFlow _ b2 p2 i2 ) = BondFlow d1 (b1 - mflowAmortAmount tr) (p1 + p2) (i1 + i2)
addTs (MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 pn1 st1) tr@(MortgageFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = (+) <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      st = sumStats st1 st2
    in 
      MortgageFlow d1 (b1 - mflowAmortAmount tr) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st
addTs (MortgageDelinqFlow d1 b1 p1 i1 prep1 delinq1 def1 rec1 los1 rat1 mbn1 pn1 st1) tr@(MortgageDelinqFlow _ b2 p2 i2 prep2 delinq2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = (+) <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      delinq = (+) delinq1 delinq2
      st = sumStats st1 st2
    in 
      MortgageDelinqFlow d1 (b1 - mflowAmortAmount tr) (p1 + p2) (i1 + i2) (prep1 + prep2) delinq (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st

addTs (LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 st1) tr@(LoanFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 st2)
  = LoanFlow d1 (b1 - mflowAmortAmount tr) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) (sumStats st1 st2)

addTs (LeaseFlow d1 b1 r1 def1) tr@(LeaseFlow d2 b2 r2 def2) 
  = LeaseFlow d1 (b1 - mflowAmortAmount tr) (r1 + r2) (def1 + def2)

addTs (ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 st1) tr@(ReceivableFlow _ b2 af2 p2 fp2 def2 rec2 los2 st2)
  = ReceivableFlow d1 (b1 - mflowAmortAmount tr) (af1 + af2) (p1 + p2) (fp1 + fp2) (def1 + def2) (rec1 + rec2) (los1 + los2) (sumStats st1 st2)

combineTs :: TsRow -> TsRow -> TsRow     

-- ^ combine two cashflow records from two entities, return cashflow with earlier date
combineTs (CashFlow d1 a1 ) (CashFlow _ a2 ) = CashFlow d1 (a1 + a2)

combineTs (BondFlow d1 b1 p1 i1 ) tr@(BondFlow _ b2 p2 i2 ) = BondFlow d1 (b1 + b2) (p1 + p2) (i1 + i2)

combineTs (MortgageDelinqFlow d1 b1 p1 i1 prep1 delinq1 def1 rec1 los1 rat1 mbn1 pn1 st1) tr@(MortgageDelinqFlow _ b2 p2 i2 prep2 delinq2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = (+) <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      delinq = (+) delinq1 delinq2
      st = sumStats st1 st2
    in 
      MortgageDelinqFlow d1 (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) delinq (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st

combineTs (MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 pn1 st1) tr@(MortgageFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = (+) <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      st = sumStats st1 st2
    in 
      MortgageFlow d1 (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st

combineTs (LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 st1) tr@(LoanFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 st2)
  = LoanFlow d1 (b1 + b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) (sumStats st1 st2)

combineTs (LeaseFlow d1 b1 r1 def1) tr@(LeaseFlow d2 b2 r2 def2) 
  = LeaseFlow d1 (b1 + b2) (r1 + r2) (def1 + def2)

combineTs (FixedFlow d1 b1 de1 cde1 p1 c1 ) (FixedFlow d2 b2 de2 cde2 p2 c2)
  = FixedFlow d1 (b1+b2) (de1+de2) (cde1+cde2) (p1+p2) (c1+c2)

combineTs (ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 st1) tr@(ReceivableFlow _ b2 af2 p2 fp2 def2 rec2 los2 st2)
  = ReceivableFlow d1 (b1 + b2) (af1 + af2) (p1 + p2) (fp1 + fp2) (def1 + def2) (rec1 + rec2) (los1 + los2) (sumStats st1 st2)

-- ^ combine two cashflows from two entities,(auto patch a beg balance)
-- ^ left cashflow is ealier ,right one is later,combine both and yield cashflow with earlier date
combineTss :: [TsRow] -> [TsRow] -> [TsRow] -> [TsRow]
combineTss [] [] r = r
combineTss [] r [] = r
combineTss [] (r1:r1s) (r2:r2s)
  | getDate r1 > getDate r2 = combineTss [] (r2:r2s) (r1:r1s)
  | getDate r1 == getDate r2 = combineTss [combineTs r1 r2] r1s r2s -- `debug` ("combineTss after same"++show r1s++" "++show r2s)
  | otherwise = combineTss [set tsRowBalance (mflowBegBalance r2+(view tsRowBalance r1)) r1]
                           r1s
                           (r2:r2s)
                           
combineTss consols [] [] = reverse consols
combineTss (consol:consols) (r:rs) [] = combineTss (appendTs consol r:consol:consols) rs []
combineTss (consol:consols) [] (tr:trs) = combineTss (appendTs consol tr:consol:consols) [] trs
combineTss (consol:consols) (r:rs) (tr:trs)
  | getDate r == getDate tr = combineTss (appendTs consol (combineTs r tr):consol:consols) rs trs
  | getDate r < getDate tr = combineTss (appendTs consol r:consol:consols) rs (tr:trs)
  | getDate r > getDate tr = combineTss (appendTs consol tr:consol:consols) (r:rs) trs 
combineTss a b c = error $ "combineTss not supported "++show a++" "++show b++" "++show c



appendTs :: TsRow -> TsRow -> TsRow 
-- ^ combine two cashflow records from two entities ,(early row on left, later row on right)
appendTs bn1@(BondFlow d1 b1 _ _ ) bn2@(BondFlow d2 b2 p2 i2 ) 
  = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 -- `debug` ("b1 >> "++show b1++">>"++show (mflowAmortAmount bn2))
appendTs (MortgageDelinqFlow d1 b1 p1 i1 prep1 _ def1 rec1 los1 rat1 mbn1 _ mstat1) bn2@(MortgageDelinqFlow _ b2 p2 i2 prep2 _ def2 rec2 los2 rat2 mbn2 _ mstat2)
  = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2
appendTs bn1@(MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 _ mstat1) bn2@(MortgageFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 _ mstat2)
  =  set tsRowBalance (b1 - mflowAmortAmount bn2) bn2 -- `debug` ("Summing stats"++ show bn1 ++ show mstat1++">>"++ show bn2 ++ show mstat2)
appendTs (LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mstat1) bn2@(LoanFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 mstat2)
  =  set tsRowBalance (b1 - mflowAmortAmount bn2) bn2
appendTs (LeaseFlow d1 b1 r1 def1) bn2@(LeaseFlow d2 b2 r2 def2) 
  = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2
appendTs (FixedFlow d1 b1 de1 cde1 p1 c1 ) bn2@(FixedFlow d2 b2 de2 cde2 p2 c2)
  = set tsRowBalance (b1 - mflowAmortAmount bn2) bn2
appendTs (ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 mstat1) bn2@(ReceivableFlow _ b2 af2 p2 fp2 def2 rec2 los2 mstat2)
  =  set tsRowBalance (b1 - mflowAmortAmount bn2) bn2
appendTs _1 _2 = error $ "appendTs failed with "++ show _1 ++ ">>" ++ show _2

-- ^ add up TsRow from same entity
addTsCF :: TsRow -> TsRow -> TsRow
addTsCF (CashFlow d1 a1 ) (CashFlow _ a2 ) = CashFlow d1 (a1 + a2)
addTsCF (BondFlow d1 b1 p1 i1 ) (BondFlow _ b2 p2 i2 ) = BondFlow d1 (min b1 b2) (p1 + p2) (i1 + i2)
addTsCF m1@(MortgageFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 mbn1 pn1 st1) m2@(MortgageFlow d2 b2 p2 i2 prep2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = min <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      st = maxStats st1 st2
    in 
      MortgageFlow d1 (min b1 b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st 
addTsCF (MortgageDelinqFlow d1 b1 p1 i1 prep1 delinq1 def1 rec1 los1 rat1 mbn1 pn1 st1) (MortgageDelinqFlow d2 b2 p2 i2 prep2 delinq2 def2 rec2 los2 rat2 mbn2 pn2 st2)
  = let 
      bn = min <$> mbn1 <*> mbn2
      p =  (+) <$> pn1 <*> pn2
      delinq = (+) delinq1 delinq2
      st = maxStats st1 st2
    in 
      MortgageDelinqFlow d1 (min b1 b2) (p1 + p2) (i1 + i2) (prep1 + prep2) delinq (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) bn p st
addTsCF (LoanFlow d1 b1 p1 i1 prep1 def1 rec1 los1 rat1 st1) (LoanFlow _ b2 p2 i2 prep2 def2 rec2 los2 rat2 st2)
  = LoanFlow d1 (min b1 b2) (p1 + p2) (i1 + i2) (prep1 + prep2) (def1 + def2) (rec1 + rec2) (los1+los2) (fromRational (weightedBy (toRational <$> [b1,b2]) (toRational <$> [rat1,rat2]))) (maxStats st1 st2)
addTsCF (LeaseFlow d1 b1 r1 def1) (LeaseFlow d2 b2 r2 def2) = LeaseFlow d1 (min b1 b2) (r1 + r2) (def1 + def2)
addTsCF (FixedFlow d1 b1 dep1 cd1 u1 c1) (FixedFlow d2 b2 dep2 cd2 u2 c2) 
  = FixedFlow d1 (min b1 b2) (dep1 + dep2) (cd1 + cd2) u2 (c1 + c2)
addTsCF (ReceivableFlow d1 b1 af1 p1 fp1 def1 rec1 los1 st1) (ReceivableFlow d2 b2 af2 p2 fp2 def2 rec2 los2 st2)
  = ReceivableFlow d1 (min b1 b2) (af1 + af2) (p1 + p2) (fp1 + fp2) (def1 + def2) (rec1 + rec2) (los1 + los2) (maxStats st1 st2)


buildBegBal :: [TsRow] -> Balance
buildBegBal [] = 0
buildBegBal (x:xs) = mflowBegBalance x


sumTs :: [TsRow] -> Date -> TsRow
sumTs trs d = set tsDate d (foldr1 addTs trs)

-- ^ group cashflow from same entity by a single date
sumTsCF :: [TsRow] -> Date -> TsRow
-- sumTsCF [] = tsSetDate (foldl1 addTsCF trs) -- `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d))
sumTsCF [] _ = error "sumTsCF failed with empty list"
sumTsCF trs d = set tsDate d (foldl1 addTsCF trs) --  `debug` ("Summing"++show trs++">>"++ show (tsSetDate (foldr1 addTsCF trs) d))

tsTotalCash :: TsRow -> Balance
tsTotalCash (CashFlow _ x) = x
tsTotalCash (BondFlow _ _ a b) = a + b
tsTotalCash (MortgageDelinqFlow x _ a b c _ _ e _ _ _ mPn _ ) = a + b + c + e + fromMaybe 0 mPn
tsTotalCash (MortgageFlow x _ a b c _ e _ _ _ mPn _) = a + b + c + e + fromMaybe 0 mPn
tsTotalCash (LoanFlow _ _ a b c _ e _ _ _) =  a + b + c + e
tsTotalCash (LeaseFlow _ _ a _) =  a
tsTotalCash (FixedFlow _ _ _ _ _ x) = x
tsTotalCash (ReceivableFlow _ _ _ a b _ c _ _ ) = a + b + c

tsDefaultBal :: TsRow -> Either String Balance
tsDefaultBal CashFlow {} = Left "no default amount for bond flow"
tsDefaultBal BondFlow {} = Left "no default amount for bond flow"
tsDefaultBal (MortgageDelinqFlow _ _ _ _ _ _ x _ _ _ _ _ _) = Right x
tsDefaultBal (MortgageFlow _ _ _ _ _ x _ _ _ _ _ _) = Right x
tsDefaultBal (LoanFlow _ _ _ _ _ x _ _ _ _) = Right x
tsDefaultBal (LeaseFlow _ _ _ x) = Right x
tsDefaultBal (FixedFlow _ _ x _ _ _) =  Right x
tsDefaultBal (ReceivableFlow _ _ _ _ _ x _ _ _ ) = Right x

tsCumulative :: Lens' TsRow (Maybe CumulativeStat)
tsCumulative = lens getter setter
  where
    getter (MortgageDelinqFlow  _ _ _ _ _ _ _ _ _ _ _ _ mStat) = mStat
    getter (MortgageFlow  _ _ _ _ _ _ _ _ _ _ _ mStat) = mStat
    getter (LoanFlow  _ _ _ _ _ _ _ _ _ mStat) = mStat
    getter (ReceivableFlow _ _ _ _ _ _ _ _ mStat) = mStat
    getter _ = Nothing

    setter (MortgageDelinqFlow  a b c d e f g h i j k l _) mStat = MortgageDelinqFlow a b c d e f g h i j k l mStat
    setter (MortgageFlow  a b c d e f g h i j k _) mStat = MortgageFlow a b c d e f g h i j k mStat
    setter (LoanFlow  a b c d e f g h i _) mStat = LoanFlow a b c d e f g h i mStat
    setter (ReceivableFlow a b c d e f g h _) mStat = ReceivableFlow a b c d e f g h mStat
    setter x _ = x

tsCumDefaultBal :: TsRow -> Maybe Balance
tsCumDefaultBal tr = preview (tsCumulative . _Just . _4) tr

tsCumDelinqBal :: TsRow -> Maybe Balance
tsCumDelinqBal tr = preview (tsCumulative . _Just . _3) tr

tsCumLossBal :: TsRow -> Maybe Balance
tsCumLossBal tr = preview (tsCumulative . _Just . _6) tr

tsCumRecoveriesBal :: TsRow -> Maybe Balance
tsCumRecoveriesBal tr = preview (tsCumulative . _Just . _5) tr

tsDate :: Lens' TsRow Date 
tsDate = lens getter setter 
  where 
    getter (CashFlow x _) = x
    getter (BondFlow x _ _ _) = x
    getter (MortgageDelinqFlow x _ _ _ _ _ _ _ _ _ _ _ _) = x 
    getter (MortgageFlow x _ _ _ _ _ _ _ _ _ _ _) = x
    getter (LoanFlow x _ _ _ _ _ _ _ _ _) = x
    getter (LeaseFlow x _ _ _ ) = x
    getter (FixedFlow x _ _ _ _ _) = x
    getter (ReceivableFlow x _ _ _ _ _ _ _ _) = x
    setter (CashFlow _ a) x = CashFlow x a
    setter (BondFlow _ a b c) x = BondFlow x a b c
    setter (MortgageDelinqFlow _ a b c d e f g h i j k l) x = MortgageDelinqFlow x a b c d e f g h i j k l
    setter (MortgageFlow _ a b c d e f g h i j k) x = MortgageFlow x a b c d e f g h i j k
    setter (LoanFlow _ a b c d e f g h i) x = LoanFlow x a b c d e f g h i
    setter (LeaseFlow _ a b c) x = LeaseFlow x a b c
    setter (FixedFlow _ a b c d e) x = FixedFlow x a b c d e
    setter (ReceivableFlow _ a b c d e f g h) x = ReceivableFlow x a b c d e f g h

tsSetLoss :: Balance -> TsRow -> TsRow
tsSetLoss x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow _d a b c d e f g x i j k l
tsSetLoss x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d a b c d e f x h i j k 
tsSetLoss x (LoanFlow _d a b c d e f g h i) = LoanFlow _d a b c d e f x h i
tsSetLoss x (ReceivableFlow _d a b c d e f g h) = ReceivableFlow _d a b c d e f x h
tsSetLoss x _ = error $ "Failed to set Loss for "++show x

tsSetRecovery :: Balance -> TsRow -> TsRow
tsSetRecovery x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow _d a b c d e f x h i j k l
tsSetRecovery x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d a b c d e x g h i j k 
tsSetRecovery x (LoanFlow _d a b c d e f g h i) = LoanFlow _d a b c d e x g h i
tsSetRecovery x (ReceivableFlow _d a b c d e f g h) = ReceivableFlow _d a b c d e x g h
tsSetRecovery x _ = error $ "Failed to set Recovery for "++show x

tsOffsetDate :: Integer -> TsRow -> TsRow
tsOffsetDate x (CashFlow _d a) = CashFlow (T.addDays x _d) a
tsOffsetDate x (BondFlow _d a b c) = BondFlow (T.addDays x _d) a b c
tsOffsetDate x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow (T.addDays x _d) a b c d e f g h i j k l
tsOffsetDate x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow (T.addDays x _d) a b c d e f g h i j k
tsOffsetDate x (LoanFlow _d a b c d e f g h i) = LoanFlow (T.addDays x _d) a b c d e f g h i
tsOffsetDate x (LeaseFlow _d a b c) = LeaseFlow (T.addDays x _d) a b c
tsOffsetDate x (ReceivableFlow _d a b c d e f g h) = ReceivableFlow (T.addDays x _d) a b c d e f g h

tsReduceInt :: Balance -> TsRow -> TsRow
tsReduceInt x (BondFlow _d a b c) = BondFlow _d a b (c-x)
tsReduceInt x (MortgageDelinqFlow _d a b c d e f g h i j k l) = MortgageDelinqFlow _d a b (c-x) d e f g h i j k l
tsReduceInt x (MortgageFlow _d a b c d e f g h i j k) = MortgageFlow _d a b (c-x) d e f g h i j k 
tsReduceInt x (LoanFlow _d a b c d e f g h i) = LoanFlow _d a b (c-x) d e f g h i
tsReduceInt _ x = error $ "Failed to reduce interest on asset "++ show x

-- ^ claw back interest from cashflow records
clawbackInt :: Balance -> [TsRow] -> [TsRow]
clawbackInt bal txns
  = let
      intFlow = mflowInterest <$> txns
      intDowns = paySeqLiabilitiesAmt bal intFlow
    in 
      [ tsReduceInt intDown txn | (txn,intDown) <- zip txns intDowns]

aggregateTsByDate :: [TsRow] -> [TsRow] -> [TsRow]
aggregateTsByDate rs [] = reverse rs
aggregateTsByDate [] (tr:trs) = aggregateTsByDate [tr] trs
aggregateTsByDate (r:rs) (tr:trs) 
  | sameDate r tr = aggregateTsByDate (combineTs r tr:rs) trs
  | otherwise = aggregateTsByDate (tr:r:rs) trs


firstDate :: CashFlowFrame -> Date 
firstDate (CashFlowFrame _ []) = error "empty cashflow frame to get first date"
firstDate (CashFlowFrame _ [r]) = getDate r
firstDate (CashFlowFrame _ (r:rs)) = getDate r


-- ! combine two cashflow frames from two entities
-- ! cashflow earlier on the left ,later cashflow on the right
combine :: CashFlowFrame -> CashFlowFrame -> CashFlowFrame 
combine (CashFlowFrame st1 []) (CashFlowFrame st2 []) = CashFlowFrame st1 []
combine (CashFlowFrame _ []) cf2 = cf2
combine cf1 (CashFlowFrame _ []) = cf1
combine cf1@(CashFlowFrame st1@(begBal1,begDate1,acc1) txn1) cf2@(CashFlowFrame st2@(begBal2,begDate2,acc2) txn2) 
  | begDate1 > begDate2 = combine cf2 cf1
  | otherwise =
    let 
      txns = combineTss [] txn1 txn2
    in 
      CashFlowFrame (begBal1,begDate1,acc1) txns 

buildCollectedCF :: [[TsRow]] -> [Date] -> [TsRow] -> [[TsRow]]
buildCollectedCF [] [] [] = []
buildCollectedCF trs [] _trs = trs
buildCollectedCF trs ds [] = trs ++ [ [viewTsRow _d ((last . last) trs)] | _d <- ds ]
buildCollectedCF trs (d:ds) _trs =
  case newFlow of
    [] -> case Util.lastOf trs (not . null) of
            Nothing -> buildCollectedCF (trs++[[]]) ds _trs  -- `debug` ("empty trs"++ show d)
            Just lastTr ->  buildCollectedCF (trs++[[viewTsRow d (last lastTr)]]) ds _trs -- `debug` ("non empty last tr "++ show lastTr ++ "for date"++ show d++"insert with "++show (viewTsRow d (last lastTr)))
    newFlow -> buildCollectedCF (trs++[newFlow]) ds remains
  where 
    (newFlow, remains) = splitBy d Inc _trs

buildCollectedCF a b c = error $ "buildCollectedCF failed"++ show a++">>"++ show b++">>"++ show c


aggTsByDates :: [TsRow] -> [Date] -> [TsRow]
aggTsByDates [] ds = []
aggTsByDates trs ds = uncurry sumTsCF <$> filter (\(cfs,_d) -> (not . null) cfs) (zip (buildCollectedCF [] ds trs) ds) -- `debug` (">>> to sumTsCF "++ show (zip (buildCollectedCF [] ds trs) ds ))

mflowPrincipal :: TsRow -> Balance
mflowPrincipal (BondFlow _ _ p _) = p
mflowPrincipal (MortgageFlow _ _ x _ _ _ _ _ _ _ _ _) = x
mflowPrincipal (MortgageDelinqFlow _ _ x _ _ _ _ _ _ _ _ _ _) = x
mflowPrincipal (LoanFlow _ _ x _ _ _ _ _ _ _) = x
mflowPrincipal (ReceivableFlow _ _ _ x _ _ _ _ _) = x
mflowPrincipal _  = error "not supported"

mflowInterest :: TsRow -> Balance
mflowInterest (BondFlow _ _ _ i) = i
mflowInterest (MortgageDelinqFlow _ _ _ x _ _ _ _ _ _ _ _ _) = x
mflowInterest (MortgageFlow _ _ _ x _ _ _ _ _ _ _ _) = x
mflowInterest (LoanFlow _ _ _ x _ _ _ _ _ _) = x
mflowInterest x  = error $ "not supported: getting interest from row" ++ show x

mflowPrepayment :: TsRow -> Balance
mflowPrepayment (MortgageFlow _ _ _ _ x _ _ _ _ _ _ _) = x
mflowPrepayment (MortgageDelinqFlow _ _ _ _ x _ _ _ _ _ _ _ _) = x
mflowPrepayment (LoanFlow _ _ _ _ x _ _ _ _ _) = x
mflowPrepayment _  = error "not supported"

mflowDefault :: TsRow -> Balance
mflowDefault (MortgageFlow _ _ _ _ _ x _ _ _ _ _ _) = x
mflowDefault (MortgageDelinqFlow _ _ _ _ _ _ x _ _ _ _ _ _) = x
mflowDefault (LoanFlow _ _ _ _ _ x _ _ _ _) = x
mflowDefault (FixedFlow _ _ x _ _ _) = x
mflowDefault (ReceivableFlow _ _ _ _ _ x _ _ _ ) = x
mflowDefault _  = 0

mflowRecovery :: TsRow -> Balance
mflowRecovery (MortgageFlow _ _ _ _ _ _ x _ _ _ _ _) = x
mflowRecovery (MortgageDelinqFlow _ _ _ _ _ _ _ x _ _ _ _ _) = x
mflowRecovery (LoanFlow _ _ _ _ _ _ x _ _ _) = x
mflowRecovery FixedFlow {} = 0
mflowRecovery (ReceivableFlow _ _ _ _ _ _ x _ _ ) = x
mflowRecovery (LeaseFlow _ _ _ _) = 0
mflowRecovery _  = error "not supported"

tsRowBalance :: Lens' TsRow Balance
tsRowBalance = lens getter setter 
  where 
    getter (BondFlow _ x _ _) = x
    getter (MortgageFlow _ x _ _ _ _ _ _ _ _ _ _) = x
    getter (MortgageDelinqFlow _ x _ _ _ _ _ _ _ _ _ _ _) = x
    getter (LoanFlow _ x _ _ _ _ _ _ _ _) = x
    getter (LeaseFlow _ x _ _) = x
    getter (FixedFlow _ x _ _ _ _) = x
    getter (ReceivableFlow _ x _ _ _ _ _ _ _ ) = x

    setter (BondFlow a _ p i) x = BondFlow a x p i
    setter (MortgageFlow a _ p i prep def rec los rat mbn pn st) x = MortgageFlow a x p i prep def rec los rat mbn pn st
    setter (MortgageDelinqFlow a _ p i prep delinq def rec los rat mbn pn st) x = MortgageDelinqFlow a x p i prep delinq def rec los rat mbn pn st
    setter (LoanFlow a _ p i prep def rec los rat st) x = LoanFlow a x p i prep def rec los rat st
    setter (LeaseFlow a _ r def) x = LeaseFlow a x r def
    setter (FixedFlow a _ b c d e) x = FixedFlow a x b c d e
    setter (ReceivableFlow a _ b c d e f g h) x = ReceivableFlow a x b c d e f g h


mflowBegBalance :: TsRow -> Balance
mflowBegBalance (BondFlow _ x p _) = x + p
mflowBegBalance (MortgageDelinqFlow _ x p _ ppy delinq def _ _ _ _ _ _) = x + p + ppy + delinq
mflowBegBalance (MortgageFlow _ x p _ ppy def _ _ _ _ _ _) = x + p + ppy + def
mflowBegBalance (LoanFlow _ x p _ ppy def _ _ _ _) = x + p + ppy + def
mflowBegBalance (LeaseFlow _ b r def ) = b + r + def 
mflowBegBalance (FixedFlow a b c d e f ) = b + c
mflowBegBalance (ReceivableFlow _ x _ b f def _ _ _) = x + b + def + f

mflowLoss :: TsRow -> Balance
mflowLoss (MortgageFlow _ _ _ _ _ _ _ x _ _ _ _) = x
mflowLoss (MortgageDelinqFlow _ _ _ _ _ _ _ _ x _ _ _ _) = x
mflowLoss (LoanFlow _ _ _ _ _ _ _ x _ _) = x
mflowLoss (ReceivableFlow _ _ _ _ _ _ _ x _ ) = x
mflowLoss _ = 0

mflowDelinq :: TsRow -> Balance
mflowDelinq (MortgageDelinqFlow _ _ _ _ _ x _ _ _ _ _ _ _) = x
mflowDelinq _ = 0

mflowRate :: TsRow -> IRate
-- ^ get rate(weigthed avg rate) for a cashflow record
mflowRate (MortgageFlow _ _ _ _ _ _ _ _ x _ _ _) = x
mflowRate (MortgageDelinqFlow _ _ _ _ _ _ _ _ _ x _ _ _) = x
mflowRate (LoanFlow _ _ _ _ _ _ _ _ x _) = x
mflowRate (BondFlow _ _ _ _) = 0
mflowRate _ = 0

mflowRental :: TsRow -> Amount
mflowRental (LeaseFlow _ _ x _) = x
mflowRental x = error ("not support get rental from row"++show x)

mflowFeePaid :: TsRow -> Amount
mflowFeePaid (ReceivableFlow _ _ _ _ x _ _ _ _ ) = x
mflowFeePaid _ = 0

mflowAmortAmount :: TsRow -> Balance
-- ^ calculate amortized amount for cashflow (for defaults only)
mflowAmortAmount (MortgageFlow _ _ p _ ppy def _ _ _ _ _ _) = p + ppy + def
mflowAmortAmount (MortgageDelinqFlow _ _ p _ ppy delinq _ _ _ _ _ _ _) = p + ppy + delinq
mflowAmortAmount (LoanFlow _ _ x _ y z _ _ _ _) = x + y + z
mflowAmortAmount (LeaseFlow _ _ x def) = x + def
mflowAmortAmount (FixedFlow _ _ x _ _ _) = x
mflowAmortAmount (BondFlow _ _ p i) = p
mflowAmortAmount (ReceivableFlow _ _ _ x f def _ _ _ ) = x + def + f

mflowBorrowerNum :: TsRow -> Maybe BorrowerNum
-- ^ get borrower numfer for Mortgage Flow
mflowBorrowerNum (MortgageFlow _ _ _ _ _ _ _ _ _ x _ _) = x
mflowBorrowerNum (MortgageDelinqFlow _ _ _ _ _ _ _ _ _ _ x _ _) = x
mflowBorrowerNum _ = undefined

mflowPrepaymentPenalty :: TsRow -> Balance
-- ^ get prepayment penalty for a cashflow record
mflowPrepaymentPenalty (MortgageFlow _ _ _ _ _ _ _ _ _ _ (Just x) _) = x
mflowPrepaymentPenalty (MortgageFlow _ _ _ _ _ _ _ _ _ _ Nothing _) = 0
mflowPrepaymentPenalty (MortgageDelinqFlow _ _ _ _ _ _ _ _ _ _ _ (Just x) _) = x
mflowPrepaymentPenalty (MortgageDelinqFlow _ _ _ _ _ _ _ _ _ _ _ Nothing _) = 0
mflowPrepaymentPenalty _ = undefined

-- tobe factor out alongside with similar funciton in bond cashflow
mflowWeightAverageBalance :: Date -> Date -> [TsRow] -> Balance
mflowWeightAverageBalance sd ed trs
  = sum $ zipWith mulBR _bals _dfs  -- `debug` ("CalcingAvgBal=>"++show sd++show ed++show txns  )
    where
     txns = filter (\x -> (view tsDate x >=sd)&& (view tsDate x)<=ed) trs
     _ds = view tsDate <$> txns -- `debug` ("fee base txns"++show txns)
     _bals = map mflowBegBalance txns
     _dfs =  getIntervalFactors $ sd:_ds

emptyTsRow :: Date -> TsRow -> TsRow 
-- ^ reset all cashflow fields to zero and init with a date
emptyTsRow _d (MortgageDelinqFlow a x c d e f g h i j k l m) = MortgageDelinqFlow _d 0 0 0 0 0 0 0 0 0 Nothing Nothing Nothing
emptyTsRow _d (MortgageFlow a x c d e f g h i j k l) = MortgageFlow _d 0 0 0 0 0 0 0 0 Nothing Nothing Nothing
emptyTsRow _d (LoanFlow a x c d e f g i j k) = LoanFlow _d 0 0 0 0 0 0 0 0 Nothing
emptyTsRow _d (LeaseFlow a x c d) = LeaseFlow _d 0 0 0
emptyTsRow _d (FixedFlow a x c d e f ) = FixedFlow _d 0 0 0 0 0
emptyTsRow _d (BondFlow a x c d) = BondFlow _d 0 0 0
emptyTsRow _d (ReceivableFlow a x c d e f g h i) = ReceivableFlow _d 0 0 0 0 0 0 0 Nothing

extendCashFlow :: Date -> CashFlowFrame -> CashFlowFrame
extendCashFlow d (CashFlowFrame st []) = CashFlowFrame st []
extendCashFlow d (CashFlowFrame st txns) 
    = let 
        lastRow = last txns
        newTxn = emptyTsRow d lastRow
      in 
        CashFlowFrame st (txns++[newTxn])


viewTsRow :: Date -> TsRow -> TsRow 
-- ^ take a snapshot of a record from record balance/stats and a new date
viewTsRow _d (MortgageDelinqFlow a b c d e f g h i j k l m) = MortgageDelinqFlow _d b 0 0 0 0 0 0 0 j k l m
viewTsRow _d (MortgageFlow a b c d e f g h i j k l) = MortgageFlow _d b 0 0 0 0 0 0 i j k l
viewTsRow _d (LoanFlow a b c d e f g i j k) = LoanFlow _d b 0 0 0 0 0 0 j k
viewTsRow _d (LeaseFlow a b c d) = LeaseFlow _d b 0 0
viewTsRow _d (FixedFlow a b c d e f ) = FixedFlow _d b 0 0 0 0
viewTsRow _d (BondFlow a b c d) = BondFlow _d b 0 0
viewTsRow _d (ReceivableFlow a b c d e f g h i) = ReceivableFlow _d b 0 0 0 0 0 0 i

-- ^ given a cashflow,build a new cf row with begin balance
buildBegTsRow :: Date -> TsRow -> TsRow
buildBegTsRow d flow@FixedFlow{} = flow
buildBegTsRow d tr = 
  let 
    r = set tsRowBalance ((view tsRowBalance tr) + mflowAmortAmount tr) (emptyTsRow d tr)
    rate = mflowRate tr
  in
    tsSetRate rate r

buildStartTsRow :: CashFlowFrame -> Maybe TsRow
buildStartTsRow (CashFlowFrame (begBal,begDate,accInt) []) = Nothing
buildStartTsRow (CashFlowFrame (begBal,begDate,accInt) (txn:txns)) = 
  let 
    rEmpty = emptyTsRow begDate txn 
    r = set tsRowBalance begBal rEmpty
    rate = mflowRate txn
  in
    Just $ tsSetRate rate r

tsSetRate :: IRate -> TsRow -> TsRow
tsSetRate _r (MortgageDelinqFlow a b c d e f g h i j k l m) = MortgageDelinqFlow a b c d e f g h i _r k l m
tsSetRate _r (MortgageFlow a b c d e f g h i j k l) = MortgageFlow a b c d e f g h _r j k l
tsSetRate _r (LoanFlow a b c d e f g i j k) = LoanFlow a b c d e f g i _r k
tsSetRate _r (BondFlow a b c d) = BondFlow a b c d
tsSetRate _r (ReceivableFlow a b c d e f g h i) = ReceivableFlow a b c d e f g h i
tsSetRate _r (LeaseFlow a b c d) = LeaseFlow a b c d
tsSetRate _r (FixedFlow {} ) = error "Not implement set rate for FixedFlow"
tsSetRate _ _ = error "Not implement set rate for this type"


insertBegTsRow :: Date -> CashFlowFrame -> CashFlowFrame
insertBegTsRow d (CashFlowFrame st []) = CashFlowFrame st []
insertBegTsRow d (CashFlowFrame st (txn:txns))
  = let
      begRow = buildBegTsRow d txn
    in 
      CashFlowFrame st (begRow:txn:txns)


totalLoss :: CashFlowFrame -> Balance
totalLoss (CashFlowFrame _ rs) = sum $ mflowLoss <$> rs

totalDefault :: CashFlowFrame -> Balance
totalDefault (CashFlowFrame _ rs) = sum $ mflowDefault <$> rs

totalRecovery :: CashFlowFrame -> Balance
totalRecovery (CashFlowFrame _ rs) = sum $ mflowRecovery <$> rs

totalPrincipal :: CashFlowFrame -> Balance
totalPrincipal (CashFlowFrame _ rs) = sum $ mflowPrincipal <$> rs

-- ^ merge two cashflow frame but no patching beg balance
mergePoolCf :: CashFlowFrame -> CashFlowFrame -> CashFlowFrame
mergePoolCf cf (CashFlowFrame _ []) = cf
mergePoolCf (CashFlowFrame _ []) cf = cf
-- first day of left is earlier than right one
mergePoolCf cf1@(CashFlowFrame st1 txns1) cf2@(CashFlowFrame st2 txns2) 
  | startDate1 > startDate2 = mergePoolCf cf2 cf1 
  | otherwise 
      = let 
          splitDate = firstDate cf2  -- (ls,rs) = splitByDate txns d st
          (txn0,txnToMerged) = splitByDate txns1 splitDate EqToRight
          txn1 = combineTss [] txnToMerged txns2 -- `debug` ("left"++show cfToBeMerged++">> right"++ show cf2)
        in 
          CashFlowFrame st1 (txn0++txn1) -- `debug` ("Txn1"++show txn1)
  where 
    [startDate1,startDate2] = firstDate <$> [cf1,cf2]


-- ^ agg cashflow (but not updating the cumulative stats)
aggTs :: [TsRow] -> [TsRow] -> [TsRow]
-- ^ short circuit
aggTs [] [] = []
-- ^ return result update the cumulative stats
aggTs rs [] = rs 
-- ^ init with the first row
aggTs [] (r:rs) = aggTs [r] rs
aggTs (r:rs) (tr:trs) 
  | sameDate r tr = aggTs (addTs r tr:rs) trs
  | otherwise = aggTs (tr:r:rs) trs 


patchBalance :: (Balance,Maybe CumulativeStat) -> [TsRow] -> [TsRow] -> [TsRow]
patchBalance (bal,stat) [] [] = []
patchBalance (bal,mStat) r [] = case mStat of 
                                  Just stat -> patchCumulative stat [] $ reverse r
                                  Nothing -> patchCumulative (0,0,0,0,0,0) [] $ reverse r
patchBalance (bal,stat) r (tr:trs) = 
  let 
    amortAmt = mflowAmortAmount tr
    newBal = bal - amortAmt
    rWithUpdatedBal = set tsRowBalance newBal tr
  in 
    patchBalance (newBal,stat) (rWithUpdatedBal:r) trs

-- type CumulativeStat = (CumPrincipal,CumPrepay,CumDelinq,CumDefault,CumRecovery,CumLoss)
-- 
calcBeginStats :: Maybe CumulativeStat -> TsRow -> CumulativeStat
calcBeginStats Nothing tr = (0,0,0,0,0,0)
calcBeginStats (Just (cumPrin,cumPrepay,cumDlinq,cumDef,cumRec,cumLoss)) tr
  = case tr of 
      (MortgageFlow _ _ p _ ppy def rec los _ _ _ _) -> 
        (cumPrin - p,cumPrepay - ppy, 0 , cumDef - def, cumRec - rec , cumLoss - los)
      (MortgageDelinqFlow _ _ p _ ppy delinq def rec los _ _ _ _) -> 
        (cumPrin - p,cumPrepay - ppy, cumDlinq - delinq , cumDef - def, cumRec - rec , cumLoss - los)
      (LoanFlow _ _ p _ ppy def rec los _ _) -> 
        (cumPrin - p,cumPrepay - ppy, 0 , cumDef - def, cumRec - rec , cumLoss - los)
      (ReceivableFlow _ _ _ p f def rec los _) -> 
        (cumPrin - p, 0 , 0 , cumDef - def, cumRec - rec , cumLoss - los)
      (BondFlow _ _ p i) -> 
        (cumPrin - p,0 , 0 , 0, 0, 0)
      (LeaseFlow _ b r def ) -> 
        (cumPrin - r,0 , 0, cumDef - def, 0, 0)
      (FixedFlow _ b c d e _ ) -> (0, 0 ,0 , 0, 0, 0)
      (CashFlow _ amt) -> (0,0,0,0,0,0)


getCfBegStats :: CashFlowFrame -> CumulativeStat
getCfBegStats (CashFlowFrame _ []) = (0,0,0,0,0,0)
getCfBegStats (CashFlowFrame _ (tr:trs)) = calcBeginStats (view tsCumulative tr) tr


mergePoolCf2 :: CashFlowFrame -> CashFlowFrame -> CashFlowFrame
mergePoolCf2 cf (CashFlowFrame _ []) = cf
mergePoolCf2 (CashFlowFrame _ []) cf = cf
mergePoolCf2 cf1@(CashFlowFrame st1@(bBal1,bDate1,a1) txns1) cf2@(CashFlowFrame (bBal2,bDate2,a2) txns2) 
  | null txns2 = over cashflowTxn (patchBalance (bBal1,head txns1 ^. tsCumulative) []) cf1
  | bDate1 > bDate2 = mergePoolCf2 cf2 cf1
  -- both cashflow frame start on the same day OR left one starts earlier than right one
  -- 20241021:why? | bDate1 == bDate2 && bBal2 == 0 = over cashflowTxn (patchBalance bBal1 []) cf1
  | bDate1 == bDate2 && bBal2 == 0 = cf1
  | bDate1 == bDate2 = 
    let 
      begBal = bBal1 + bBal2
      
      begStat = sumStats (Just (getCfBegStats cf1)) (Just (getCfBegStats cf2))
      txnsSorted = reverse $ L.sortOn getDate (txns1 ++ txns2)
      txnAggregated = aggTs [] txnsSorted
      txnPatchedBalance = patchBalance (begBal,begStat) [] txnAggregated -- `debug` ("\n Pathcing with stat"++ show begStat)
    in 
      CashFlowFrame (begBal, bDate1, a1) txnPatchedBalance
  | otherwise 
      = let 
          (resultCf1, cfToCombine) = splitCashFlowFrameByDate cf1 bDate2 EqToRight 
          (CashFlowFrame _ txnCombined) = mergePoolCf2 cfToCombine cf2
        in 
          over cashflowTxn (++ txnCombined) resultCf1 


mergeCf :: CashFlowFrame -> CashFlowFrame -> CashFlowFrame
mergeCf cf (CashFlowFrame _ []) = cf
mergeCf (CashFlowFrame _ []) cf = cf
mergeCf cf1@(CashFlowFrame (begBal1,begDate1,mAccInt1) txns1) cf2@(CashFlowFrame (begBal2,begDate2,mAccInt2)txns2) -- first day of left is earlier than right one
  = let 
      mSrow1 = buildStartTsRow cf1
      mSrow2 = buildStartTsRow cf2
      txns1' = case mSrow1 of
                  Nothing -> txns1
                  Just srow1 -> srow1:txns1
      txns2' = case mSrow2 of
                  Nothing -> txns2
                  Just srow2 -> srow2:txns2
      txns = combineTss [] txns1' txns2'
      newSt = if begDate1 < begDate2 then (begBal1,begDate1,mAccInt1) else (begBal2,begDate2,mAccInt2)
    in 
      CashFlowFrame newSt txns


consolidateCashFlow :: CashFlowFrame -> CashFlowFrame
consolidateCashFlow (CashFlowFrame st []) = CashFlowFrame st []
consolidateCashFlow (CashFlowFrame st (txn:txns))
  = let 
      totalBals = sum $ mflowAmortAmount <$> (txn:txns)
    in 
      CashFlowFrame st (set tsRowBalance totalBals txn:txns)
    

shiftCfToStartDate :: Date -> CashFlowFrame -> CashFlowFrame
shiftCfToStartDate d cf@(CashFlowFrame st (txn:txns))
  = let 
      fstDate = firstDate cf 
      diffDays = daysBetween fstDate d
    in 
      CashFlowFrame st $ tsOffsetDate diffDays <$> (txn:txns)

-- ^ sum a single pool source from a cashflow frame
sumPoolFlow :: CashFlowFrame -> PoolSource -> Balance
sumPoolFlow (CashFlowFrame _ trs) ps 
  = sum $ (`lookupSource` ps) <$> trs

-- ^ lookup a pool source from a row
lookupSource :: TsRow -> PoolSource -> Balance 
lookupSource tr CollectedPrepayment  = mflowPrepayment tr
lookupSource tr CollectedPrincipal = mflowPrincipal tr
lookupSource tr CollectedRecoveries = mflowRecovery tr
lookupSource tr CollectedRental = mflowRental tr
lookupSource tr CollectedInterest = mflowInterest tr
lookupSource tr CollectedPrepaymentPenalty = mflowPrepaymentPenalty tr
lookupSource tr CollectedFeePaid = mflowFeePaid tr
lookupSource tr CollectedCash = tsTotalCash tr
lookupSource tr NewDelinquencies = mflowDelinq tr
lookupSource tr NewDefaults = mflowDefault tr
lookupSource tr NewLosses = mflowLoss tr
lookupSource tr CurBalance = view tsRowBalance tr
lookupSource tr CurBegBalance = mflowBegBalance tr
lookupSource tr x = error ("Failed to lookup source"++ show x)

lookupSourceM :: Balance -> Maybe TsRow -> PoolSource -> Balance
lookupSourceM bal Nothing CurBegBalance = bal
lookupSourceM bal Nothing CurBalance = bal
lookupSourceM _ Nothing _ = 0
lookupSourceM _ (Just tr) ps = lookupSource tr ps


setPrepaymentPenalty :: Balance -> TsRow -> TsRow
setPrepaymentPenalty bal (MortgageDelinqFlow a b c d e f g h i j k l m) = MortgageDelinqFlow a b c d e f g h i j k (Just bal) m
setPrepaymentPenalty bal (MortgageFlow b c d e f g h i j k l m) = MortgageFlow b c d e f g h i j k (Just bal) m
setPrepaymentPenalty _ _ = error "prepay pental only applies to MortgageFlow"

setPrepaymentPenaltyFlow :: [Balance] -> [TsRow] -> [TsRow]
setPrepaymentPenaltyFlow bals trs = [ setPrepaymentPenalty bal tr | (bal,tr) <- zip bals trs]


-- ^ split single cashflow record by a rate
splitTs :: Rate -> TsRow -> TsRow 
splitTs r (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN mStat)
  = MortgageDelinqFlow d (mulBR bal r) (mulBR p r) (mulBR i r) (mulBR ppy r)
                       (mulBR delinq r) (mulBR def r) (mulBR recovery r) (mulBR loss r)
                       rate ((\x -> round (toRational x * r)) <$> mB) ((`mulBR` r) <$> mPPN)
                       (splitStats r <$> mStat)
splitTs r (MortgageFlow d bal p i ppy def recovery loss rate mB mPPN mStat)
  = MortgageFlow d (mulBR bal r) (mulBR p r) (mulBR i r) (mulBR ppy r)
                       (mulBR def r) (mulBR recovery r) (mulBR loss r)
                       rate ((\x -> round (toRational x * r)) <$> mB) ((`mulBR` r) <$> mPPN)
                       (splitStats r <$> mStat)
splitTs r (LeaseFlow d bal p def)
  = LeaseFlow d (mulBR bal r) (mulBR p r) (mulBR def r)
splitTs _ tr = error $ "Not support for spliting TsRow"++show tr

splitTrs :: Rate -> [TsRow] -> [TsRow]
splitTrs r trs = splitTs r <$> trs 

splitCf :: Rate -> CashFlowFrame -> CashFlowFrame
splitCf 1 cf = cf
splitCf r (CashFlowFrame st []) = CashFlowFrame st []
splitCf r (CashFlowFrame (begBal, begDate, mAccInt) trs) 
  = CashFlowFrame (mulBR begBal r, begDate, (`mulBR` r) <$> mAccInt) $ splitTrs r trs -- `debug` ("split by rate"++ show (fromRational r))

currentCumulativeStat :: [TsRow] -> CumulativeStat
currentCumulativeStat [] = (0,0,0,0,0,0)
currentCumulativeStat trs = 
  let 
    tr = last trs
  in 
    fromMaybe (0,0,0,0,0,0) $ view txnCumulativeStats tr


cashFlowInitCumulativeStats ::  Lens' CashFlowFrame (Maybe CumulativeStat)
cashFlowInitCumulativeStats = lens getter setter 
  where
    getter (CashFlowFrame _ []) = Nothing
    getter (CashFlowFrame _ (tr:trs)) = view txnCumulativeStats tr
    
    setter (CashFlowFrame st []) mStat = CashFlowFrame st []
    setter (CashFlowFrame st (tr:trs)) mStat = CashFlowFrame st $ (set txnCumulativeStats mStat tr):trs


patchCumulativeAtInit :: Maybe CumulativeStat -> [TsRow] -> [TsRow]
patchCumulativeAtInit _ [] = []
patchCumulativeAtInit mStatsInit (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN mStat:trs)
  = MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN (sumStats mStat mStatsInit):trs
patchCumulativeAtInit mStatsInit (MortgageFlow d bal p i ppy def recovery loss rate mB mPPN mStat:trs)
  = MortgageFlow d bal p i ppy def recovery loss rate mB mPPN (sumStats mStat mStatsInit):trs
patchCumulativeAtInit mStatsInit (LoanFlow d bal p i ppy def recovery loss rate mStat:trs)
  = LoanFlow d bal p i ppy def recovery loss rate (sumStats mStat mStatsInit):trs
patchCumulativeAtInit mStatsInit (ReceivableFlow d bal p i ppy def recovery loss mStat:trs)
  = ReceivableFlow d bal p i ppy def recovery loss (sumStats mStat mStatsInit):trs
patchCumulativeAtInit _ trs = trs


patchCumulative :: CumulativeStat -> [TsRow] -> [TsRow] -> [TsRow]
patchCumulative _ rs [] = reverse rs
patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
                rs
                (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN _:trs)
  = patchCumulative newSt
                    (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN (Just newSt):rs)
                    trs
                 where 
                   newSt = (cPrin+p,cPrepay+ppy,cDelinq+delinq,cDefault+def,cRecovery+recovery,cLoss+loss)
patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
               rs
               ((MortgageFlow d bal p i ppy def recovery loss rate mB mPPN _):trs)
  = patchCumulative newSt
                   (MortgageFlow d bal p i ppy def recovery loss rate mB mPPN (Just newSt):rs)
                   trs
                where 
                  newSt = (cPrin+p,cPrepay+ppy,cDelinq,cDefault+def,cRecovery+recovery,cLoss+loss)
patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
              rs
              ((LoanFlow d bal p i ppy def recovery loss rate _):trs)
  = patchCumulative newSt
                  (LoanFlow d bal p i ppy def recovery loss rate (Just newSt):rs)
                  trs
               where 
                 newSt = (cPrin+p,cPrepay+ppy,cDelinq,cDefault+def,cRecovery+recovery,cLoss+loss)

patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
              rs
              ((FixedFlow a b c d e f):trs)
  = patchCumulative newSt
                  (FixedFlow a b c d e f:rs)
                  trs
               where 
                 newSt = (0,0,0,0,0,0)

patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
              rs
              ((ReceivableFlow a b c d e f g h i):trs)
  = patchCumulative newSt
                  (ReceivableFlow a b c d e f g h (Just newSt):rs)
                  trs
               where
                 newSt = (cPrin+c,0,0,cDefault+f,cRecovery+g,cLoss+h)

patchCumulative (cPrin,cPrepay,cDelinq,cDefault,cRecovery,cLoss)
              rs
              ((LeaseFlow a b c d) :trs)
  = patchCumulative newSt
                  (LeaseFlow a b c d:rs)
                  trs
               where
                 newSt = (0,0,0,0,0,0)

patchCumulative a b c = error ("failed to patch cumulative stats for "++show a ++">>"++show b++">>"++show c)



-- ^ split cashflow by rate while build missing defaults/losses stats
cutoffTrs :: Date -> [TsRow] -> ([TsRow],Map.Map CutoffFields Balance)
cutoffTrs d [] = ([],Map.empty)
cutoffTrs d trs 
  = let 
      beforeTrs = cutBy Exc Past d trs
      cumuDefaults = sum $ mflowDefault <$> beforeTrs 
      cumuDelinquency = sum $ mflowDelinq <$> beforeTrs  
      cumuLoss = sum $ mflowLoss <$> beforeTrs 
      m = Map.fromList [(HistoryDefaults,cumuDefaults),(HistoryDelinquency,cumuDelinquency),(HistoryLoss,cumuLoss)]
      
      afterTrs  = cutBy Inc Future d trs
    in
      (patchCumulative (0.0,0.0,0.0,0.0,0.0,0.0) [] afterTrs, m)

-- TODO need to fix accrue interest & cutoff stat
cutoffCashflow :: Date -> Dates -> CashFlowFrame -> CashFlowFrame
cutoffCashflow sd ds (CashFlowFrame st []) = CashFlowFrame st []
cutoffCashflow sd ds (CashFlowFrame st txns) 
  = let 
      futureTxns = cutBy Inc Future sd txns
      withBegTs [] =  []
      withBegTs (tr:trs) = buildBegTsRow sd tr: tr :trs 
      aggTxns = aggTsByDates (withBegTs futureTxns) ds
    in 
      CashFlowFrame (buildBegBal aggTxns, sd, Nothing) aggTxns 


extendTxns :: TsRow -> [Date] -> [TsRow]      
extendTxns tr ds = [ emptyTsRow d tr | d <- ds ]

-- test emtpy cashflow row
isEmptyRow :: TsRow -> Bool 
isEmptyRow (MortgageDelinqFlow _ 0 0 0 0 0 0 0 0 _ _ _ _) = True
isEmptyRow (MortgageFlow _ 0 0 0 0 0 0 0 _ _ _ _) = True
isEmptyRow (LoanFlow _ 0 0 0 0 0 0 0 i j ) = True
isEmptyRow (LeaseFlow _ 0 0 0) = True
isEmptyRow (FixedFlow _ 0 0 0 0 0) = True
isEmptyRow (BondFlow _ 0 0 0) = True
isEmptyRow (CashFlow _ 0) = True
isEmptyRow (ReceivableFlow _ 0 0 0 0 0 0 0 _ ) = True
isEmptyRow _ = False

-- test emtpy cashflow row (ignore balance)
isEmptyRow2 :: TsRow -> Bool 
isEmptyRow2 (MortgageDelinqFlow _ _ 0 0 0 0 0 0 0 _ _ _ _) = True
isEmptyRow2 (MortgageFlow _ _ 0 0 0 0 0 0 _ _ _ _) = True
isEmptyRow2 (LoanFlow _ _ 0 0 0 0 0 0 i j ) = True
isEmptyRow2 (LeaseFlow _ _ 0 _) = True
isEmptyRow2 (FixedFlow _ _ 0 0 0 0) = True
isEmptyRow2 (BondFlow _ _ 0 0) = True
isEmptyRow2 (CashFlow _ 0) = True
isEmptyRow2 (ReceivableFlow _ _ 0 0 0 0 0 0 _ ) = True
isEmptyRow2 _ = False

-- ^ Remove empty cashflow from the tail
dropTailEmptyTxns :: [TsRow] -> [TsRow]
dropTailEmptyTxns trs 
  = reverse $ dropWhile isEmptyRow (reverse trs)


cashflowTxn :: Lens' CashFlowFrame [TsRow]
cashflowTxn = lens getter setter
  where 
    getter (CashFlowFrame _ txns) = txns
    setter (CashFlowFrame st txns) newTxns = CashFlowFrame st newTxns


txnCumulativeStats :: Lens' TsRow (Maybe CumulativeStat)
txnCumulativeStats = lens getter setter
  where 
    getter (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN mStat) = mStat
    getter (MortgageFlow d bal p i ppy def recovery loss rate mB mPPN mStat) = mStat
    getter (LoanFlow d bal p i ppy def recovery loss rate mStat) = mStat
    getter (ReceivableFlow d bal p i ppy def recovery loss mStat) = mStat
    getter _ = Nothing
    
    setter (MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN _) mStat 
      = MortgageDelinqFlow d bal p i ppy delinq def recovery loss rate mB mPPN mStat
    setter (MortgageFlow d bal p i ppy def recovery loss rate mB mPPN _) mStat
      = MortgageFlow d bal p i ppy def recovery loss rate mB mPPN mStat
    setter (LoanFlow d bal p i ppy def recovery loss rate _) mStat
      = LoanFlow d bal p i ppy def recovery loss rate mStat
    setter (ReceivableFlow d bal p i ppy def recovery loss _) mStat
      = ReceivableFlow d bal p i ppy def recovery loss mStat
    setter x _ = x

$(deriveJSON defaultOptions ''TsRow)
$(deriveJSON defaultOptions ''CashFlowFrame)