{-# LANGUAGE OverloadedStrings #-}

{-|
  Parser for URI
-}

module Network.Web.URI (
    URI, uriScheme, uriAuthority, uriPath, uriQuery, uriFragment
  , URIAuth, uriUserInfo, uriRegName, uriPort
  , parseURI
  , uriHostName, uriPortNumber, toURL, toURLwoPort, toURLPath
  , isAbsoluteURI, unEscapeString, unEscapeByteString
  ) where

import qualified Data.ByteString.Char8 as S
import Data.Char

{-|
  Abstract data type for URI
-}
data URI = URI {
    uriScheme    :: S.ByteString
  , uriAuthority :: Maybe URIAuth
  , uriPath      :: S.ByteString
  , uriQuery     :: S.ByteString
  , uriFragment  :: S.ByteString
} deriving Show

{-|
  Abstract data type for URI Authority
-}
data URIAuth = URIAuth {
    uriUserInfo :: S.ByteString
  , uriRegName  :: S.ByteString
  , uriPort     :: S.ByteString
} deriving Show

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

{-|
  Parsing URI.
-}
parseURI :: S.ByteString -> Maybe URI
parseURI url = Just URI {
    uriScheme = "http:"
  , uriAuthority = Just URIAuth {
        uriUserInfo = ""
      , uriRegName = host
      , uriPort = port
      }
  , uriPath = path
  , uriQuery = query
  , uriFragment = ""
  }
  where
    (auth,pathQuery) = parseURL url
    (path,query) = parsePathQuery pathQuery
    (host,port) = parseAuthority auth

parseURL :: S.ByteString -> (S.ByteString,S.ByteString)
parseURL reqUri = let (hostServ,path) = S.break (=='/') $ S.drop 7 reqUri
                  in (hostServ, checkPath path)
  where
    checkPath ""   = "/"
    checkPath path = path

parsePathQuery :: S.ByteString -> (S.ByteString,S.ByteString)
parsePathQuery = S.break (=='?')

parseAuthority :: S.ByteString -> (S.ByteString,S.ByteString)
parseAuthority hostServ
  | serv == "" = (host, "")
  | otherwise  = (host, S.tail serv)
  where
    (host,serv) = S.break (==':') hostServ

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

{-|
  Getting a hostname from 'URI'.
-}
uriHostName :: URI -> S.ByteString
uriHostName uri = maybe "" uriRegName $ uriAuthority uri

{-|
  Getting a port number from 'URI'.
-}
uriPortNumber :: URI -> S.ByteString
uriPortNumber uri = maybe "" uriPort $ uriAuthority uri

{-|
  Making a URL string from 'URI'.
-}
toURL :: URI -> S.ByteString
toURL uri = uriScheme uri +++ "//" +++ hostServ +++ uriPath uri +++ uriQuery uri
  where
    host = uriHostName uri
    serv = uriPortNumber uri
    hostServ = if S.null serv
               then host
               else host +++ ":" +++ serv
    (+++) = S.append

{-|
  Making a URL string from 'URI' without port.
-}
toURLwoPort :: URI -> S.ByteString
toURLwoPort uri = uriScheme uri +++ "//" +++ uriHostName uri +++ uriPath uri +++ uriQuery uri
  where
    (+++) = S.append

{-|
  Making a URL string from 'URI' without port and parameters.
-}
toURLPath :: URI -> S.ByteString
toURLPath uri = uriScheme uri +++ "//" +++ uriHostName uri +++ uriPath uri
  where
    (+++) = S.append

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

{-|
  Checking whether or not URI starts with \"http://\".
-}
isAbsoluteURI :: S.ByteString -> Bool
isAbsoluteURI url = "http://" `S.isPrefixOf` url

{-|
  Decoding the %XX encoding.
-}
unEscapeByteString :: S.ByteString -> S.ByteString
unEscapeByteString "" = ""
unEscapeByteString bs
  | S.head bs == '%' && S.length bs >= 3
    && isHexDigit c1 && isHexDigit c2    = dc <:> unEscapeByteString cs
  where
    [_,c1,c2] = S.unpack $ S.take 3 bs
    cs = S.drop 3 bs
    dc = chr $ digitToInt c1 * 16 + digitToInt c2
    (<:>) = S.cons
unEscapeByteString bs = c <:> unEscapeByteString cs
  where
    c = S.head bs
    cs = S.tail bs
    (<:>) = S.cons

{-|
   Decoding the %XX encoding.
 -}
unEscapeString :: String -> String
unEscapeString [] = ""
unEscapeString ('%':c1:c2:cs)
  | isHexDigit c1 && isHexDigit c2 = dc : unEscapeString cs
  where
    dc = chr $ digitToInt c1 * 16 + digitToInt c2
unEscapeString (c:cs) = c : unEscapeString cs
