content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RankNTypes #-}
  
  module Data.Sv.RoundTripsDecodeEncode (test_Roundtrips) where
  
  import Control.Lens ((&), (.~))
  import Data.ByteString (ByteString)
  import qualified Data.ByteString.Lazy as LBS
  import qualified Data.ByteString.UTF8 as UTF8
  import Data.Semigroup ((<>))
  import Data.String (IsString)
  import Test.Tasty (TestName, TestTree, testGroup)
  import Test.Tasty.HUnit ((@?=), testCase)
  
  import Data.Sv
  import qualified Data.Sv.Decode as D
  import qualified Data.Sv.Encode as E
  
  test_Roundtrips :: TestTree
  test_Roundtrips =
    testGroup "Round trips" [
      testGroup "decode/encode isomorphisms" [
        bool
      , char
      , int
      , integer
      , float
      , double
      , doubleFast
      , readDouble
      , string
      , byteString
      , lazyByteString
      , text
      ]
    , testGroup "decode/encode normalising" [
        boolSI
      , floatSI
      , doubleSI
      ]
  
    ]
  
  encOpts :: EncodeOptions
  encOpts = defaultEncodeOptions & quoting .~ Never
  
  parOpts :: ParseOptions
  parOpts = defaultParseOptions & headedness .~ Unheaded
  
  lbUtf8 :: LBS.ByteString -> String
  lbUtf8 = UTF8.toString . LBS.toStrict
  
  utf8lb :: String -> LBS.ByteString
  utf8lb = LBS.fromStrict . UTF8.fromString
  
  -- Round-trips an encode/decode pair. This version checks whether the pair
  -- form an isomorphism
  roundTripCodecIso :: (Eq a, Show a) => TestName -> Decode' ByteString a -> Encode a -> [(LBS.ByteString, a)] -> TestTree
  roundTripCodecIso name dec enc bsas = testGroup name . flip foldMap bsas $ \(bs,a) ->
    [ testCase (lbUtf8 bs <> ": encode . decode") $
        Success bs @?= (encode enc encOpts <$> parseDecode dec parOpts bs)
    , testCase (lbUtf8 bs <> ": decode . encode") $
        Success [a] @?= (parseDecode dec parOpts $ encodeRow enc encOpts a)
    ]
  
  -- Round-trips an encode/decode pair. This version checks whether the pair
  -- form a split-idempotent. That is to say, one direction is identity, the other is
  -- idempotent.
  roundTripCodecSplitIdempotent :: (Eq a, Show a) => TestName -> Decode' ByteString a -> Encode a -> [(LBS.ByteString, a)] -> TestTree
  roundTripCodecSplitIdempotent name dec enc bsas =
      let deco = parseDecode dec parOpts
          enco = encode enc encOpts
          encdec = fmap enco . deco
      in  testGroup name . flip foldMap bsas $ \(bs,a) ->
        [ testCase (lbUtf8 bs <> ": decode . encode . decode") $
            Success (Success [a]) @?= (deco <$> encdec bs)
        , testCase (lbUtf8 bs <> ": decode . encode") $
            Success [a] @?= (parseDecode dec parOpts $ enco [a])
        ]
  
  byteString :: TestTree
  byteString = roundTripCodecIso "bytestring" D.contents E.byteString
    [ ("hello","hello")]
  
  lazyByteString :: TestTree
  lazyByteString = roundTripCodecIso "lazy bytestring" D.lazyByteString E.lazyByteString [("hello","hello")]
  
  bool :: TestTree
  bool = roundTripCodecIso "bool" D.boolean E.booltruefalse [("true", True), ("false", False)]
  
  char :: TestTree
  char = roundTripCodecIso "char" D.char E.char [(utf8lb "c", 'c'), (utf8lb "💩", '💩')]
  
  string :: TestTree
  string = roundTripCodecIso "string" D.string E.string [(utf8lb "hello", "hello"), (utf8lb "💩💩💩💩", "💩💩💩💩")]
  
  int :: TestTree
  int = roundTripCodecIso "int" D.int E.int [("5", 5)]
  
  integer :: TestTree
  integer = roundTripCodecIso "integer" D.integer E.integer
    [ ("5", 5)
    , ("1000000", 1000000)
    ]
  
  floatingTests :: (IsString s, Fractional a) => [(s,a)]
  floatingTests =
    [ ("5.0", 5)
    , ("10.5", 10.5)
    , ("12345.678", 12345.678)
    ]
  
  float :: TestTree
  float = roundTripCodecIso "float" D.float E.float
    floatingTests
  
  doubleTests :: (IsString s, Fractional a) => [(s,a)]
  doubleTests = ("7.845860130857695", 7.845860130857695) : floatingTests
  
  double :: TestTree
  double = roundTripCodecIso "double" D.double E.double
    ( ("1.0000000000034547e-2", 1.0000000000034547e-2)
    : doubleTests
    )
  
  doubleFast :: TestTree
  doubleFast = roundTripCodecIso "doubleFast" D.double E.doubleFast
    [ ("5", 5)
    , ("10.5", 10.5)
    , ("12345.678", 12345.678)
    , ("7.845860130857695", 7.845860130857695)
    , ("0.010000000000034547", 1.0000000000034547e-2)
    ]
  
  readDouble :: TestTree
  readDouble = roundTripCodecIso "read double" D.read (E.show :: Encode Double)
    ( ("1.0000000000034547e-2", 1.0000000000034547e-2)
    : doubleTests
    )
  
  text :: TestTree
  text = roundTripCodecIso "text" D.utf8 E.text [(utf8lb "hello", "hello"), (utf8lb "💩💩💩💩", "💩💩💩💩")]
  
  boolSI :: TestTree
  boolSI = roundTripCodecSplitIdempotent "bool" D.boolean E.bool10
    [ ("1", True)
    , ("0", False)
    ]
  
  floatSI :: TestTree
  floatSI = roundTripCodecSplitIdempotent "float" D.float E.float
    [ ("5", 5)
    ]
  
  doubleSI :: TestTree
  doubleSI = roundTripCodecSplitIdempotent "double" D.double E.double
    [ ("5", 5)
    ]
  