{-# LANGUAGE OverloadedStrings #-}
module B9.Content.ErlangPropListSpec (spec) where

import Control.Applicative
import Data.List
import Test.Hspec
import Test.QuickCheck
import Data.Semigroup
import Data.Text ()

import B9.Content.ErlTerms
import B9.Content.ErlangPropList
import B9.Content.AST

spec :: Spec
spec = do

  describe "ErlangPropList" $ do

    it "implements ConcatableSyntax method decodeSyntax" $
       let v = decodeSyntax "" "ok."
       in v `shouldBe` Right (ErlangPropList (ErlAtom "ok"))

    it "implements ConcatableSyntax method encodeSyntax" $
       let v = encodeSyntax (ErlangPropList (ErlAtom "ok"))
       in v `shouldBe` "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) = decodeSyntax "" "[a,b,c]." :: Either String ErlangPropList
           (Right p) = decodeSyntax "" "{ok,value}."
           (Right combined) = decodeSyntax "" "[a,b,c,{ok,value}]."
       in l <> p `shouldBe` combined

    it "combines a primitve and a list by extending the list" $
       let (Right l) = decodeSyntax "" "[a,b,c]." :: Either String ErlangPropList
           (Right p) = decodeSyntax "" "{ok,value}."
           (Right combined) = decodeSyntax "" "[{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]
