module Tests.Extra.Semigroup.Permutation (tests) where

import AtCoder.Extra.Semigroup.Permutation qualified as P
import Control.Exception (evaluate)
import Data.Vector.Unboxed qualified as VU
import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec
import Test.QuickCheck.Classes qualified as QCC
import Test.Tasty
import Test.Tasty.Hspec
import Test.Tasty.QuickCheck as QC
import Tests.Util (laws)

spec_invalid :: IO TestTree
spec_invalid = testSpec "boundary check" $ do
  let !_ = P.new $ VU.fromList [0]
  let !_ = P.new $ VU.fromList [-1] -- -1 is allowed
  it "throws error 1" $ do
    evaluate (P.new (VU.fromList [1])) `shouldThrow` anyException
  it "throws error 2" $ do
    evaluate (P.new (VU.fromList [-2])) `shouldThrow` anyException

prop_ident :: P.Permutation -> QC.Property
prop_ident p =
  QC.conjoin
    [ p <> ident QC.=== p,
      ident <> p QC.=== p
    ]
  where
    ident = P.ident (P.length p)

prop_zero :: P.Permutation -> QC.Property
prop_zero p =
  QC.conjoin
    [ p <> zero QC.=== zero,
      zero <> p QC.=== zero
    ]
  where
    zero = P.zero (P.length p)

prop_identAct :: QC.Positive Int -> QC.Gen QC.Property
prop_identAct (QC.Positive len) = do
  let p = P.ident len
  i <- QC.chooseInt (0, len - 1)
  pure $ P.act p i QC.=== i

prop_zeroAct :: QC.Positive Int -> QC.Gen QC.Property
prop_zeroAct (QC.Positive len) = do
  let p = P.zero len
  i <- QC.chooseInt (0, len - 1)
  pure $ P.act p i QC.=== i

-- orphan instance
instance QC.Arbitrary P.Permutation where
  arbitrary = do
    let n = 33
    vec <- VU.fromList <$> QC.vectorOf n (QC.chooseInt (-1, n - 1))
    pure $ P.new vec

tests :: [TestTree]
tests =
  [ unsafePerformIO spec_invalid,
    QC.testProperty "ident" prop_ident,
    QC.testProperty "zero" prop_zero,
    QC.testProperty "identAct" prop_identAct,
    QC.testProperty "zeroAct" prop_zeroAct,
    laws
      @P.Permutation
      [ QCC.semigroupLaws
      ]
  ]