{-# LANGUAGE TupleSections #-}

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

import Control.Monad (join, replicateM, (>=>))
import Data.Foldable (foldlM)
import Data.Function (on)
import Data.List (nubBy)
import Data.Map (empty)
import Data.Monoid ((<>))
import Data.Word (Word16)
import Network.HTTP.Media hiding
  ( parameters,
    subType,
  )
import Network.HTTP.Media.Gen (padString)
import Network.HTTP.Media.MediaType.Gen
import Network.HTTP.Media.MediaType.Internal
import Network.HTTP.Media.Quality
import Test.QuickCheck
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import Prelude hiding ((<>))

tests :: [TestTree]
tests =
  [ testParse,
    testMatchAccept,
    testMapAccept,
    testMatchContent,
    testMapContent,
    testMatchQuality,
    testMapQuality
  ]

testParse :: TestTree
testParse =
  testGroup
    "parseQuality"
    [ testProperty "Without quality" $ do
        media <- medias
        rendered <- padConcat (return . renderHeader) media
        return $ parseQuality rendered === Just (map maxQuality media),
      testProperty "With quality" $ do
        media <- qualities
        rendered <- padConcat padQuality media
        return $ parseQuality rendered === Just media,
      testProperty "With extensions" $ do
        media <- qualities
        rendered <- padConcat (padQuality >=> padExtensions) media
        return $ parseQuality rendered === Just media
    ]
  where
    medias = listOf1 genMediaType
    qualities = medias >>= mapM (flip fmap (choose (0, 1000)) . Quality)
    padConcat f l = flip (foldlM (padComma f)) (tail l) =<< f (head l)
    padComma f a b = pad a <$> padString "," <*> f b
    padQuality qMedia = do
      semi <- padString ";"
      let d = renderHeader (qualityData qMedia)
          v = showQ (qualityValue qMedia)
      return $ d <> semi <> "q=" <> v
    padExtensions s = genParameters >>= fmap (s <>) . renderParameters
    pad a s b = a <> s <> b

testMatchAccept :: TestTree
testMatchAccept = testMatch "Accept" matchAccept renderHeader

testMapAccept :: TestTree
testMapAccept = testMap "Accept" mapAccept renderHeader

testMatchContent :: TestTree
testMatchContent =
  testGroup
    "matchContent"
    [ testProperty "Matches" $ do
        media <- genMediaType
        return $ matchContent [media] (renderHeader media) === Just media,
      testProperty "Nothing" $ do
        content <- genMediaType
        parsers <- filter (not . matches content) <$> genServer
        return $ matchContent parsers (renderHeader content) === Nothing,
      testProperty "Against */*" $ do
        media <- genMediaType
        return $
          matchContent [anything] (renderHeader media) === Just anything,
      testProperty "Against type/*" $ do
        media <- genMediaType
        let sub = subStarOf media
        return $ matchContent [sub] (renderHeader media) === Just sub
    ]

testMapContent :: TestTree
testMapContent =
  testGroup
    "mapContent"
    [ testProperty "Matches" $ do
        media <- genMediaType
        return $ mapContent [(media, ())] (renderHeader media) === Just (),
      testProperty "Nothing" $ do
        content <- genMediaType
        parsers <- join zip . filter (not . matches content) <$> genServer
        return $ mapContent parsers (renderHeader content) === Nothing,
      testProperty "Overlapping keys" $ do
        (a, b) <- genMatchingPair
        return $ mapContent [(a, False), (b, True)] (renderHeader b)
    ]

testMatchQuality :: TestTree
testMatchQuality = testMatch "Quality" matchQuality id

testMapQuality :: TestTree
testMapQuality = testMap "Quality" mapQuality id

testMatch ::
  String ->
  ([MediaType] -> a -> Maybe MediaType) ->
  ([Quality MediaType] -> a) ->
  TestTree
testMatch name match qToI =
  testGroup
    ("match" ++ name)
    [ testProperty "Most specific" $ do
        media <- genConcreteMediaType
        let client =
              qToI $
                map
                  maxQuality
                  [ MediaType "*" "*" empty,
                    media {subType = "*"},
                    media {parameters = empty},
                    media
                  ]
        return $ match [media] client === Just media,
      testProperty "Nothing" $ do
        client <- listOf1 genConcreteMediaType
        server <- filter (not . flip any client . matches) <$> genServer
        return $ match server (qToI $ map maxQuality client) === Nothing,
      testProperty "Left biased" $ do
        server <- genNubServer
        let client = qToI $ map maxQuality server
        return $ match server client === Just (head server),
      testProperty "Against */*" $ do
        server <- genNubServer
        let stars = "*/*" :: MediaType
        return $
          match server (qToI [maxQuality stars])
            === Just (head server),
      testProperty "Against type/*" $ do
        server <- genNubServer
        let client = qToI [maxQuality (subStarOf $ head server)]
        return $ match server client === Just (head server),
      testQuality match qToI
    ]

testQuality ::
  ([MediaType] -> a -> Maybe MediaType) ->
  ([Quality MediaType] -> a) ->
  TestTree
testQuality match qToI =
  testGroup
    "Quality"
    [ testProperty "Highest quality" $ do
        server <- genServer
        qs <- replicateM (length server) $ choose (1, 1000)
        let client = zipWith Quality server qs
            qmax v q = if qualityValue q > qualityValue v then q else v
        return $
          match server (qToI client)
            === Just (qualityData $ foldr1 qmax client),
      testProperty "Most specific quality" $ do
        (a, b) <- genMatchingPair
        c <- genDiffMediaType a
        let client = qToI [quality a "0.5", maxQuality b, maxQuality c]
        return $ match [a, c] client === Just c,
      testQ0 match qToI
    ]

testQ0 ::
  ([MediaType] -> a -> Maybe MediaType) ->
  ([Quality MediaType] -> a) ->
  TestTree
testQ0 match qToI =
  testGroup
    "q=0"
    [ testProperty "Does not choose a q=0" $ do
        server <- genConcreteMediaType
        return $ match [server] (qToI [minQuality server]) === Nothing,
      testProperty "Does not choose any q=0" $ do
        server <- genServer
        return $ match server (qToI $ map minQuality server) === Nothing,
      testProperty "Does not choose q=0 with less specific type" $ do
        (a, b) <- genMatchingPair
        let client = qToI [minQuality a, maxQuality b]
        return $ match [a] client === Nothing,
      testProperty "Does choose type with q=0 on less specific type" $ do
        (a, b) <- genMatchingPair
        let client = qToI [minQuality b, maxQuality a]
        return $ match [a] client === Just a,
      testProperty "Does not choose q=0 when followed by same type" $ do
        server <- genConcreteMediaType
        let client = qToI [minQuality server, maxQuality server]
        return $ match [server] client === Nothing,
      testProperty "Does not choose q=0 when preceded by same type" $ do
        server <- genConcreteMediaType
        let client = qToI [maxQuality server, minQuality server]
        return $ match [server] client === Nothing
    ]

testMap ::
  String ->
  ([(MediaType, MediaType)] -> a -> Maybe MediaType) ->
  ([Quality MediaType] -> a) ->
  TestTree
testMap name mapf qToI =
  testGroup
    ("map" ++ name)
    [ testProperty "Matches" $ do
        server <- genServer
        qs <- replicateM (length server) $ choose (1, 1000 :: Word16)
        let client = zipWith Quality server qs
            qmax q v = if qualityValue q >= qualityValue v then q else v
            zipped = zip server server
        return $
          mapf zipped (qToI client)
            === Just (qualityData $ foldr1 qmax client),
      testProperty "Nothing" $ do
        (server, client) <- genServerAndClient
        let zipped = map (,"*/*") server
        return $ mapf zipped (qToI $ map maxQuality client) === Nothing
    ]

genServer :: Gen [MediaType]
genServer = listOf1 genConcreteMediaType

genNubServer :: Gen [MediaType]
genNubServer = nubBy (on (==) stripParams) <$> genServer

genServerAndClient :: Gen ([MediaType], [MediaType])
genServerAndClient = do
  server <- genServer
  client <-
    filter (not . flip any server . flip matches)
      <$> listOf1 (genDiffMediaTypesWith genConcreteMediaType server)
  return (server, client)
