module Valuation where

import qualified Data.Map as Map
import qualified Data.Set as Set
import Data.Time
import Control.Monad.Error
import LedgerT
import ErrVal

type Date = Day
type CommoditySymbol = String

-- | To value commodities we need a single commodity to do it in, and
-- price information.
--
-- For example, if we were doing our valuations in USD, and we were
-- valuing AUD at an exchange rate of 0.70 AUD/USD, and AAPL at 80USD,
-- then we'd have venv_commodity=USD, and venv_prices = Map.fromList [
-- ((AUD,USD),0.70), ((AAPL,USD),80) ]
data ValuationEnv = ValuationEnv {
    venv_commodity :: Commodity,
    venv_prices :: Map.Map (CommoditySymbol,CommoditySymbol) Double
}
   deriving (Show)


-- | Make a valuation environment from a ledger file
--
-- ''makeValuationEnv c cs tp rl'' constructs a ValuationEnv by reading
-- prices for all commodities cs in terms of commodity c for dates
-- matching dp from the given ledger data rl. If more than one date
-- matches the predicate, then the most recent will be chosen. The
-- valuation commodity is set to c.
makeValuationEnv :: CommoditySymbol -> [CommoditySymbol] ->
                    (Date -> Bool) -> RawLedger -> ValuationEnv
makeValuationEnv vcur commods dateok rl = (ValuationEnv vcommod (Map.map snd pmap))
  where
    pmap = foldr pf Map.empty (historical_prices rl)
    vcommod = dollar{symbol=vcur}
    commodOK c = Set.member c cset
    cset = Set.fromList commods
    pf (HistoricalPrice t sym1 sym2 price) m
        | sym2 == vcur && commodOK sym1 && dateok t = Map.insertWith f (sym1,sym2) (t,price) m
        | otherwise = m
    f (t1,p1) (t2,p2) | t1 > t2 = (t1,p1)
                      | otherwise = (t2,p2)


-- | Get the current value of a unit of a commodity
presentValue :: ValuationEnv -> CommoditySymbol -> ErrVal Double
presentValue venv c = if vcur == c then eVal 1.0 else price
  where
    vcur = symbol (venv_commodity venv)
    prices = venv_prices venv
    price = case Map.lookup (c,vcur) prices of
       Just v -> eVal v
       Nothing -> case Map.lookup (vcur,c) prices of
           Just v -> eVal (1/v)
           Nothing -> eErr ("no price for " ++ c)

