-----------------------------------------------------------------------------
--
-- Module      :  Data.Function.Excel
-- Copyright   :  (c) 2012-16 Brian W Bush
-- License     :  MIT
--
-- Maintainer  :  Brian W Bush <consult@brianwbush.info>
-- Stability   :  Stable
-- Portability :  Portable
--
-- | Several Microsoft Excel functions, adapted from <https://gist.github.com/econ-r/dcd503815bbb271484ff>.
--
-----------------------------------------------------------------------------


{-# LANGUAGE Safe                        #-}

{-# OPTIONS_GHC -fno-warn-name-shadowing #-}


module Data.Function.Excel (
-- * Finance functions
  pmt
, ipmt
, ppmt
, nper
, pv
, fv
) where


import Data.Maybe (fromMaybe)


-- | Compute payments.
pmt :: Double       -- ^ Interest rate per period.
    -> Int          -- ^ Total number of periods.
    -> Double       -- ^ Present value.
    -> Maybe Double -- ^ Future value.
    -> Maybe Bool   -- ^ Whether payments are due at the beginning of each perioed.
    -> Double       -- ^ Payment per period.
pmt rate nper pv fv typ =
  if rate /= 0 
    then (rate * (fv' + pv * (1+ rate)^nper)) / ((1 + rate * typ') * (1 - (1 + rate)^nper))
    else  -1 * (fv' + pv) / fromIntegral nper
    where
      fv' = fromMaybe 0 fv
      typ' = if fromMaybe False typ then 1 else 0


-- | Compute interest payment.
ipmt :: Double       -- ^ Interest rate per period.
     -> Int          -- ^ Period for which interest is to be computed.
     -> Int          -- ^ Total number of periods.
     -> Double       -- ^ Present value.
     -> Maybe Double -- ^ Future value.
     -> Maybe Bool   -- ^ Whether payments are due at the beginning of each period.
     -> Double       -- ^ Interest payment.
ipmt rate per nper pv fv (Just True) = ipmt rate per nper pv fv (Just False) / (1 + rate)
ipmt rate per nper pv fv _ = - ((1 + rate)^(per-1) * (pv * rate + pmt rate nper pv fv (Just False)) - pmt rate nper pv fv (Just False))


-- | Compute principle payment.
ppmt :: Double       -- ^ Interest rate per period.
     -> Int          -- ^ Period for which interest is to be computed.
     -> Int          -- ^ Total number of periods.
     -> Double       -- ^ Present value.
     -> Maybe Double -- ^ Future value.
     -> Maybe Bool   -- ^ Whether payments are due at the beginning of each period.
     -> Double       -- ^ Interest payment. 
ppmt rate per nper pv fv typ = pmt rate nper pv fv typ - ipmt rate per nper pv fv typ


-- | Number of periods for an investment.
nper :: Double       -- ^ Interest rate per period.
     -> Double       -- ^ Payment per period.
     -> Double       -- ^ Present value.
     -> Maybe Double -- ^ Future value.
     -> Maybe Bool   -- ^ Whether payments are due at the beginning of each period.
     -> Double       -- ^ Number of periods.
nper rate pmt pv fv typ =
  if rate /= 0
    then logBase (1 + rate) ((pmt * (1 + rate * typ') - fv' * rate) / (pmt * (1 + rate * typ') + pv * rate))
    else - (fv' + pv) / pmt
    where
      fv' = fromMaybe 0 fv
      typ' = if fromMaybe False typ then 1 else 0


-- | Compute prevent value.
pv :: Double       -- ^ Interest rate per period.
   -> Int          -- ^ Total number of periods.
   -> Double       -- ^ Payment per period.
   -> Maybe Double -- ^ Future value.
   -> Maybe Bool   -- ^ Whether payments are due at the beginning of each period.
   -> Double       -- ^ Present value.
pv rate nper pmt fv typ =
  if rate /= 0
    then - (fv' + pmt * (1 + rate * typ') * (((1 + rate)^nper) - 1) / rate) / (1 + rate)^nper
    else - (fv' + pmt * fromIntegral nper)
    where
      fv' = fromMaybe 0 fv
      typ' = if fromMaybe False typ then 1 else 0


-- | Compute future value.
fv :: Double       -- ^ Interest rate per period.
   -> Int          -- ^ Total number of periods.
   -> Double       -- ^ Payment per period.
   -> Maybe Double -- ^ Present value.
   -> Maybe Bool   -- ^ Whether payments are due at the beginning of each period.
   -> Double       -- ^ Future value.
fv rate nper pmt pv typ =
  if rate /= 0
    then (pmt * (1 + rate * typ') * (1 - (1 + rate)^nper) / rate) - pv' * (1 + rate)^nper
    else - (pv' + pmt * fromIntegral nper)
    where
      pv' = fromMaybe 0 pv
      typ' = if fromMaybe False typ then 1 else 0