module Spec where

import           Data.Functor.Identity (Identity (Identity))
import           Data.Rec.SmallArray   (Rec, invariant, pattern (:++:), pattern (:~:), (/++/), (/~/), (<#>))
import qualified Data.Rec.SmallArray   as Rec
import           Data.Typeable         (cast)
import           Test.Hspec

type I = Identity
i :: a -> Identity a
i = Identity

spec :: Spec
spec = describe "Rec (SmallArray)" $ parallel do
  it "is Typeable" do
    let
      x = i (5 :: Int) :~: i False :~: Rec.empty
      y = cast x :: Maybe (Rec I '[Int, String])
      z = cast x :: Maybe (Rec I '[Int, Bool])
    y `shouldBe` Nothing
    z `shouldBe` Just x

  it "is Read & Show" do
    let
      s = "Identity 5 :~: Identity False :~: Identity 'X' :~: Identity (Just 'O') :~: empty"
      s' = "Identity 5 :~: Identity False :~: Identity 'X' :~: (Identity (Just 'O') :~: (empty))"
      x = invariant $ read s :: Rec Identity '[Int, Bool, Char, Maybe Char]
      x' = invariant $ read s' :: Rec Identity '[Int, Bool, Char, Maybe Char]
    show x `shouldBe` s
    show x' `shouldBe` s

  it "is Eq" do
    let
      x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
      y = invariant $ id <#> x
      z = invariant $ read "Identity 5 :~: Identity False :~: Identity 'X' :~: Identity (Just 'O') :~: empty"
        :: Rec Identity '[Int, Bool, Char, Maybe Char]
    x `shouldBe` y
    y `shouldBe` z

  it "can be constructed with 'empty', 'singleton', 'cons', 'concat'" do
    let
      x = invariant $ i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
      y = invariant $ Rec.singleton (i (5 :: Int)) :++: Rec.singleton (i False)
        :++: Rec.singleton (i 'X') :++: Rec.singleton (i (Just 'O'))
      a = invariant $ i (5 :: Int) :~: Rec.singleton (i False)
      b = invariant $  Rec.singleton (i 'X') :++: Rec.singleton (i (Just 'O'))
    x `shouldBe` y
    invariant (a :++: b) `shouldBe` x

  it "can contain multiple fields of the same type" do
    let
      x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
      y = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: i (6 :: Int) :~: i (Just 'A') :~: Rec.empty
    invariant (x :++: 6 :~: i (Just 'A') :~: Rec.empty) `shouldBe` y

  it "can be destructed via 'head', 'tail', 'take', 'drop'" do
    let
      a = (x :~: y) :++: Rec.singleton z
      x = i (5 :: Int)
      y = i (Rec.singleton $ i False) :~: i 'X' :~: Rec.empty
      z = i (Just 'O')
    Rec.head a `shouldBe` x
    invariant (Rec.drop @'[Int, Rec I '[Bool], Char] a) `shouldBe` Rec.singleton z
    invariant (Rec.tail a) `shouldBe` invariant (y :++: Rec.singleton z)
    invariant (Rec.take @'[Int, Rec I '[Bool], Char] a) `shouldBe` (x :~: y)

  it "can get elements via 'index'" do
    let x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    Rec.index @Int x `shouldBe` 5
    Rec.index @Bool x `shouldBe` i False
    Rec.index @Char x `shouldBe` i 'X'
    Rec.index @(Maybe Char) x `shouldBe` i (Just 'O')

  it "can get the topmost element among the duplicate ones" do
    let y = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: i (6 :: Int) :~: i (Just 'A') :~: Rec.empty
    Rec.index @Int y `shouldBe` 5
    Rec.index @Bool y `shouldBe` i False
    Rec.index @Char y `shouldBe` i 'X'
    Rec.index @(Maybe Char) y `shouldBe` i (Just 'O')

  it "can set elements via 'update'" do
    let x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant (Rec.update @Int 6 x) `shouldBe` 6 :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant (i True /~/ x) `shouldBe` 5 :~: i True :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant (i 'O' /~/ x) `shouldBe` 5 :~: i False :~: i 'O' :~: i (Just 'O') :~: Rec.empty
    invariant (i (Just 'P') /~/ x) `shouldBe` 5 :~: i False :~: i 'X' :~: i (Just 'P') :~: Rec.empty

  it "can get multiple elements via 'pick'" do
    let x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant (Rec.pick @'[Int, Maybe Char] x) `shouldBe` 5 :~: i (Just 'O') :~: Rec.empty

  it "can reorder elements via 'pick'" do
    let x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant (Rec.pick @'[Bool, Int, Maybe Char] x) `shouldBe` i False :~: 5 :~: i (Just 'O') :~: Rec.empty

  it "can set multiple fields via 'batch'" do
    let x = i (5 :: Int) :~: i False :~: i 'X' :~: i (Just 'O') :~: Rec.empty
    invariant ((i (6 :: Int) :~: i (Just 'X') :~: Rec.empty) /++/ x)
      `shouldBe` 6 :~: i False :~: i 'X' :~: i (Just 'X') :~: Rec.empty
