{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}

----------------------------------------------------------------------
-- |
-- Module: Data.Money
--
--
--
----------------------------------------------------------------------

module Data.Money
  ( -- * Currencies
    module Data.Money.Currency
    -- * Money
  , Money (..)
    -- * Exchange rates
  , ExchangeRate (..)
    -- * Interchanges
  , interchange
    -- * Conversions
  , convert
  , convert'
  )
  where

-- money
import Data.Money.Currency


----------------------------------------------------------------------
-- * Money
----------------------------------------------------------------------

-- |
--
-- Money in a currency.
--
-- Examples:
--
-- >>> 1000 :: Money COP
-- COP 1000.0
--
-- >>> 1000 :: Money EUR
-- EUR 1000.0
--
-- >>> 1000 :: Money USD
-- USD 1000.0
--
-- >>> 999.99 :: Money USD
-- USD 999.99
--
-- >>> 1000 + 500 :: Money COP
-- COP 1500.0
--
-- >>> (1000 :: Money COP) + (500 :: Money EUR)
-- ...
--
-- >>> 1000 == (500 * 2 :: Money USD)
-- True
--
-- >>> 1000 < (500 :: Money EUR)
-- False

newtype Money currency =
  Money
    { getAmount :: Rational
    }
  deriving (Eq, Fractional, Num, Ord)


-- |
--
-- Show money in COP.

instance Show (Money COP) where
  show Money {..} =
    "COP " ++ show (fromRational getAmount :: Double)


-- |
--
-- Show money in EUR.

instance Show (Money EUR) where
  show Money {..} =
    "EUR " ++ show (fromRational getAmount :: Double)


-- |
--
-- Show money in USD.

instance Show (Money USD) where
  show Money {..} =
    "USD " ++ show (fromRational getAmount :: Double)


----------------------------------------------------------------------
-- * Exchange rates
----------------------------------------------------------------------

-- |
--
-- An exchange rate, that is, the value of one currency for the
-- purpose of conversion to another.
--
-- Examples:
--
-- >>> 3167.20 :: ExchangeRate USD COP
-- COP 3167.2
--
-- >>> 1.06 :: ExchangeRate EUR USD
-- USD 1.06
--
-- >>> 0.94 :: ExchangeRate USD EUR
-- EUR 0.94

newtype ExchangeRate currency1 currency2 =
  ExchangeRate
    { getExchangeRate :: Money currency2
    }
  deriving (Eq, Fractional, Num)


-- |
--
--

instance Show (Money cur2) => Show (ExchangeRate cur1 cur2) where
  show =
    show . getExchangeRate


----------------------------------------------------------------------
-- * Interchanges
----------------------------------------------------------------------

-- |
--
-- Interchange (or flip) an exchange rate.
--
-- Examples:
--
-- >>> interchange (1.06 :: ExchangeRate EUR USD)
-- EUR 0.94...
--
-- >>> interchange (0.94 :: ExchangeRate USD EUR)
-- USD 1.06...
--
-- >>> interchange (interchange (1.06 :: ExchangeRate EUR USD))
-- USD 1.06
--
-- >>> interchange (interchange (0.94 :: ExchangeRate USD EUR))
-- EUR 0.94

interchange
  :: ExchangeRate cur1 cur2
  -> ExchangeRate cur2 cur1
interchange (ExchangeRate exchangeRate) =
  ExchangeRate (Money (1 / getAmount exchangeRate))


----------------------------------------------------------------------
-- * Conversions
----------------------------------------------------------------------

-- |
--
-- Convert money using an exchange rate.
--
-- Examples:
--
-- >>> usdToCop = 3182.01 :: ExchangeRate USD COP
--
-- >>> convert usdToCop 1000
-- COP 3182010.0
--
-- >>> convert (interchange usdToCop) 1000
-- USD 0.31...
--
-- >>> convert (interchange usdToCop) (convert usdToCop 1000)
-- USD 1000.0

convert
  :: ExchangeRate cur1 cur2
  -> Money cur1
  -> Money cur2
convert (ExchangeRate exchangeRate) (Money amount) =
  Money (amount * getAmount exchangeRate)


-- |
--
-- Convert money using an interchanged (or flipped) exchange rate.
--
-- Examples:
--
-- >>> usdToCop = 3182.01 :: ExchangeRate USD COP
--
-- >>> convert' usdToCop 1000
-- USD 0.31...
--
-- >>> convert' (interchange usdToCop) 1000
-- COP 3182010.0
--
-- >>> convert' usdToCop (convert' (interchange usdToCop) 1000)
-- USD 1000.0
--
-- >>> convert' usdToCop (convert usdToCop 1000)
-- USD 1000.0

convert'
  :: ExchangeRate cur2 cur1
  -> Money cur1
  -> Money cur2
convert' exchangeRate =
  convert (interchange exchangeRate)