{-# LANGUAGE OverloadedStrings, CPP #-}

module RoundTripSpec where

import Control.Monad (replicateM)
import Data.IP (IP (..), IPv4, IPv6, toIPv4, toIPv6)
import qualified Data.ByteString.Char8 as BS
import Network.DNS.Decode
import Network.DNS.Encode
import Network.DNS.Types
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck (Gen, arbitrary, elements, forAll, frequency, listOf, oneof)
import Data.Word (Word8, Word16, Word32)
import Data.Monoid ((<>))

#if __GLASGOW_HASKELL__ < 709
import Control.Applicative
#endif


spec :: Spec
spec = do
    prop "IPv4" . forAll genIPv4 $ \ ip4 -> do
        let str = show ip4
        read str `shouldBe` ip4
        show (read str :: IPv4) `shouldBe` str

    prop "IPv6" . forAll genIPv6 $ \ ip6 -> do
        let str = show ip6
        read str `shouldBe` ip6
        show (read str :: IPv6) `shouldBe` str

    prop "TYPE" . forAll genTYPE $ \ t ->
        toTYPE (fromTYPE t) `shouldBe` t

    prop "Domain" . forAll genDomain $ \ dom -> do
        let bs = encodeDomain dom
        decodeDomain bs `shouldBe` Right dom
        fmap encodeDomain (decodeDomain bs) `shouldBe` Right bs

    prop "Mailbox" . forAll genMailbox $ \ dom -> do
        let bs = encodeMailbox dom
        decodeMailbox bs `shouldBe` Right dom
        fmap encodeMailbox (decodeMailbox bs) `shouldBe` Right bs

    prop "DNSFlags" . forAll genDNSFlags $ \ flgs -> do
        let bs = encodeDNSFlags flgs
        decodeDNSFlags bs `shouldBe` Right flgs
        fmap encodeDNSFlags (decodeDNSFlags bs) `shouldBe` Right bs

    prop "ResourceRecord" . forAll genResourceRecord $ \ rr -> do
        let bs = encodeResourceRecord rr
        decodeResourceRecord bs `shouldBe` Right rr
        fmap encodeResourceRecord (decodeResourceRecord bs) `shouldBe` Right bs

    prop "DNSHeader" . forAll genDNSHeader $ \ hdr ->
        decodeDNSHeader (encodeDNSHeader hdr) `shouldBe` Right hdr

    prop "DNSMessage" . forAll genDNSMessage $ \ msg ->
        decode (encode msg) `shouldBe` Right msg

    prop "EDNS0" . forAll genEDNS0Header $ \(edns0,hdr) -> do
        let rr0 = fromEDNS0 edns0
            msg0 = DNSMessage hdr [] [] [] [rr0]
            Right msg1 = decode $ encode msg0
            medns1 = toEDNS0 (flags $ header msg0) (head $ additional msg1)
        medns1 `shouldBe` Just edns0

----------------------------------------------------------------

genDNSMessage :: Gen DNSMessage
genDNSMessage =
    DNSMessage <$> genDNSHeader <*> listOf genQuestion <*> listOf genResourceRecord
                <*> listOf genResourceRecord <*> listOf genResourceRecord


genQuestion :: Gen Question
genQuestion = do
    typ <- genTYPE
    dom <- genDomain
    pure $ Question dom typ

genTYPE :: Gen TYPE
genTYPE = frequency
    [ (20, elements
            [ A, AAAA, NS, TXT, MX, CNAME, SOA, PTR, SRV, DNAME, OPT, DS, RRSIG
            , NSEC, DNSKEY, NSEC3, NSEC3PARAM, TLSA, CDS, CDNSKEY, CSYNC
            ])
    , (1, toTYPE <$> genWord16)
    ]

genResourceRecord :: Gen ResourceRecord
genResourceRecord = frequency
    [ (8, genRR)
    ]
  where
    genRR = do
      dom <- genDomain
      t <- elements [A , AAAA, NS, TXT, MX, CNAME, SOA, PTR, SRV, DNAME, DS]
      ResourceRecord dom t classIN <$> genWord32 <*> mkRData dom t

mkRData :: Domain -> TYPE -> Gen RData
mkRData dom typ =
    case typ of
        A -> RD_A <$> genIPv4
        AAAA -> RD_AAAA <$> genIPv6
        NS -> pure $ RD_NS dom
        TXT -> RD_TXT <$> genByteString
        MX -> RD_MX <$> genWord16 <*> genDomain
        CNAME -> pure $ RD_CNAME dom
        SOA -> RD_SOA dom <$> genMailbox <*> genWord32 <*> genWord32 <*> genWord32 <*> genWord32 <*> genWord32
        PTR -> RD_PTR <$> genDomain
        SRV -> RD_SRV <$> genWord16 <*> genWord16 <*> genWord16 <*> genDomain
        DNAME -> RD_DNAME <$> genDomain
        DS -> RD_DS <$> genWord16 <*> genWord8 <*> genWord8 <*> genByteString
        TLSA -> RD_TLSA <$> genWord8 <*> genWord8 <*> genWord8 <*> genByteString

        _ -> pure . RD_TXT $ "Unhandled type " <> BS.pack (show typ)

genIPv4 :: Gen IPv4
genIPv4 = toIPv4 <$> replicateM 4 (fromIntegral <$> genWord8)

genIPv6 :: Gen IPv6
genIPv6 = toIPv6 <$> replicateM 8 (fromIntegral <$> genWord16)

genByteString :: Gen BS.ByteString
genByteString = elements
    [ "", "a", "a.b", "abc", "a.b.c" ]

genMboxString :: Gen BS.ByteString
genMboxString = elements
    [ "", "a", "a@b", "abc", "a@b.c" ]

genDomain :: Gen Domain
genDomain = do
    bs <- genByteString
    pure $ bs <> "."

genMailbox :: Gen Mailbox
genMailbox = do
    bs <- genMboxString
    pure $ bs <> "."

genDNSHeader :: Gen DNSHeader
genDNSHeader = DNSHeader <$> genWord16 <*> genDNSFlags

genDNSFlags :: Gen DNSFlags
genDNSFlags =
  DNSFlags <$> genQorR <*> genOPCODE <*> genBool <*> genBool
            <*> genBool <*> genBool <*> genRCODE <*> genBool

genWord16 :: Gen Word16
genWord16 = arbitrary

genWord32 :: Gen Word32
genWord32 = arbitrary

genWord8 :: Gen Word8
genWord8 = arbitrary

genBool :: Gen Bool
genBool = elements [True, False]

genQorR :: Gen QorR
genQorR = elements [minBound .. maxBound]

genOPCODE :: Gen OPCODE
genOPCODE  = elements [minBound .. maxBound]

genRCODE :: Gen RCODE
genRCODE = elements $ map toRCODE [0..15]

genEDNS0 :: Gen EDNS0
genEDNS0 = do
    erc <- genExtRCODE
    ok <- genBool
    od <- genOData
    return $ defaultEDNS0 {
        extRCODE = erc
      , dnssecOk = ok
      , options  = [od]
      }

genOData :: Gen OData
genOData = oneof
    [ genOD_Unknown
    , OD_ClientSubnet <$> genWord8 <*> genWord8 <*> oneof [ IPv4 <$> genIPv4, IPv6 <$> genIPv6 ]
    ]
  where
    genOD_Unknown = do
      bs <- genByteString
      let opc = toOptCode $ fromIntegral $ BS.length bs
      pure $ UnknownOData opc bs

genExtRCODE :: Gen RCODE
genExtRCODE = elements $ map toRCODE [0..4095]

genEDNS0Header :: Gen (EDNS0, DNSHeader)
genEDNS0Header = do
    edns <- genEDNS0
    hdr <- genDNSHeader
    let flg = flags hdr
    return (edns, hdr { flags =  flg { rcode = extRCODE edns } })
