{-# LANGUAGE TypeFamilies #-}

module Control.Applicative.PermutationsSpec (spec) where

import Control.Applicative.Permutations
import Control.Monad
import Data.List
import Data.Void
import Test.Hspec
import Test.Hspec.Megaparsec
import Test.Hspec.Megaparsec.AdHoc
import Test.QuickCheck
import Text.Megaparsec
import Text.Megaparsec.Char

spec :: Spec
spec = do
  describe "runPermutation & Permutation" $ do
    describe "Functor instance" $ do
      it "obeys identity law" $
        property $ \n ->
          prsp (fmap id (pure (n :: Int))) ""
            === prsp (id (pure n)) ""
      it "obeys composition law" $
        property $ \n m t ->
          let f = (+ m)
              g = (* t)
           in prs (fmap (f . g) (pure (n :: Int))) ""
                === prs ((fmap f . fmap g) (pure n)) ""
    describe "Applicative instance" $ do
      it "obeys identity law" $
        property $ \n ->
          prsp (pure id <*> pure (n :: Int)) ""
            === prsp (pure n) ""
      it "obeys composition law" $
        property $ \n m t ->
          let u = pure (+ m)
              v = pure (* t)
              w = pure (n :: Int)
           in prsp (pure (.) <*> u <*> v <*> w) ""
                === prsp (u <*> (v <*> w)) ""
      it "obeys homomorphism law" $
        property $ \x m ->
          let f = (+ m)
           in prsp (pure f <*> pure (x :: Int)) ""
                === prsp (pure (f x)) ""
      it "obeys interchange law" $
        property $ \n y ->
          let u = pure (+ n)
           in prsp (u <*> pure (y :: Int)) ""
                === prsp (pure ($ y) <*> u) ""
  describe "toPermutation" $
    it "works" $
      property $ \xs s' -> forAll (shuffle xs) $ \ys -> do
        let s = ys ++ s'
            p = foldr f (pure []) xs
            f x p' = (:) <$> toPermutation (char x) <*> p'
        prsp p s `shouldParse` xs
        prsp' p s `succeedsLeaving` s'
  describe "intercalateEffect" $
    it "works" $
      property $ \e xs s' ->
        let preconditions f =
              foldr1
                (.||.)
                [ -- Zero permutation targets will cause problems
                  property $ null xs,
                  -- If the effect is the prefix of the suffix, it will fail
                  not (null s') .&&. e == head s',
                  f
                ]
         in preconditions $
              forAll (intersperse e <$> shuffle xs) $ \ys -> do
                let s = ys ++ s'
                    p = intercalateEffect (char e) $ foldr f (pure []) xs
                    f x p' = (:) <$> toPermutation (char x) <*> p'
                prs p s `shouldParse` xs
                prs' p s `succeedsLeaving` s'
  describe "toPermutationWithDefault" $ do
    let testCases =
          [ ("abc", "abc", ""),
            ("acb", "abc", ""),
            ("bac", "abc", ""),
            ("bca", "abc", ""),
            ("cab", "abc", ""),
            ("cba", "abc", ""),
            ("aab", "ayz", "ab"),
            ("aba", "abz", "a"),
            ("baa", "abz", "a"),
            ("bba", "xbz", "ba"),
            ("bab", "abz", "b"),
            ("abb", "abz", "b"),
            ("cca", "xyc", "ca"),
            ("cac", "ayc", "c"),
            ("acc", "ayc", "c"),
            ("aaa", "ayz", "aa"),
            ("bbb", "xbz", "bb"),
            ("ccc", "xyc", "cc"),
            ("q", "xyz", "q"),
            ("", "xyz", "")
          ]
    forM_ testCases $ \(i, o, r) ->
      it ("parses \"" ++ i ++ "\" as \"" ++ o ++ "\" leaving \"" ++ r ++ "\"") $ do
        prsp testPermParser i `shouldParse` o
        prsp' testPermParser i `succeedsLeaving` r
  describe "intercalateEffect (with default)" $ do
    let p = intercalateEffect (char ',') testPermParser
        testCases =
          [ ("a,b,c", "abc", ""),
            ("a,c,b", "abc", ""),
            ("b,a,c", "abc", ""),
            ("b,c,a", "abc", ""),
            ("c,a,b", "abc", ""),
            ("c,b,a", "abc", ""),
            ("aab", "ayz", "ab"),
            ("a,ba", "abz", "a"),
            ("b,aa", "abz", "a"),
            ("bba", "xbz", "ba"),
            ("b,ab", "abz", "b"),
            ("a,bb", "abz", "b"),
            ("cca", "xyc", "ca"),
            ("c,ac", "ayc", "c"),
            ("a,cc", "ayc", "c"),
            ("aaa", "ayz", "aa"),
            ("bbb", "xbz", "bb"),
            ("ccc", "xyc", "cc"),
            ("!", "xyz", "!"),
            (",", "xyz", ","),
            ("", "xyz", "")
          ]
    forM_ testCases $ \(i, o, r) ->
      it ("parses \"" ++ i ++ "\" as \"" ++ o ++ "\" leaving \"" ++ r ++ "\"") $ do
        prs p i `shouldParse` o
        prs' p i `succeedsLeaving` r

prsp ::
  Permutation Parser a ->
  String ->
  Either (ParseErrorBundle String Void) a
prsp p = prs (runPermutation p)

prsp' ::
  Permutation Parser a ->
  String ->
  (State String Void, Either (ParseErrorBundle String Void) a)
prsp' p = prs' (runPermutation p)

testPermParser :: Permutation Parser String
testPermParser =
  f <$> toPermutationWithDefault 'x' (char 'a')
    <*> toPermutationWithDefault 'y' (char 'b')
    <*> toPermutationWithDefault 'z' (char 'c')
  where
    f a b c = [a, b, c]
