# `RequestParser` handles all the `HttpParser` module stuff for `HttpServer`
#
# The `HttpParser` module wraps [Joyent's `http-parser` C library][hprepo].
# A new `HttpParser` is created for each TCP connection being handled by
# our server.  Each `HttpParser` is initialized with a set of callback
# functions. When new data comes in, it is fed into the `http-parser` which
# executes the callbacks as different elements are parsed.  Finally, it calls
# on_message_complete when the incoming `Request` is fully built. The parser
# does not care if it receives just one byte at a time, or multiple requests.
# It will simply parse in order and run the callbacks normally.
#
# Note that this is not a module, it is included directly in `HttpServer.jl`
#
# [hprepo]: https://github.com/joyent/http-parser
#
using HttpParser
using HttpCommon
export RequestParser,
       clean!,
       add_data

# Datatype Tuples for the different `cfunction` signatures used by `HttpParser`
HTTP_CB      = (Int, (Ptr{Parser},))
HTTP_DATA_CB = (Int, (Ptr{Parser}, Ptr{Cchar}, Csize_t,))

# All the `HttpParser` callbacks to be run in C land
# Each one adds data to the `Request` until it is complete
#
function on_message_begin(parser)
    pd(parser).request = Request()

    return 0
end

function on_url(parser, at, len)
    r = pd(parser).request
    r.resource = string(r.resource, unsafe_string(convert(Ptr{UInt8}, at), Int(len)))
    return 0
end

function on_status_complete(parser)
    return 0
end

# Gather the header_field, set the field
# on header value, set the value for the current field
# there might be a better way to do
# this: https://github.com/joyent/node/blob/master/src/node_http_parser.cc#L207

function on_header_field(parser, at, len)
    par = pd(parser)
    if par.num_fields == par.num_values
        par.num_fields += 1
        push!(par.vec_fields, "")
    end
    header = unsafe_string(convert(Ptr{UInt8}, at))
    header_field = header[1:len]
    par.vec_fields[par.num_fields] = string(par.vec_fields[par.num_fields], header_field)
    return 0
end

function on_header_value(parser, at, len)
    par = pd(parser)
    if par.num_values != par.num_fields
        par.num_values += 1
        push!(par.vec_values, "")
    end
    s = unsafe_string(convert(Ptr{UInt8}, at), Int(len))
    header_value = s[1:len]
    par.vec_values[par.num_values] = string(par.vec_values[par.num_values], header_value)
    return 0
end

function on_headers_complete(parser)
    par = pd(parser)
    r = par.request
    merge!(r.headers, Dict(zip(par.vec_fields, par.vec_values)))
    par.num_fields = 0
    par.num_values = 0
    empty!(par.vec_fields)
    empty!(par.vec_values)
    p = unsafe_load(parser)
    # get first two bits of p.type_and_flags
    ptype = p.type_and_flags & 0x03
    if ptype == 0
        r.method = http_method_str(convert(Int, p.method))
    elseif ptype == 1
        r.headers["status_code"] = string(convert(Int, p.status_code))
    end
    r.headers["http_major"] = string(convert(Int, p.http_major))
    r.headers["http_minor"] = string(convert(Int, p.http_minor))
    r.headers["Keep-Alive"] = string(http_should_keep_alive(parser))
    return 0
end

function on_body(parser, at, len)
    r = pd(parser).request
    # write(pd(parser).data, convert(Ptr{UInt8}, at), len)
    append!(r.data, unsafe_wrap(Array,convert(Ptr{UInt8}, at), (len,)))
    r.data
    return 0
end

function on_message_complete(parser)
    state = pd(parser)
    r = state.request
    # r.data = takebuf_array(state.data)

    # Get the `parser.id` from the C pointer `parser`.
    # Retrieve our callback function from the global Dict.
    # Call it with the completed `Request`
    #
    state.complete_cb(r)
    return 0
end



default_complete_cb(r::Request) = nothing

mutable struct RequestParserState
    request::Request
    complete_cb::Function
    num_fields::Int
    num_values::Int
    vec_fields::Vector{String}
    vec_values::Vector{String}
end
RequestParserState() = RequestParserState(Request(),default_complete_cb,0,0,Vector{String}(),Vector{String}())

pd(p::Ptr{Parser}) = (unsafe_load(p).data)::RequestParserState

# `ClientParser` wraps our `HttpParser`
# Constructed with `on_message_complete` function.
#
struct ClientParser
    parser::Parser
    settings::ParserSettings

    function ClientParser(on_message_complete::Function)
        parser = Parser()
        parser.data = RequestParserState()
        http_parser_init(parser)
        parser.data.complete_cb = on_message_complete

        settings = ParserSettings(on_message_begin_cb, on_url_cb,
                                  on_status_complete_cb, on_header_field_cb,
                                  on_header_value_cb, on_headers_complete_cb,
                                  on_body_cb, on_message_complete_cb)

        new(parser, settings)
    end
end

# Passes `request_data` into `parser`
function add_data(parser::ClientParser, request_data::Vector{UInt8})
    http_parser_execute(parser.parser, parser.settings, request_data)
end
