content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeApplications #-}
  {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  module Data.PercentageSpec
      ( spec
      ) where
  
  import Prelude hiding
      ( fromRational
      , toRational
      )
  
  import Cardano.Wallet.Unsafe
      ( unsafeMkPercentage
      )
  import Control.Monad
      ( forM_
      )
  import Data.Either
      ( isRight
      )
  import Data.Percentage
      ( Percentage (..)
      , PercentageError (..)
      , fromRational
      , toDouble
      , toRational
      )
  import Data.Proxy
      ( Proxy (..)
      )
  import Data.Quantity
      ( Quantity
      )
  import Data.Ratio
      ( (%)
      )
  import Data.Text.Class
      ( FromText (..)
      , TextDecodingError (..)
      , ToText (..)
      )
  import Test.Hspec
      ( Spec
      , describe
      , it
      , shouldBe
      , shouldSatisfy
      )
  import Test.QuickCheck
      ( Arbitrary (..)
      , checkCoverage
      , choose
      , cover
      , property
      , (===)
      )
  import Test.Text.Roundtrip
      ( textRoundtrip
      )
  
  import qualified Data.Aeson as Aeson
  import qualified Data.ByteString.Char8 as B8
  import qualified Data.ByteString.Lazy as BL
  import qualified Data.Text as T
  
  spec :: Spec
  spec = do
      textRoundtrip (Proxy @Percentage)
  
      describe "fromText failures" $ do
          let err = TextDecodingError
                  "expected a value between 0 and 100 with a '%' suffix \
                  \(e.g. '14%')"
          let tests =
                  [ "14..2%"
                  , "42%%"
                  , "%"
                  , "-65%"
                  , "42"
                  , "14.0"
                  , "101%"
                  , "100% "
                  ]
          forM_ tests $ \test -> it (T.unpack test) $ do
              fromText @Percentage test `shouldBe` Left err
  
      describe "ToText & ToJSON goldens (checking rounding)" $ do
          let tests =
                  [ (unsafeMkPercentage $ 1 % 3, "33.33%", "33.33")
                  , (unsafeMkPercentage 1, "100.00%", "100")
                  , (unsafeMkPercentage 0, "0.00%", "0")
                  , (unsafeMkPercentage 0.0001, "0.01%", "1.0e-2")
                  , (unsafeMkPercentage 0.00009, "0.01%", "1.0e-2")
                  , (unsafeMkPercentage 0.00006, "0.01%", "1.0e-2")
                  , (unsafeMkPercentage 0.00005, "0.00%", "0")
                  , (unsafeMkPercentage 0.00004, "0.00%", "0")
                  ]
  
          forM_ tests $ \(p,txt,json) -> do
              let desc = mconcat
                      [ show (toDouble p)
                      , " -> toText: "
                      , T.unpack txt
                      , ", toJSON: "
                      , B8.unpack . BL.toStrict $ json
                      ]
              it desc $ do
                  toText p `shouldBe` txt
                  Aeson.encode p `shouldBe` json
  
      it "fail to percent from JSON when out of bounds" $ do
          let msg = "Error in $.quantity: PercentageOutOfBoundsError"
          Aeson.eitherDecode "{\"unit\":\"percent\",\"quantity\":-14}"
              `shouldBe` (Left @String @(Quantity "percent" Percentage) msg)
  
      it "can't make percentage out of bound" $ checkCoverage $ \case
          n | n > 10 -> cover 25 True "n > 100" $
              -- NOTE taking 10 * n to get bigger Int generated by QuickCheck
              fromRational (n / 10) === Left PercentageOutOfBoundsError
          n | n < 0 -> cover 25 True "n < 0" $
              fromRational n === Left PercentageOutOfBoundsError
          n ->
              case fromRational (n / 10) of
                  Left _  -> property False
                  Right _ -> property True
  
      it "fromRational $ toRational maxBound succeeds" $ do
          fromRational (toRational minBound) `shouldSatisfy` isRight
  
      it "fromRational $ toRational minBound succeeds" $ do
          fromRational (toRational minBound) `shouldSatisfy` isRight
  
  --------------------------------------------------------------------------------
  -- Arbitrary instances
  --------------------------------------------------------------------------------
  
  instance Arbitrary Percentage where
      shrink _ = []
      -- Note: We are only generating values that pass roundtrips here!
      --
      -- 0.01% is the precision we use for presentation.
      arbitrary = unsafeMkPercentage . (% 10000) <$> choose (0, 10000)
  