{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

module Data.Sv.EncodeTest (test_Encode) where

import Control.Lens (makeClassyPrisms)
import Data.Functor.Contravariant
import Data.Functor.Contravariant.Divisible
import Data.Semigroup ((<>))
import Data.Text (Text)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))

import Data.Sv
import qualified Data.Sv.Encode as E

data IntOrString =
  I Int | S String
  deriving (Eq, Ord, Show)

makeClassyPrisms ''IntOrString

data IntAndString = IAS { getInt :: Int, getString :: String }

test_Encode :: TestTree
test_Encode =
  testGroup "Encode" [
    divisibleTests
  , decidableTests
  , encodeTests
  , escapeTests
  , encodeNamedTests
  ]

intAndString :: Encode IntAndString
intAndString = contramap getInt E.int <> contramap getString E.string


opts :: EncodeOptions
opts = defaultEncodeOptions

intOrString :: Encode IntOrString
intOrString = E.encodeOf _I E.int <> E.encodeOf _S E.string

i :: IntOrString
i = I 5

s :: IntOrString
s = S "hello"

decidableTests :: TestTree
decidableTests =
  testGroup "decidable" [
    testCase "encode an Int" $
      encodeRow intOrString opts i @?= "5"
  , testCase "encode a String" $
      encodeRow intOrString opts s @?= "hello"
  ]

intEmptyAndString :: Encode IntAndString
intEmptyAndString = contramap getInt E.int <> E.empty <> contramap getString E.string

ias :: IntAndString
ias = IAS 10 "goodbye"

divisibleTests :: TestTree
divisibleTests =
  testGroup "divisible" [
    testCase "encode an IntAndString" $
      encodeRow intAndString opts ias @?= "10,goodbye"
  , testCase "encode an IntAndString with an empty between" $
      encodeRow intEmptyAndString opts ias @?= "10,,goodbye"
  ]

encodeTests :: TestTree
encodeTests =
  testCase "multiple lines" $
    encode (divided intAndString intOrString) opts [(IAS 3 "book", I 4), (IAS 7 "film", S "ok")]
      @?= "3,book,4\n7,film,ok"

escapeTests :: TestTree
escapeTests =
  testGroup "escape" [
    testCase "string" $
      encodeRow E.string opts "hello \"test\" case" @?= "\"hello \"\"test\"\" case\""
  , testCase "text" $
      encodeRow E.text opts "this is also\"a test\"" @?= "\"this is also\"\"a test\"\"\""
  , testCase "bytestring - strict" $
      encodeRow E.byteString opts "\"test\"ing\" " @?= "\"\"\"test\"\"ing\"\" \""
  , testCase "bytestring - lazy" $
      encodeRow E.lazyByteString opts "\"" @?= "\"\"\"\""
  ]

data Three = Three {
  int :: Int
, double :: Double
, text :: Text
} deriving (Eq, Ord, Show)

three :: NameEncode Three
three =
  E.named "first" (contramap int E.int)
    <> E.named "\"Second\"" (contramap double E.doubleFast)
    <> E.named "third" (contramap text E.text)

myInt :: NameEncode Int
myInt = E.named "my int" E.int

encodeNamedTests :: TestTree
encodeNamedTests = testGroup "named" [
    testCase "empty decoder" $
      encodeNamed mempty opts [] @?= ""
  , testCase "single column, zero rows" $
      encodeNamed myInt opts [] @?= "my int"
  , testCase "single column, one row" $
      encodeNamed myInt opts [5] @?= "my int\n5"
  , testCase "single column, many rows" $
      encodeNamed myInt opts [1..5] @?= "my int\n1\n2\n3\n4\n5"
  , testCase "multiple columns, zero rows" $
      encodeNamed three opts [] @?= "first,\"\"\"Second\"\"\",third"
  , testCase "multiple columns, one row" $
      encodeNamed three opts [Three 1 2 "th\"ree"]
        @?= "first,\"\"\"Second\"\"\",third\n1,2,\"th\"\"ree\""
  , testCase "multiple columns, multiple rows" $
      encodeNamed three opts [Three 1 2 "three", Three 4 5 "SIX"]
        @?= "first,\"\"\"Second\"\"\",third\n1,2,three\n4,5,SIX"
  ]