{-# LANGUAGE RecordWildCards #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

{-|
Module      : Network.URI.Arbitrary
Description : Arbitrary Instances for Network.URI
Copyright   : (c) Alex Brandt, 2018
License     : MIT

Arbitrary instances for "Network.URI".
-}
module Network.URI.Arbitrary
  ()
where

import           Control.Applicative            ( (<$>)
                                                , (<*>)
                                                )
import           Control.Monad                  ( replicateM )
import           Data.List                      ( intercalate )
import           Network.URI                    ( parseURIReference
                                                , URI(..)
                                                , URIAuth(..)
                                                , uriToString
                                                )
import           Test.QuickCheck                ( Arbitrary(arbitrary, shrink)
                                                , choose
                                                , elements
                                                , Gen
                                                , listOf
                                                , listOf1
                                                , oneof
                                                , suchThat
                                                )

instance Arbitrary URI where
  arbitrary = do
    uriScheme    <- scheme
    uriAuthority <- arbitrary :: Gen (Maybe URIAuth)
    uriPath <- path (null uriScheme) $ maybe True emptyAuthority uriAuthority
    uriQuery     <- oneof [query, return ""]
    uriFragment  <- oneof [fragment, return ""]

    return URI { .. }
   where
    emptyAuthority URIAuth {..} = all null [uriUserInfo, uriRegName, uriPort]

  shrink URI {..} = filter
    isURI
    [ URI uriScheme' uriAuthority' uriPath' uriQuery' uriFragment'
    | (uriScheme', uriAuthority', uriPath', uriQuery', uriFragment') <- shrink
      (uriScheme, uriAuthority, uriPath, uriQuery, uriFragment)
    ]
   where
    isURI u = case parseURIReference (uriToString id u "") of
      Just u' -> u' == u
      Nothing -> False


instance Arbitrary URIAuth where
  arbitrary = URIAuth <$> userinfo <*> host `suchThat` (not . null) <*> port

  shrink URIAuth {..} =
    [ URIAuth uriUserInfo' uriRegName' uriPort'
    | (uriUserInfo', uriRegName', uriPort') <- shrink
      (uriUserInfo, uriRegName, uriPort)
    ]

-- * RFC 3986 Generators
--
--   Some generators are handled by the 'Arbitrary' instances above, and others
--   are folded into symbols that are preceeded or followed by identifying
--   tokens.

scheme :: Gen String
scheme = do
  a <- alpha
  r <- listOf $ oneof [alpha, digit, elements ['+', '-', '.']]
  return $ a : (r ++ ":")

userinfo :: Gen String
userinfo = do
  u <- concat <$> userinfo'
  if null u then return "" else return $ u ++ "@"
 where
  userinfo' = listOf $ oneof
    [replicateM 1 $ oneof [unreserved, subDelims, return ':'], percentEncoded]

host :: Gen String
host = oneof [ipLiteral, ipv4Address, regName]

port :: Gen String
port = do
  p <- listOf digit
  if null p then return "" else return $ ':' : p

ipLiteral :: Gen String
ipLiteral = do
  x <- oneof [ipv6Address
             --, ipvFuture
                         ]
  return $ "[" ++ x ++ "]"

{- TODO Check that "Network.URI" implements this correctly.
ipvFuture :: Gen String
ipvFuture =
  do h <- hexdig
     o <- oneof [ unreserved, subDelims, return ':' ]
     return ['v', h, '.', o]
-}

ipv6Address :: Gen String
ipv6Address = concat <$> oneof
  [ sequence [b 6, ls32]
  , sequence [return "::", b 5, ls32]
  , sequence [h16, return "::", b 4, ls32]
  , sequence [b 1, h16, return "::", b 3, ls32]
  , sequence [b 2, h16, return "::", b 2, ls32]
  , sequence [b 3, h16, return "::", b 1, ls32]
  , sequence [b 4, h16, return "::", ls32]
  , sequence [b 5, h16, return "::", h16]
  , sequence [b 6, h16, return "::"]
  ]
  where b n = fmap concat $ replicateM n $ fmap (++ ":") h16 :: Gen String

h16 :: Gen String
h16 = replicateM 4 hexdig

ls32 :: Gen String
ls32 = oneof [intercalate ":" <$> replicateM 2 h16, ipv4Address]

ipv4Address :: Gen String
ipv4Address = intercalate "." <$> replicateM 4 decOctet

decOctet :: Gen String
decOctet = (show :: Int -> String) <$> choose (0, 255)

regName :: Gen String
regName = fmap concat $ listOf $ oneof
  [replicateM 1 unreserved, percentEncoded, replicateM 1 subDelims]

path :: Bool -> Bool -> Gen String
path emptyScheme emptyURIAuth = if emptyURIAuth
  then oneof
    [ pathAbsolute
    , if emptyScheme then pathNoScheme else pathRootless
    , return ""
    ]
  else pathAbEmpty

pathAbEmpty :: Gen String
pathAbEmpty = concat <$> listOf (('/' :) . concat <$> listOf pchar)

pathAbsolute :: Gen String
pathAbsolute = ('/' :) <$> oneof [return "", pathRootless]

pathNoScheme :: Gen String
pathNoScheme = concat <$> sequence [segment1nc, pathAbEmpty]

pathRootless :: Gen String
pathRootless = concat <$> sequence [concat <$> listOf1 pchar, pathAbEmpty]

segment1nc :: Gen String
segment1nc = oneof
  [ replicateM 1 unreserved
  , percentEncoded
  , replicateM 1 subDelims
  , replicateM 1 $ return '@'
  ]

pchar :: Gen String
pchar = oneof
  [ replicateM 1 unreserved
  , percentEncoded
  , replicateM 1 subDelims
  , replicateM 1 $ return ':'
  , replicateM 1 $ return '@'
  ]

query :: Gen String
query =
  fmap (('?' :) . concat) $ listOf $ oneof [pchar, return "/", return "?"]

fragment :: Gen String
fragment =
  fmap (('#' :) . concat) $ listOf $ oneof [pchar, return "/", return "?"]

percentEncoded :: Gen String
percentEncoded = ('%' :) <$> replicateM 2 hexdig

unreserved :: Gen Char
unreserved = oneof [alpha, digit, elements ['-', '.', '_', '~']]

subDelims :: Gen Char
subDelims = elements ['!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=']

-- * RFC 2234 Generators

alpha :: Gen Char
alpha = elements $ ['a' .. 'z'] ++ ['A' .. 'Z']

digit :: Gen Char
digit = elements ['0' .. '9']

hexdig :: Gen Char
hexdig = oneof [digit, elements ['A' .. 'F']]