module Ledger.Amount
where
import qualified Data.Map as Map
import Ledger.Utils
import Ledger.Types
import Ledger.Commodity
instance Show Amount where show = showAmount
instance Show MixedAmount where show = showMixedAmount
instance Num Amount where
abs (Amount c q p) = Amount c (abs q) p
signum (Amount c q p) = Amount c (signum q) p
fromInteger i = Amount (comm "") (fromInteger i) Nothing
(+) = amountop (+)
() = amountop ()
(*) = amountop (*)
instance Ord Amount where
compare (Amount ac aq ap) (Amount bc bq bp) = compare (ac,aq,ap) (bc,bq,bp)
instance Num MixedAmount where
fromInteger i = Mixed [Amount (comm "") (fromInteger i) Nothing]
negate (Mixed as) = Mixed $ map negateAmountPreservingPrice as
(+) (Mixed as) (Mixed bs) = normaliseMixedAmount $ Mixed $ as ++ bs
(*) = error "programming error, mixed amounts do not support multiplication"
abs = error "programming error, mixed amounts do not support abs"
signum = error "programming error, mixed amounts do not support signum"
instance Ord MixedAmount where
compare (Mixed as) (Mixed bs) = compare as bs
negateAmountPreservingPrice a = (a){price=price a}
amountop :: (Double -> Double -> Double) -> Amount -> Amount -> Amount
amountop op a@(Amount ac aq ap) b@(Amount bc bq bp) =
Amount bc ((quantity $ convertAmountTo bc a) `op` bq) Nothing
costOfAmount :: Amount -> Amount
costOfAmount a@(Amount _ _ Nothing) = a
costOfAmount a@(Amount _ q (Just price))
| isZeroMixedAmount price = nullamt
| otherwise = Amount pc (pq*q) Nothing
where (Amount pc pq _) = head $ amounts price
convertAmountTo :: Commodity -> Amount -> Amount
convertAmountTo c2 (Amount c1 q p) = Amount c2 (q * conversionRate c1 c2) Nothing
showAmount :: Amount -> String
showAmount a@(Amount (Commodity {symbol=sym,side=side,spaced=spaced}) q pri)
| sym=="AUTO" = ""
| side==L = printf "%s%s%s%s" sym space quantity price
| side==R = printf "%s%s%s%s" quantity space sym price
where
space = if spaced then " " else ""
quantity = showAmount' a
price = case pri of (Just pamt) -> " @ " ++ showMixedAmount pamt
Nothing -> ""
showAmount' :: Amount -> String
showAmount' (Amount (Commodity {comma=comma,precision=p}) q _) = quantity
where
quantity = commad $ printf ("%."++show p++"f") q
commad = if comma then punctuatethousands else id
punctuatethousands :: String -> String
punctuatethousands s =
sign ++ (addcommas int) ++ frac
where
(sign,num) = break isDigit s
(int,frac) = break (=='.') num
addcommas = reverse . concat . intersperse "," . triples . reverse
triples [] = []
triples l = [take 3 l] ++ (triples $ drop 3 l)
isZeroAmount :: Amount -> Bool
isZeroAmount a = nonzerodigits == ""
where nonzerodigits = filter (`elem` "123456789") $ showAmount a
amounts :: MixedAmount -> [Amount]
amounts (Mixed as) = as
isZeroMixedAmount :: MixedAmount -> Bool
isZeroMixedAmount = all isZeroAmount . amounts . normaliseMixedAmount
mixedAmountEquals :: MixedAmount -> MixedAmount -> Bool
mixedAmountEquals a b = amounts a' == amounts b' || (isZeroMixedAmount a' && isZeroMixedAmount b')
where a' = normaliseMixedAmount a
b' = normaliseMixedAmount b
showMixedAmount :: MixedAmount -> String
showMixedAmount m = concat $ intersperse "\n" $ map showfixedwidth as
where
(Mixed as) = normaliseMixedAmount m
width = maximum $ map (length . show) $ as
showfixedwidth = printf (printf "%%%ds" width) . show
showMixedAmountOrZero :: MixedAmount -> String
showMixedAmountOrZero a
| isZeroMixedAmount a = "0"
| otherwise = showMixedAmount a
normaliseMixedAmount :: MixedAmount -> MixedAmount
normaliseMixedAmount (Mixed as) = Mixed as''
where
as'' = map sumAmountsPreservingPrice $ group $ sort as'
sort = sortBy cmpsymbolandprice
cmpsymbolandprice a1 a2 = compare (sym a1,price a1) (sym a2,price a2)
group = groupBy samesymbolandprice
samesymbolandprice a1 a2 = (sym a1 == sym a2) && (price a1 == price a2)
sym = symbol . commodity
as' | null nonzeros = [head $ zeros ++ [nullamt]]
| otherwise = nonzeros
(zeros,nonzeros) = partition isZeroAmount as
sumAmountsPreservingPrice [] = nullamt
sumAmountsPreservingPrice as = (sum as){price=price $ head as}
costOfMixedAmount :: MixedAmount -> MixedAmount
costOfMixedAmount (Mixed as) = Mixed $ map costOfAmount as
nullamt :: Amount
nullamt = Amount unknown 0 Nothing
nullmixedamt :: MixedAmount
nullmixedamt = Mixed []
missingamt :: MixedAmount
missingamt = Mixed [Amount (Commodity {symbol="AUTO",side=L,spaced=False,comma=False,precision=0}) 0 Nothing]