------------------------------------------------------------------------------
module Network.HTTP.Media.MediaType.Tests (tests) where

import qualified Data.ByteString.Char8                 as BS
import qualified Data.Map                              as Map

import           Control.Monad                         (join, liftM)
import           Data.ByteString                       (ByteString)
import           Data.CaseInsensitive                  (foldedCase)
import           Data.Monoid                           ((<>))
import           Data.String                           (fromString)
import           Test.Framework                        (Test, testGroup)
import           Test.Framework.Providers.QuickCheck2  (testProperty)
import           Test.QuickCheck                       (property, (.&&.), (===))
import           Test.QuickCheck.Gen                   (Gen)

import           Network.HTTP.Media.Accept
import           Network.HTTP.Media.Gen
import           Network.HTTP.Media.MediaType          ((/.), (/?))
import           Network.HTTP.Media.MediaType.Gen
import           Network.HTTP.Media.MediaType.Internal
import           Network.HTTP.Media.RenderHeader       (renderHeader)


------------------------------------------------------------------------------
tests :: [Test]
tests =
    [ testEq
    , testShow
    , testFromString
    , testHas
    , testGet
    , testMatches
    , testMoreSpecificThan
    , testParseAccept
    ]


------------------------------------------------------------------------------
-- Equality is derived, but we test it here to get 100% coverage.
testEq :: Test
testEq = testGroup "Eq"
    [ testProperty "==" $ do
        media <- genMediaType
        return $ media === media
    , testProperty "/=" $ do
        media  <- genMediaType
        media' <- genDiffMediaType media
        return $ media /= media'
    ]


------------------------------------------------------------------------------
testShow :: Test
testShow = testProperty "show" $ do
    media <- genMediaType
    return $ parseAccept (BS.pack $ show media) === Just media


------------------------------------------------------------------------------
testFromString :: Test
testFromString = testProperty "fromString" $ do
    media <- genMediaType
    return $ media === fromString (show media)


------------------------------------------------------------------------------
testHas :: Test
testHas = testGroup "(/?)"
    [ testProperty "True for property it has" $ do
        media <- genWithParams
        return $ all ((media /?) . foldedCase) (Map.keys $ parameters media)
    , testProperty "False for property it doesn't have" $ do
        media <- genWithParams
        return $ all (not . (stripParams media /?) . foldedCase)
            (Map.keys $ parameters media)
    ]


------------------------------------------------------------------------------
testGet :: Test
testGet = testGroup "(/.)"
    [ testProperty "Retrieves property it has" $ do
        media  <- genWithParams
        let is n v = (.&&. media /. foldedCase n === Just v)
        return $ Map.foldrWithKey is (property True) $ parameters media
    , testProperty "Nothing for property it doesn't have" $ do
        media <- genWithParams
        let is n _ = (.&&. stripParams media /. foldedCase n === Nothing)
        return $ Map.foldrWithKey is (property True) $ parameters media
    ]


------------------------------------------------------------------------------
testMatches :: Test
testMatches = testGroup "matches"
    [ testProperty "Equal values match" $ do
        media <- genMediaType
        return $ matches media media
    , testProperty "Same sub but different main don't match" $ do
        media <- genMaybeSubStar
        main  <- genDiffCIByteString $ mainType media
        return $ not (matches media media { mainType = main }) &&
            not (matches media { mainType = main } media)
    , testProperty "Same main but different sub don't match" $ do
        media <- genConcreteMediaType
        sub   <- genDiffCIByteString $ subType media
        return . not $ matches media media { subType = sub } ||
            matches media { subType = sub } media
    , testProperty "Different parameters don't match" $
        liftM (not . dotJoin matches stripParams) genWithParams
    , testProperty "Missing parameters match" $ do
        media <- genWithParams
        let media' = stripParams media
        return $ matches media media' && not (matches media' media)
    , testGroup "*/*"
        [ testProperty "Matches itself" $ matches anything anything
        , testProperty "Matches anything on the right" $
            liftM (`matches` anything) genMediaType
        , testProperty "Doesn't match more specific on the left" $
            liftM (not . matches anything) genMaybeSubStar
        ]
    , testGroup "type/*"
        [ testProperty "Matches itself" $ liftM (join matches) genSubStar
        , testProperty "Matches on the right" $
            liftM (dotJoin (flip matches) subStarOf) genConcreteMediaType
        , testProperty "Doesn't match on the left" $
            liftM (not . dotJoin matches subStarOf) genConcreteMediaType
        ]
    ]


------------------------------------------------------------------------------
testMoreSpecificThan :: Test
testMoreSpecificThan = testGroup "moreSpecificThan"
    [ testProperty "Against */*" $
        liftM (`moreSpecificThan` anything) genMaybeSubStar
    , testProperty "With */*" $
        liftM (not . moreSpecificThan anything) genMaybeSubStar
    , testProperty "Against type/*" $
        liftM (dotJoin (flip moreSpecificThan) subStarOf) genConcreteMediaType
    , testProperty "With type/*" $
        liftM (not . dotJoin moreSpecificThan subStarOf) genConcreteMediaType
    , testProperty "With parameters" $
        liftM (dotJoin (flip moreSpecificThan) stripParams) genWithParams
    , testProperty "Different types" $ do
        media  <- genWithoutParams
        media' <- genDiffMediaTypeWith genWithoutParams media
        return . not $
            moreSpecificThan media media' || moreSpecificThan media' media
    , testProperty "Different parameters" $ do
        media  <- genWithParams
        params <- genDiffParameters $ parameters media
        return . not $ moreSpecificThan media media { parameters = params }
    ]


------------------------------------------------------------------------------
testParseAccept :: Test
testParseAccept = testGroup "parseAccept"
    [ testProperty "Valid parse" $ do
        media <- genMediaType
        let main = mainType media
            sub  = subType media
        params <- renderParameters (parameters media)
        let parsed = parseAccept $ foldedCase (main <> "/" <> sub) <> params
        return $ parsed === Just media
    , testProperty "No sub" $ do
        bs <- genByteString
        return $ (parseAccept bs :: Maybe MediaType) === Nothing
    , testProperty "Empty main" $ do
        sep <- padString "/"
        bs  <- (sep <>) <$> genByteString
        return $ (parseAccept bs :: Maybe MediaType) === Nothing
    , testProperty "Empty sub" $ do
        sep <- padString "/"
        bs  <- (<> sep) <$> genByteString
        return $ (parseAccept bs :: Maybe MediaType) === Nothing
    , testProperty "Empty parameters" $ do
        sep <- padString ";"
        bs  <- renderHeader <$> genWithoutParams
        return $ (parseAccept (bs <> sep) :: Maybe MediaType) === Nothing
    , testProperty "No value" $
        (=== Nothing) <$> genMediaNameAndParams ""
    , testProperty "Empty value" $ do
        eq <- padString "="
        (=== Nothing) <$> genMediaNameAndParams eq
    ]

genMediaNameAndParams :: ByteString -> Gen (Maybe MediaType)
genMediaNameAndParams eq = do
    sep  <- padString ";"
    bs   <- renderHeader <$> genByteString
    name <- genByteString
    ps   <- genMaybeParameters >>= renderParameters
    return $ parseAccept (bs <> sep <> name <> eq <> sep <> ps)


------------------------------------------------------------------------------
-- | Like 'join', but applies the given function to the first argument.
dotJoin :: (a -> a -> b) -> (a -> a) -> a -> b
dotJoin f g a = f (g a) a
