{-# LANGUAGE OverloadedStrings #-}

module B9.Content.ErlangPropListSpec
  ( spec,
  )
where

import B9.Artifact.Content.ErlTerms
import B9.Artifact.Content.ErlangPropList
import B9.Text
import Data.List
import Data.Text ()
import Test.Hspec
import Test.QuickCheck

spec :: Spec
spec = describe "ErlangPropList" $ do
  it "parseFromText" $
    let v = parseFromText "ok."
     in v `shouldBe` Right (ErlangPropList (ErlAtom "ok"))
  it "renterToText" $
    let v = renderToText (ErlangPropList (ErlAtom "ok"))
     in v `shouldBe` Right "ok."
  it "combines primitives by putting them in a list" $
    let p1 = ErlangPropList (ErlList [ErlAtom "a"])
        p2 = ErlangPropList (ErlList [ErlNatural 123])
        combined = ErlangPropList (ErlList [ErlAtom "a", ErlNatural 123])
     in (p1 <> p2) `shouldBe` combined
  it "combines a list and a primitve by extending the list" $
    let (Right l) =
          parseFromText "[a,b,c]." :: Either String ErlangPropList
        (Right p) = parseFromText "{ok,value}."
        (Right combined) = parseFromText "[a,b,c,{ok,value}]."
     in l <> p `shouldBe` combined
  it "combines a primitve and a list by extending the list" $
    let (Right l) =
          parseFromText "[a,b,c]." :: Either String ErlangPropList
        (Right p) = parseFromText "{ok,value}."
        (Right combined) = parseFromText "[{ok,value},a,b,c]."
     in p <> l `shouldBe` combined
  it
    "merges lists with distinct elements to lists containing the elements of both lists"
    $ let p1 =
            ErlangPropList
              (ErlList [ErlTuple [ErlAtom "k_p1", ErlList [ErlNatural 1]]])
          p2 =
            ErlangPropList
              (ErlList [ErlTuple [ErlAtom "k_p2", ErlList [ErlNatural 1]]])
          expected =
            ErlangPropList
              ( ErlList
                  [ ErlTuple [ErlAtom "k_p1", ErlList [ErlNatural 1]],
                    ErlTuple [ErlAtom "k_p2", ErlList [ErlNatural 1]]
                  ]
              )
       in p1 <> p2 `shouldBe` expected
  it
    "merges two property lists into a prop list that has the lenght of the left + the right proplist - the number of entries sharing the same key"
    (property mergedPropListsHaveCorrectLength)

data ErlPropListTestData
  = ErlPropListTestData
      { plistLeft :: [SimpleErlangTerm],
        plistRight :: [SimpleErlangTerm],
        commonKeys :: [SimpleErlangTerm]
      }
  deriving (Eq, Ord, Show)

mergedPropListsHaveCorrectLength :: ErlPropListTestData -> Bool
mergedPropListsHaveCorrectLength (ErlPropListTestData l r common) =
  let (ErlangPropList (ErlList merged)) =
        ErlangPropList (ErlList l) <> ErlangPropList (ErlList r)
      expectedLen = length l + length r - length common
   in length merged == expectedLen

instance Arbitrary ErlPropListTestData where
  arbitrary = do
    someKeys <- nub <$> listOf arbitraryPlistKey
    numLeftOnly <- choose (0, length someKeys - 1)
    let keysLeftOnly = take numLeftOnly someKeys
    numCommon <- choose (0, length someKeys - numLeftOnly - 1)
    let keysCommon = take numCommon (drop numLeftOnly someKeys)
    let keysRightOnly = drop (numLeftOnly + numCommon) someKeys
    let numRightOnly = length someKeys - numLeftOnly - numCommon
    valuesLeft <- vectorOf (numLeftOnly + numCommon) arbitraryPlistValue
    valuesRight <- vectorOf (numRightOnly + numCommon) arbitraryPlistValue
    return
      ErlPropListTestData
        { plistLeft = zipWith toPair (keysLeftOnly <> keysCommon) valuesLeft,
          plistRight = zipWith toPair (keysRightOnly <> keysCommon) valuesRight,
          commonKeys = keysCommon
        }
    where
      toPair a b = ErlTuple [a, b]
      arbitraryPlist = ErlList <$> listOf arbitraryPlistEntry
      arbitraryPlistEntry = toPair <$> arbitraryPlistKey <*> arbitraryPlistValue
      arbitraryPlistKey = arbitraryErlSimpleAtom
      arbitraryPlistValue =
        oneof [arbitraryLiteral, arbitraryList, arbitraryPlist, arbitraryTuple]
      arbitraryTuple = ErlTuple <$> listOf arbitraryPlistValue
      arbitraryList = ErlList <$> listOf arbitraryPlistValue
      arbitraryLiteral =
        oneof [arbitraryPlistKey, arbitraryErlString, arbitraryErlNumber]
