{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

------------------------------------------------------------------------------
module Snap.Internal.Parsing.Tests ( tests ) where
------------------------------------------------------------------------------
import           Control.Applicative              (many)
import           Data.Attoparsec.ByteString.Char8 (IResult (..), char, string, (<?>))
import qualified Data.ByteString.Char8            as S (concat)
import qualified Data.Map                         as Map (fromList)
import           Data.Word                        (Word8)
import           Snap.Internal.Http.Types         (Cookie (Cookie, cookieDomain, cookieExpires, cookieHttpOnly, cookieName, cookiePath, cookieSecure, cookieValue))
import           Snap.Internal.Parsing            (crlf, finish, fullyParse, fullyParse', pAvPairs, pHeaders, pQuotedString, parseCookie, parseToCompletion, parseUrlEncoded, unsafeFromHex, unsafeFromNat)
import           Snap.Test.Common                 (expectExceptionH)
import           System.Random                    (Random (random, randomR))
import           Test.Framework                   (Test)
import           Test.Framework.Providers.HUnit   (testCase)
import           Test.HUnit                       (assertEqual)
import           Test.QuickCheck                  (Arbitrary (arbitrary), choose)
------------------------------------------------------------------------------

tests :: [Test]
tests = [ testAvPairs
        , testCookie
        , testHeaderParse
        , testQuotedString
        , testUnsafeFromHex
        , testUnsafeFromInt
        , testUrlEncoded
        , testFailParse
        ]


------------------------------------------------------------------------------
testAvPairs :: Test
testAvPairs = testCase "parsing/avpairs" $ do
    let x = fullyParse txt pAvPairs
    assertEqual "avpairs" (Right [ ("foo" , "bar")
                                 , ("bar" , "baz")
                                 , ("quux", ""   )
                                 ]) x
  where
    txt = "foo = bar; bar = baz; quux"


------------------------------------------------------------------------------
testCookie :: Test
testCookie =
    testCase "parsing/parseCookie" $ do
        assertEqual "cookie parsing" (Just [cv]) cv2
        let (Just [c]) = cv2
        -- stupid assertions to cover the accessors
        assertEqual "c1" (cookieName c) nm
        assertEqual "c2" (cookieValue c) v
        assertEqual "c3" (cookieExpires c) Nothing
        assertEqual "c4" (cookieDomain c) Nothing
        assertEqual "c5" (cookiePath c) Nothing
        assertEqual "c6" (cookieSecure c) False
        assertEqual "c7" (cookieHttpOnly c) False

  where
    cv  = Cookie nm v Nothing Nothing Nothing False False
    cv2 = parseCookie ct

    nm     = "foo"
    v      = "bar"

    ct = S.concat [ nm , "=" , v ]


------------------------------------------------------------------------------
testHeaderParse :: Test
testHeaderParse = testCase "parsing/headers" $ do
    let e = fullyParse txt pHeaders
    assertEqual "parse" (Right [("foo", "bar baz quux")]) e

    let f = fullyParse bad pHeaders
    assertEqual "bad parse" (Right []) f

  where
    txt = S.concat [ "foo: bar\r\n"
                   , "  baz\r\n"
                   , "  quux\r\n"
                   ]

    bad = "%&^%&^*^(*&^*&^*%*&%^^#$"


------------------------------------------------------------------------------
testQuotedString :: Test
testQuotedString = testCase "parsing/quoted-string" $ do
    let e = fullyParse txt pQuotedString
    assertEqual "q-s" (Right "foo\"bar\"baz") e

  where
    txt = "\"foo\\\"bar\\\"baz\""


------------------------------------------------------------------------------
-- older random didn't have a Word8 instance.....
data WrappedWord8 = W { _unW :: Word8 }

instance Show WrappedWord8 where
    show (W w) = show w

instance Random WrappedWord8 where
    randomR (W a, W b) g = case randomR (fromIntegral a :: Int,
                                         fromIntegral b :: Int) g
                           of (x, g') -> (W $! fromIntegral x, g')
    random = randomR (W minBound, W maxBound)

instance Arbitrary WrappedWord8 where
    arbitrary = choose (W minBound, W maxBound)


------------------------------------------------------------------------------
testUnsafeFromHex :: Test
testUnsafeFromHex = testCase "parsing/unsafeFromHex" $ do
    expectExceptionH $ return $! ((unsafeFromHex "zz") :: Int)
    let x = unsafeFromHex "a"
    assertEqual "a" (10 :: Int) x


------------------------------------------------------------------------------
testUnsafeFromInt :: Test
testUnsafeFromInt = testCase "parsing/unsafeFromNat" $ do
    expectExceptionH $ return $! ((unsafeFromNat "zz") :: Int)
    let x = unsafeFromNat "10"
    assertEqual "10" (10 :: Int) x


------------------------------------------------------------------------------
testUrlEncoded :: Test
testUrlEncoded = testCase "parsing/urlEncoded" $ do
    let x = parseUrlEncoded "foo=h%20i&bar=baz+baz&baz=quux&baz=zzz&%zz"
    assertEqual "map" (Map.fromList [ ("foo", ["h i"])
                                    , ("bar", ["baz baz"])
                                    , ("baz", ["quux", "zzz"]) ]) x


------------------------------------------------------------------------------
testFailParse :: Test
testFailParse = testCase "parsing/failParse" $ do
    let (Left a) = fullyParse "foo" ((string "bar" <?> "foo") <?> "bar")
    let (Left b) = fullyParse "foo" (fail "bar")
    let (Left c) = fullyParse "aaaaa" (many (char 'a') >> string "b")
    let f  = const $ Partial f
    let (Left d) = fullyParse' (const f) (const f) "aaaaa" (string "b")
    let e@(Just "aaaa") = parseToCompletion (many (char 'a')) "aaaa"
    let (Left g) = fullyParse "\x09" crlf
    let (Done z (0::Int)) = finish $! Partial $! \s ->
                             s `seq` Partial (\t -> t `seq` Done t 0)


    return $! length a `seq` length b `seq` length c `seq` length d `seq` e
                       `seq` length g `seq` z `seq` ()
