{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | This module only exports orphan 'Ae.FromJSON' and 'Ae.ToJSON' instances.
-- Import as:
--
-- @
-- import "Money.Aeson" ()
-- @
module Money.Aeson () where

import Control.Applicative ((<|>), empty)
import Control.Monad ((<=<), when)
import qualified Data.Aeson as Ae
import Data.Ratio ((%), numerator, denominator)
import qualified Data.Text as T
import GHC.TypeLits (KnownSymbol)
import qualified Money
import qualified Money.Internal as MoneyI

--------------------------------------------------------------------------------
-- | Compatible with 'Money.SomeDense'
--
-- Example rendering @'Money.dense'' (2 '%' 3) :: 'Money.Dense' \"BTC\"@:
--
-- @
-- [\"BTC\", 2, 3]
-- @
--
-- Note: The JSON serialization changed in version 0.4 (the leading @"Dense"@
-- string was dropped from the rendered 'Ae.Array').
instance KnownSymbol currency => Ae.ToJSON (Money.Dense currency) where
  toJSON = Ae.toJSON . Money.toSomeDense

-- | Compatible with 'Money.SomeDense'
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,
-- this instance is still able to cope with the previous format.
instance KnownSymbol currency => Ae.FromJSON (Money.Dense currency) where
  parseJSON = maybe empty pure <=< fmap Money.fromSomeDense . Ae.parseJSON

-- | Compatible with 'Money.Dense'
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading
-- @"Dense"@ string was dropped from the rendered 'Ae.Array').
instance Ae.ToJSON Money.SomeDense where
  toJSON = \sd ->
    let r = Money.someDenseAmount sd
    in Ae.toJSON (MoneyI.someDenseCurrency' sd, numerator r, denominator r)

-- | Compatible with 'Money.Dense'.
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,
-- this instance is still able to cope with the previous format.
instance Ae.FromJSON Money.SomeDense where
  parseJSON = \v -> do
    (c, n, d) <- Ae.parseJSON v <|> do
       -- Pre 0.4 format.
       ("Dense" :: String, c, n, d) <- Ae.parseJSON v
       pure (c, n, d)
    when (d == 0) (fail "denominator is zero")
    maybe empty pure (MoneyI.mkSomeDense' c (n % d))

-- | Compatible with 'Money.SomeDiscrete'
--
-- Example rendering @'Money.discrete' 43 :: 'Money.Discrete' \"BTC\" \"satoshi\"@:
--
-- @
-- [\"BTC\", 100000000, 1, 43]
-- @
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading
-- @"Discrete"@ string was dropped from the rendered 'Ae.Array').
instance
  ( KnownSymbol currency, Money.GoodScale scale
  ) => Ae.ToJSON (Money.Discrete' currency scale) where
  toJSON = Ae.toJSON . Money.toSomeDiscrete

-- | Compatible with 'Money.SomeDiscrete'
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,
-- this instance is still able to cope with the previous format.
instance
  ( KnownSymbol currency, Money.GoodScale scale
  ) => Ae.FromJSON (Money.Discrete' currency scale) where
  parseJSON = maybe empty pure <=< fmap Money.fromSomeDiscrete . Ae.parseJSON

-- | Compatible with 'Money.Discrete''
--
-- Note: The JSON serialization changed in version 0.4 (the leading @"Discrete"@
-- string was dropped from the rendered 'Ae.Array').
instance Ae.ToJSON Money.SomeDiscrete where
  toJSON = \sd ->
    let rs = Money.scaleToRational (Money.someDiscreteScale sd)
    in Ae.toJSON (MoneyI.someDiscreteCurrency' sd,
                  numerator rs, denominator rs,
                  Money.someDiscreteAmount sd)

-- | Compatible with 'Money.Discrete''
--
-- Note: The JSON serialization changed in version 0.4. However, this instance
-- is still able to cope with the previous format.
instance Ae.FromJSON Money.SomeDiscrete where
  parseJSON = \v -> do
    (c, n, d, a) <- Ae.parseJSON v <|> do
       -- Pre 0.4 format.
       ("Discrete" :: T.Text, c, n, d, a) <- Ae.parseJSON v
       pure (c, n, d, a)
    when (d == 0) (fail "denominator is zero")
    maybe empty pure (MoneyI.mkSomeDiscrete' c
                        <$> Money.scaleFromRational (n % d)
                        <*> pure a)

-- | Compatible with 'Money.SomeExchangeRate'
--
-- Example rendering an 'Money.ExchangeRate' constructed with
-- @'Money.exchangeRate' (5 '%' 7) :: 'Money.ExchangeRate' \"USD\" \"JPY\"@
--
-- @
-- [\"USD\", \"JPY\", 5, 7]
-- @
--
-- Note: The JSON serialization changed in version 0.4 (the leading
-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').
instance
  ( KnownSymbol src, KnownSymbol dst
  ) => Ae.ToJSON (Money.ExchangeRate src dst) where
  toJSON = Ae.toJSON . Money.toSomeExchangeRate

-- | Compatible with 'Money.SomeExchangeRate'
--
-- Note: The JSON serialization changed in version 0.4. However, this instance
-- is still able to cope with the previous format.
instance
  ( KnownSymbol src, KnownSymbol dst
  ) => Ae.FromJSON (Money.ExchangeRate src dst) where
  parseJSON =
    maybe empty pure <=< fmap Money.fromSomeExchangeRate . Ae.parseJSON

-- | Compatible with 'Money.ExchangeRate'
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4 (the leading
-- @"ExchangeRate"@ string was dropped from the rendered 'Ae.Array').
instance Ae.ToJSON Money.SomeExchangeRate where
  toJSON = \ser ->
    let r = Money.someExchangeRateRate ser
    in Ae.toJSON (MoneyI.someExchangeRateSrcCurrency' ser,
                  MoneyI.someExchangeRateDstCurrency' ser,
                  numerator r, denominator r)

-- | Compatible with 'Money.ExchangeRate'
--
-- Note: The JSON serialization changed in @safe-money@ version 0.4. However,
-- this instance is still able to cope with the previous format.
instance Ae.FromJSON Money.SomeExchangeRate where
  parseJSON = \v -> do
    (src, dst, n, d) <- Ae.parseJSON v <|> do
       -- Pre 0.4 format.
       ("ExchangeRate" :: T.Text, src, dst, n, d) <- Ae.parseJSON v
       pure (src, dst, n, d)
    when (d == 0) (fail "denominator is zero")
    maybe empty pure (MoneyI.mkSomeExchangeRate' src dst (n % d))