module HeadersRequest

export headerslayer, setuseragent!

using Base64, URIs
using ..Messages, ..Forms, ..IOExtras, ..Sniff, ..Forms, ..Strings

"""
    headerslayer(handler) -> handler

Sets default expected headers.
"""
function headerslayer(handler)
    return function defaultheaders(req; iofunction=nothing, decompress=nothing,
            basicauth::Bool=true, detect_content_type::Bool=false, canonicalize_headers::Bool=false, kw...)
        headers = req.headers
        ## basicauth
        if basicauth
            userinfo = unescapeuri(req.url.userinfo)
            if !isempty(userinfo) && !hasheader(headers, "Authorization")
                @debug "Adding Authorization: Basic header."
                setheader(headers, "Authorization" => "Basic $(base64encode(userinfo))")
            end
        end
        ## content type detection
        if detect_content_type && (!hasheader(headers, "Content-Type")
            && !isa(req.body, Form)
            && isbytes(req.body))

            sn = sniff(bytes(req.body))
            setheader(headers, "Content-Type" => sn)
            @debug "setting Content-Type header to: $sn"
        end
        ## default headers
        if isempty(req.url.port) ||
            (req.url.scheme == "http" && req.url.port == "80") ||
            (req.url.scheme == "https" && req.url.port == "443")
            hostheader = req.url.host
        else
            hostheader = req.url.host * ":" * req.url.port
        end
        defaultheader!(headers, "Host" => hostheader)
        defaultheader!(headers, "Accept" => "*/*")
        if USER_AGENT[] !== nothing
            defaultheader!(headers, "User-Agent" => USER_AGENT[])
        end

        if !hasheader(headers, "Content-Length") &&
        !hasheader(headers, "Transfer-Encoding") &&
        !hasheader(headers, "Upgrade")
            l = nbytes(req.body)
            if l !== nothing
                setheader(headers, "Content-Length" => string(l))
            elseif req.method == "GET" && iofunction isa Function
                setheader(headers, "Content-Length" => "0")
            end
        end
        if !hasheader(headers, "Content-Type") && req.body isa Form && req.method in ("POST", "PUT", "PATCH")
            # "Content-Type" => "multipart/form-data; boundary=..."
            setheader(headers, content_type(req.body))
        elseif !hasheader(headers, "Content-Type") && (req.body isa Union{AbstractDict, NamedTuple}) && req.method in ("POST", "PUT", "PATCH")
            setheader(headers, "Content-Type" => "application/x-www-form-urlencoded")
        end
        if decompress === nothing || decompress
            defaultheader!(headers, "Accept-Encoding" => "gzip")
        end
        ## canonicalize headers
        if canonicalize_headers
            req.headers = canonicalizeheaders(headers)
        end
        res = handler(req; iofunction, decompress, kw...)
        if canonicalize_headers
            res.headers = canonicalizeheaders(res.headers)
        end
        return res
    end
end

canonicalizeheaders(h::T) where {T} = T([tocameldash(k) => v for (k,v) in h])

const USER_AGENT = Ref{Union{String, Nothing}}("HTTP.jl/$VERSION")

"""
    setuseragent!(x::Union{String, Nothing})

Set the default User-Agent string to be used in each HTTP request.
Can be manually overridden by passing an explicit `User-Agent` header.
Setting `nothing` will prevent the default `User-Agent` header from being passed.
"""
function setuseragent!(x::Union{String, Nothing})
    USER_AGENT[] = x
    return
end

end # module
