text:
# Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev
#
# Additional changes are licensed under the same terms as NGINX and
# copyright Joyent, Inc. and other Node contributors. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#

mutable struct Parser
    state::UInt8
    header_state::UInt8
    index::UInt8
    flags::UInt8
    nread::UInt32
    content_length::UInt64
    fieldbuffer::Vector{UInt8}
    valuebuffer::Vector{UInt8}
end

Parser() = Parser(s_start_req_or_res, 0x00, 0, 0, 0, 0, UInt8[], UInt8[])

function reset!(p::Parser)
    p.state = start_state
    p.header_state = 0x00
    p.index = 0x00
    p.flags = 0x00
    p.nread = 0x00000000
    p.content_length = 0x0000000000000000
    empty!(p.fieldbuffer)
    empty!(p.valuebuffer)
    return
end

macro nread(n)
    return esc(quote
        parser.nread += UInt32($n)
        @errorif(parser.nread > maxheader, HPE_HEADER_OVERFLOW)
    end)
end

onmessagebegin(r) = @debug(PARSING_DEBUG, "onmessagebegin")
# should we just make a copy of the byte vector for URI here?
function onurl(r, bytes, i, j)
    @debug(PARSING_DEBUG, "onurl")
    @debug(PARSING_DEBUG, i - j + 1)
    @debug(PARSING_DEBUG, "'$(String(bytes[i:j]))'")
    @debug(PARSING_DEBUG, r.method)
    uri = URIs.http_parser_parse_url(bytes, i, j - i + 1, r.method == CONNECT)
    @debug(PARSING_DEBUG, uri)
    setfield!(r, :uri, uri)
    return
end
onstatus(r) = @debug(PARSING_DEBUG, "onstatus")
function onheaderfield(p::Parser, bytes, i, j)
    @debug(PARSING_DEBUG, "onheaderfield")
    append!(p.fieldbuffer, view(bytes, i:j))
    return
end
function onheadervalue(p::Parser, bytes, i, j)
    @debug(PARSING_DEBUG, "onheadervalue")
    append!(p.valuebuffer, view(bytes, i:j))
    return
end
function onheadervalue(p, r, bytes, i, j, issetcookie, host, KEY)
    @debug(PARSING_DEBUG, "onheadervalue2")
    append!(p.valuebuffer, view(bytes, i:j))
    key = canonicalize!(unsafe_string(pointer(p.fieldbuffer), length(p.fieldbuffer)))
    val = unsafe_string(pointer(p.valuebuffer), length(p.valuebuffer))
    if key == ""
        # the header value was parsed in two parts,
        # KEY[] holds the most recently parsed header field,
        # and we already stored the first part of the header value in r.headers
        # get the first part and concatenate it with the second part we have now
        key = KEY[]
        setindex!(r.headers, string(r.headers[key], val), key)
    else
        KEY[] = key
        val2 = get!(r.headers, key, val)
        val2 !== val && setindex!(r.headers, string(val2, ", ", val), key)
    end
    issetcookie && push!(r.cookies, Cookies.readsetcookie(host, val))
    empty!(p.fieldbuffer)
    empty!(p.valuebuffer)
    return
end
onheaderscomplete(r) = @debug(PARSING_DEBUG, "onheaderscomplete")
function onbody(r, maintask, bytes, i, j)
    @debug(PARSING_DEBUG, "onbody")
    @debug(PARSING_DEBUG, String(r.body))
    @debug(PARSING_DEBUG, String(bytes[i:j]))
    len = j - i + 1
    #TODO: avoid copying the bytes here? can we somehow write the bytes to a FIFOBuffer more efficiently?
    nb = write(r.body, bytes, i, j)
    if nb < len # didn't write all available bytes
        if current_task() == maintask
            # main request function hasn't returned yet, so not safe to wait
            r.body.max += len - nb
            write(r.body, bytes, i + nb, j)
        else
            while nb < len
                nb += write(r.body, bytes, i + nb, j)
            end
        end
    end
    @debug(PARSING_DEBUG, String(r.body))
    return
end
onmessagecomplete(r::Request) = @debug(PARSING_DEBUG, "onmessagecomplete")
onmessagecomplete(r::Response) = (@debug(PARSING_DEBUG, "onmessagecomplete"); close(r.body))

"""
    HTTP.parse([HTTP.Request, HTTP.Response], str; kwargs...)

Parse a `HTTP.Request` or `HTTP.Response` from a string. `str` must contain at least one
full request or response (but may include more than one). Supported keyword arguments include:

  * `extra`: a `Ref{String}` that will be used to store any extra bytes beyond a full request or response
  * `lenient`: whether the request/response parsing should allow additional characters
  * `maxuri`: the maximum allowed size of a uri in a request
  * `maxheader`: the maximum allowed size of headers
  * `maxbody`: the maximum allowed size of a request or response body
"""
function parse(T::Type{<:Union{Request, Response}}, str;
                extra::Ref{String}=Ref{String}(), lenient::Bool=true,
                maxuri::Int64=DEFAULT_MAX_URI, maxheader::Int64=DEFAULT_MAX_HEADER,
                maxbody::Int64=DEFAULT_MAX_BODY,
                maintask::Task=current_task())
    r = T(body=FIFOBuffer())
    reset!(DEFAULT_PARSER)
    err, headerscomplete, messagecomplete, upgrade = parse!(r, DEFAULT_PARSER, Vector{UInt8}(str);
        lenient=lenient, maxuri=maxuri, maxheader=maxheader, maxbody=maxbody, maintask=maintask)
    err != HPE_OK && throw(ParsingError("error parsing $T: $(ParsingErrorCodeMap[err])"))
    extra[] = upgrade
    return r
end

const start_state = s_start_req_or_res
const DEFAULT_MAX_HEADER = Int64(80 * 1024)
const DEFAULT_MAX_URI = Int64(8000)
const DEFAULT_MAX_BODY = Int64(2)^32 # 4Gib
const DEFAULT_PARSER = Parser()

function parse!(r::Union{Request, Response}, parser, bytes, len=length(bytes);
        lenient::Bool=true, host::String="", method::Method=GET,
        maxuri::Int64=DEFAULT_MAX_URI, maxheader::Int64=DEFAULT_MAX_HEADER,
        maxbody::Int64=DEFAULT_MAX_BODY, maintask::Task=current_task())::Tuple{ParsingErrorCode, Bool, Bool, String}
    return parse!(r, parser, bytes, len, lenient, host, method, maxuri, maxheader, maxbody, maintask)
end
function parse!(r, parser, bytes, len, lenient, host, method, maxuri, maxheader, maxbody, maintask)
    strict = !lenient
    p_state = parser.state
    status_mark = url_mark = header_field_mark = header_field_end_mark = header_value_mark = body_mark = 0
    errno = HPE_OK
    upgrade = issetcookie = headersdone = false
    KEY = Ref{String}()
    @debug(PARSING_DEBUG, len)
    @debug(PARSING_DEBUG, ParsingStateCode(p_state))
    if len == 0
        if p_state == s_body_identity_eof
            parser.state = p_state
            onmessagecomplete(r)
            @debug(PARSING_DEBUG, "this 6")
            return HPE_OK, true, true, ""
        elseif @anyeq(p_state, s_dead, s_start_req_or_res, s_start_res, s_start_req)
            return HPE_OK, false, false, ""
        else
            return HPE_INVALID_EOF_STATE, false, false, ""
        end
    end

    if p_state == s_header_field
        @debug(PARSING_DEBUG, ParsingStateCode(p_state))
        header_field_mark = header_field_end_mark = 1
    end
    if p_state == s_header_value
        @debug(PARSING_DEBUG, ParsingStateCode(p_state))
        header_value_mark = 1
    end
    if @anyeq(p_state, s_req_path, s_req_schema, s_req_schema_slash, s_req_schema_slash_slash,
                   s_req_server_start, s_req_server, s_req_server_with_at,
                   s_req_query_string_start, s_req_query_string, s_req_fragment)
        url_mark = 1
    elseif p_state == s_res_status
        status_mark = 1
    end
    p = 1
    while p <= len
        @inbounds ch = Char(bytes[p])
        @debug(PARSING_DEBUG, "top of main for-loop")
        @debug(PARSING_DEBUG, Base.escape_string(string(ch)))
        if p_state <= s_headers_done
            @nread(1)
        end

        @label reexecute

        if p_state == s_dead
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            #= this state is used after a 'Connection: close' message
             # the parser will error out if it reads another message
            =#
            (ch == CR || ch == LF) && @goto breakout
            @err HPE_CLOSED_CONNECTION

        elseif p_state == s_start_req_or_res
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))

            (ch == CR || ch == LF) && @goto breakout
            parser.flags = 0
            parser.content_length = ULLONG_MAX

            if ch == 'H'
                p_state = s_res_or_resp_H
                parser.state = p_state
                onmessagebegin(r)
            else
                p_state = s_start_req
                @goto reexecute
            end

        elseif p_state == s_res_or_resp_H
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == 'T'
                p_state = s_res_HT
            else
                @errorif(ch != 'E', HPE_INVALID_CONSTANT)
                r.method = HEAD
                parser.index = 3
                p_state = s_req_method
            end

        elseif p_state == s_start_res
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            parser.flags = 0
            parser.content_length = ULLONG_MAX
            if ch == 'H'
                p_state = s_res_H
            elseif ch == CR || ch == LF
            else
                @err HPE_INVALID_CONSTANT
            end
            parser.state = p_state
            onmessagebegin(r)

        elseif p_state == s_res_H
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'T')
            p_state = s_res_HT

        elseif p_state == s_res_HT
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'T')
            p_state = s_res_HTT

        elseif p_state == s_res_HTT
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'P')
            p_state = s_res_HTTP

        elseif p_state == s_res_HTTP
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != '/')
            p_state = s_res_first_http_major

        elseif p_state == s_res_first_http_major
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(!isnum(ch), HPE_INVALID_VERSION)
            r.major = Int16(ch - '0')
            p_state = s_res_http_major

        #= major HTTP version or dot =#
        elseif p_state == s_res_http_major
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == '.'
                p_state = s_res_first_http_minor
                @goto breakout
            end
            @errorif(!isnum(ch), HPE_INVALID_VERSION)
            r.major *= Int16(10)
            r.major += Int16(ch - '0')
            @errorif(r.major > 999, HPE_INVALID_VERSION)

        #= first digit of minor HTTP version =#
        elseif p_state == s_res_first_http_minor
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(!isnum(ch), HPE_INVALID_VERSION)
            r.minor = Int16(ch - '0')
            p_state = s_res_http_minor

        #= minor HTTP version or end of request line =#
        elseif p_state == s_res_http_minor
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == ' '
                p_state = s_res_first_status_code
                @goto breakout
            end
            @errorif(!isnum(ch), HPE_INVALID_VERSION)
            r.minor *= Int16(10)
            r.minor += Int16(ch - '0')
            @errorif(r.minor > 999, HPE_INVALID_VERSION)

        elseif p_state == s_res_first_status_code
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if !isnum(ch)
                ch == ' ' && @goto breakout
                @err(HPE_INVALID_STATUS)
            end
            r.status = Int32(ch - '0')
            p_state = s_res_status_code

        elseif p_state == s_res_status_code
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if !isnum(ch)
                if ch == ' '
                    p_state = s_res_status_start
                elseif ch == CR
                    p_state = s_res_line_almost_done
                elseif ch == LF
                    p_state = s_header_field_start
                else
                    @err(HPE_INVALID_STATUS)
                end
            else
                r.status *= Int32(10)
                r.status += Int32(ch - '0')
                @errorif(r.status > 999, HPE_INVALID_STATUS)
            end

        elseif p_state == s_res_status_start
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == CR
                p_state = s_res_line_almost_done
            elseif ch == LF
                p_state = s_header_field_start
            else
                status_mark = p
                p_state = s_res_status
                parser.index = 1
            end

        elseif p_state == s_res_status
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == CR
                p_state = s_res_line_almost_done
                parser.state = p_state
                onstatus(r)
                status_mark = 0
            elseif ch == LF
                p_state = s_header_field_start
                parser.state = p_state
                onstatus(r)
                status_mark = 0
            end

        elseif p_state == s_res_line_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != LF)
            p_state = s_header_field_start

        elseif p_state == s_start_req
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            (ch == CR || ch == LF) && @goto breakout
            parser.flags = 0
            parser.content_length = ULLONG_MAX
            @errorif(!isalpha(ch), HPE_INVALID_METHOD)

            r.method = Method(0)
            parser.index = 2

            if ch == 'A'
                r.method = ACL
            elseif ch == 'B'
                r.method = BIND
            elseif ch == 'C'
                r.method = CONNECT
            elseif ch == 'D'
                r.method = DELETE
            elseif ch == 'G'
                r.method = GET
            elseif ch == 'H'
                r.method = HEAD
            elseif ch == 'L'
                r.method = LOCK
            elseif ch == 'M'
                r.method = MKCOL
            elseif ch == 'N'
                r.method = NOTIFY
            elseif ch == 'O'
                r.method = OPTIONS
            elseif ch == 'P'
                r.method = POST
            elseif ch == 'R'
                r.method = REPORT
            elseif ch == 'S'
                r.method = SUBSCRIBE
            elseif ch == 'T'
                r.method = TRACE
            elseif ch == 'U'
                r.method = UNLOCK
            else
                @err(HPE_INVALID_METHOD)
            end
            p_state = s_req_method
            parser.state = p_state
            onmessagebegin(r)

        elseif p_state == s_req_method
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            matcher = string(r.method)
            @debug(PARSING_DEBUG, matcher)
            @debug(PARSING_DEBUG, parser.index)
            @debug(PARSING_DEBUG, Base.escape_string(string(ch)))
            if ch == ' ' && parser.index == length(matcher) + 1
                p_state = s_req_spaces_before_url
            elseif parser.index > length(matcher)
                @err(HPE_INVALID_METHOD)
            elseif ch == matcher[parser.index]
                @debug(PARSING_DEBUG, "nada")
            elseif isalpha(ch)
                ci = @shifted(r.method, Int(parser.index) - 1, ch)
                if ci == @shifted(POST, 1, 'U')
                    r.method = PUT
                elseif ci == @shifted(POST, 1, 'A')
                    r.method =  PATCH
                elseif ci == @shifted(CONNECT, 1, 'H')
                    r.method =  CHECKOUT
                elseif ci == @shifted(CONNECT, 2, 'P')
                    r.method =  COPY
                elseif ci == @shifted(MKCOL, 1, 'O')
                    r.method =  MOVE
                elseif ci == @shifted(MKCOL, 1, 'E')
                    r.method =  MERGE
                elseif ci == @shifted(MKCOL, 2, 'A')
                    r.method =  MKACTIVITY
                elseif ci == @shifted(MKCOL, 3, 'A')
                    r.method =  MKCALENDAR
                elseif ci == @shifted(SUBSCRIBE, 1, 'E')
                    r.method =  SEARCH
                elseif ci == @shifted(REPORT, 2, 'B')
                    r.method =  REBIND
                elseif ci == @shifted(POST, 1, 'R')
                    r.method =  PROPFIND
                elseif ci == @shifted(PROPFIND, 4, 'P')
                    r.method =  PROPPATCH
                elseif ci == @shifted(PUT, 2, 'R')
                    r.method =  PURGE
                elseif ci == @shifted(LOCK, 1, 'I')
                    r.method =  LINK
                elseif ci == @shifted(UNLOCK, 2, 'S')
                    r.method =  UNSUBSCRIBE
                elseif ci == @shifted(UNLOCK, 2, 'B')
                    r.method =  UNBIND
                elseif ci == @shifted(UNLOCK, 3, 'I')
                    r.method =  UNLINK
                else
                    @err(HPE_INVALID_METHOD)
                end
            elseif ch == '-' && parser.index == 2 && r.method == MKCOL
                @debug(PARSING_DEBUG, "matched MSEARCH")
                r.method = MSEARCH
                parser.index -= 1
            else
                @err(HPE_INVALID_METHOD)
            end
            parser.index += 1
            @debug(PARSING_DEBUG, parser.index)

        elseif p_state == s_req_spaces_before_url
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            ch == ' ' && @goto breakout
            url_mark = p
            if r.method == CONNECT
                p_state = s_req_server_start
            end
            p_state = URIs.parseurlchar(p_state, ch, strict)
            @errorif(p_state == s_dead, HPE_INVALID_URL)

        elseif @anyeq(p_state, s_req_schema, s_req_schema_slash, s_req_schema_slash_slash, s_req_server_start)
            @errorif(ch in (' ', CR, LF), HPE_INVALID_URL)
            p_state = URIs.parseurlchar(p_state, ch, strict)
            @errorif(p_state == s_dead, HPE_INVALID_URL)

        elseif @anyeq(p_state, s_req_server, s_req_server_with_at, s_req_path, s_req_query_string_start,
                           s_req_query_string, s_req_fragment_start, s_req_fragment)
            if ch == ' '
                p_state = s_req_http_start
                parser.state = p_state
                p - url_mark > maxuri && @err(HPE_URI_OVERFLOW)
                onurl(r, bytes, url_mark, p-1)
                url_mark = 0
            elseif ch in (CR, LF)
                r.major = Int16(0)
                r.minor = Int16(9)
                p_state = ifelse(ch == CR, s_req_line_almost_done, s_header_field_start)
                parser.state = p_state
                p - url_mark > maxuri && @err(HPE_URI_OVERFLOW)
                onurl(r, bytes, url_mark, p-1)
                url_mark = 0
            else
                p_state = URIs.parseurlchar(p_state, ch, strict)
                @errorif(p_state == s_dead, HPE_INVALID_URL)
            end

        elseif p_state == s_req_http_start
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == 'H'
                p_state = s_req_http_H
            elseif ch == ' '
            else
                @err(HPE_INVALID_CONSTANT)
            end

        elseif p_state == s_req_http_H
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'T')
            p_state = s_req_http_HT

        elseif p_state == s_req_http_HT
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'T')
            p_state = s_req_http_HTT

        elseif p_state == s_req_http_HTT
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != 'P')
            p_state = s_req_http_HTTP

        elseif p_state == s_req_http_HTTP
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != '/')
            p_state = s_req_first_http_major

        #= first digit of major HTTP version =#
        elseif p_state == s_req_first_http_major
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(ch < '1' || ch > '9', HPE_INVALID_VERSION)
            r.major = Int16(ch - '0')
            p_state = s_req_http_major

        #= major HTTP version or dot =#
        elseif p_state == s_req_http_major
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == '.'
                p_state = s_req_first_http_minor
            elseif !isnum(ch)
                @err(HPE_INVALID_VERSION)
            else
                r.major *= Int16(10)
                r.major += Int16(ch - '0')
                @errorif(r.major > 999, HPE_INVALID_VERSION)
            end

        #= first digit of minor HTTP version =#
        elseif p_state == s_req_first_http_minor
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(!isnum(ch), HPE_INVALID_VERSION)
            r.minor = Int16(ch - '0')
            p_state = s_req_http_minor

        #= minor HTTP version or end of request line =#
        elseif p_state == s_req_http_minor
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == CR
                p_state = s_req_line_almost_done
            elseif ch == LF
                p_state = s_header_field_start
            else
                #= XXX allow spaces after digit? =#
                @errorif(!isnum(ch), HPE_INVALID_VERSION)
                r.minor *= Int16(10)
                r.minor += Int16(ch - '0')
                @errorif(r.minor > 999, HPE_INVALID_VERSION)
            end

        #= end of request line =#
        elseif p_state == s_req_line_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(ch != LF, HPE_LF_EXPECTED)
            p_state = s_header_field_start

        elseif p_state == s_header_field_start
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == CR
                p_state = s_headers_almost_done
            elseif ch == LF
                #= they might be just sending \n instead of \r\n so this would be
                 * the second \n to denote the end of headers=#
                p_state = s_headers_almost_done
                @goto reexecute
            else
                c = (!strict && ch == ' ') ? ' ' : tokens[Int(ch)+1]
                @errorif(c == Char(0), HPE_INVALID_HEADER_TOKEN)
                header_field_mark = header_field_end_mark = p
                parser.index = 1
                issetcookie = false
                p_state = s_header_field

                if c == 'c'
                    parser.header_state = h_C
                elseif c == 'p'
                    parser.header_state = h_matching_proxy_connection
                elseif c == 't'
                    parser.header_state = h_matching_transfer_encoding
                elseif c == 'u'
                    parser.header_state = h_matching_upgrade
                elseif c == 's'
                    parser.header_state = h_matching_setcookie
                else
                    parser.header_state = h_general
                end
            end

        elseif p_state == s_header_field
            @debug(PARSING_DEBUG, "parsing header_field")
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            start = p
            while p <= len
                ch = Char(bytes[p])
                @debug(PARSING_DEBUG, Base.escape_string(string(ch)))
                c = (!strict && ch == ' ') ? ' ' : tokens[Int(ch)+1]
                c == Char(0) && break
                h = parser.header_state
                if h == h_general
                    @debug(PARSING_DEBUG, parser.header_state)

                elseif h == h_C
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    parser.header_state = c == 'o' ? h_CO : h_general
                elseif h == h_CO
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    parser.header_state = c == 'n' ? h_CON : h_general
                elseif h == h_CON
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if c == 'n'
                        parser.header_state = h_matching_connection
                    elseif c == 't'
                        parser.header_state = h_matching_content_length
                    else
                        parser.header_state = h_general
                    end
                #= connection =#
                elseif h == h_matching_connection
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(CONNECTION) || c != CONNECTION[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(CONNECTION)
                        parser.header_state = h_connection
                    end
                #= proxy-connection =#
                elseif h == h_matching_proxy_connection
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(PROXY_CONNECTION) || c != PROXY_CONNECTION[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(PROXY_CONNECTION)
                        parser.header_state = h_connection
                    end
                #= content-length =#
                elseif h == h_matching_content_length
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(CONTENT_LENGTH) || c != CONTENT_LENGTH[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(CONTENT_LENGTH)
                        parser.header_state = h_content_length
                    end
                #= transfer-encoding =#
                elseif h == h_matching_transfer_encoding
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(TRANSFER_ENCODING) || c != TRANSFER_ENCODING[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(TRANSFER_ENCODING)
                        parser.header_state = h_transfer_encoding
                    end
                #= upgrade =#
                elseif h == h_matching_upgrade
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(UPGRADE) || c != UPGRADE[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(UPGRADE)
                        parser.header_state = h_upgrade
                    end
                #= set-cookie =#
                elseif h == h_matching_setcookie
                    @debug(PARSING_DEBUG, parser.header_state)
                    parser.index += 1
                    if parser.index > length(SETCOOKIE) || c != SETCOOKIE[parser.index]
                        parser.header_state = h_general
                    elseif parser.index == length(SETCOOKIE)
                        parser.header_state = h_general
                        issetcookie = true
                    end
                elseif h in (h_connection, h_content_length, h_transfer_encoding, h_upgrade)
                    if ch != ' '
                        parser.header_state = h_general
                    end
                else
                    error("Unknown header_state")
                end
                p += 1
            end

            @nread(p - start)

            if p == len
                p -= 1
                @goto breakout
            end
            if ch == ':'
                p_state = s_header_value_discard_ws
                parser.state = p_state
                header_field_end_mark = p
                onheaderfield(parser, bytes, header_field_mark, p - 1)
                header_field_mark = 0
            else
                @err(HPE_INVALID_HEADER_TOKEN)
            end

        elseif p_state == s_header_value_discard_ws
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            (ch == ' ' || ch == '\t') && @goto breakout
            if ch == CR
                p_state = s_header_value_discard_ws_almost_done
                @goto breakout
            end
            if ch == LF
                p_state = s_header_value_discard_lws
                @goto breakout
            end
            @goto s_header_value_start_label
        #= FALLTHROUGH =#
        elseif p_state == s_header_value_start
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @label s_header_value_start_label
            header_value_mark = p
            p_state = s_header_value
            parser.index = 1
            c = lower(ch)

            if parser.header_state == h_upgrade
                parser.flags |= F_UPGRADE
                parser.header_state = h_general
            elseif parser.header_state == h_transfer_encoding
                #= looking for 'Transfer-Encoding: chunked' =#
                parser.header_state =  ifelse(c == 'c', h_matching_transfer_encoding_chunked, h_general)

            elseif parser.header_state == h_content_length
                @errorif(!isnum(ch), HPE_INVALID_CONTENT_LENGTH)
                @errorif((parser.flags & F_CONTENTLENGTH > 0) != 0, HPE_UNEXPECTED_CONTENT_LENGTH)
                parser.flags |= F_CONTENTLENGTH
                parser.content_length = UInt64(ch - '0')

            elseif parser.header_state == h_connection
                #= looking for 'Connection: keep-alive' =#
                if c == 'k'
                    parser.header_state = h_matching_connection_keep_alive
                #= looking for 'Connection: close' =#
                elseif c == 'c'
                    parser.header_state = h_matching_connection_close
                elseif c == 'u'
                    parser.header_state = h_matching_connection_upgrade
                else
                    parser.header_state = h_matching_connection_token
                end
            #= Multi-value `Connection` header =#
            elseif parser.header_state == h_matching_connection_token_start
            else
              parser.header_state = h_general
            end

        elseif p_state == s_header_value
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            start = p
            h = parser.header_state
            while p <= len
                @inbounds ch = Char(bytes[p])
                @debug(PARSING_DEBUG, Base.escape_string(string('\'', ch, '\'')))
                @debug(PARSING_DEBUG, lenient)
                @debug(PARSING_DEBUG, isheaderchar(ch))
                if ch == CR
                    p_state = s_header_almost_done
                    parser.header_state = h
                    parser.state = p_state
                    @debug(PARSING_DEBUG, "onheadervalue 1")
                    onheadervalue(parser, r, bytes, header_value_mark, p - 1, issetcookie, host, KEY)
                    header_value_mark = 0
                    break
                elseif ch == LF
                    p_state = s_header_almost_done
                    @nread(p - start)
                    parser.header_state = h
                    parser.state = p_state
                    @debug(PARSING_DEBUG, "onheadervalue 2")
                    onheadervalue(parser, r, bytes, header_value_mark, p - 1, issetcookie, host, KEY)
                    header_value_mark = 0
                    @goto reexecute
                elseif !lenient && !isheaderchar(ch)
                    @err(HPE_INVALID_HEADER_TOKEN)
                end

                c = lower(ch)

                if h == h_general
                    @debug(PARSING_DEBUG, parser.header_state)
                    limit = len - p
                    limit = min(limit, maxheader)
                    ptr = pointer(bytes, p)
                    @debug(PARSING_DEBUG, Base.escape_string(string('\'', Char(bytes[p]), '\'')))
                    p_cr = ccall(:memchr, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), ptr, CR, limit)
                    p_lf = ccall(:memchr, Ptr{Void}, (Ptr{Void}, Cint, Csize_t), ptr, LF, limit)
                    @debug(PARSING_DEBUG, limit)
                    @debug(PARSING_DEBUG, Int(p_cr))
                    @debug(PARSING_DEBUG, Int(p_lf))
                    if p_cr != C_NULL
                        if p_lf != C_NULL && p_cr >= p_lf
                            @debug(PARSING_DEBUG, Base.escape_string(string('\'', Char(bytes[p + Int(p_lf - ptr + 1)]), '\'')))
                            p += Int(p_lf - ptr)
                        else
                            @debug(PARSING_DEBUG, Base.escape_string(string('\'', Char(bytes[p + Int(p_cr - ptr + 1)]), '\'')))
                            p += Int(p_cr - ptr)
                        end
                    elseif p_lf != C_NULL
                        @debug(PARSING_DEBUG, Base.escape_string(string('\'', Char(bytes[p + Int(p_lf - ptr + 1)]), '\'')))
                        p += Int(p_lf - ptr)
                    else
                        @debug(PARSING_DEBUG, Base.escape_string(string('\'', Char(bytes[len]), '\'')))
                        p = len + 1
                    end
                    p -= 1

                elseif h == h_connection || h == h_transfer_encoding
                    error("Shouldn't get here.")
                elseif h == h_content_length
                    t = UInt64(0)
                    if ch == ' '
                    else
                        if !isnum(ch)
                            parser.header_state = h
                            @err(HPE_INVALID_CONTENT_LENGTH)
                        end
                        t = parser.content_length
                        t *= UInt64(10)
                        t += UInt64(ch - '0')

                        #= Overflow? Test against a conservative limit for simplicity. =#
                        @debug(PARSING_DEBUG, "this content_length 1")
                        @debug(PARSING_DEBUG, Int(parser.content_length))
                        if div(ULLONG_MAX - 10, 10) < t
                            parser.header_state = h
                            @err(HPE_INVALID_CONTENT_LENGTH)
                        elseif t > maxbody
                            @err(HPE_BODY_OVERFLOW)
                        end
                        parser.content_length = t
                     end

                #= Transfer-Encoding: chunked =#
                elseif h == h_matching_transfer_encoding_chunked
                    parser.index += 1
                    if parser.index > length(CHUNKED) || c != CHUNKED[parser.index]
                        h = h_general
                    elseif parser.index == length(CHUNKED)
                        h = h_transfer_encoding_chunked
                    end

                elseif h == h_matching_connection_token_start
                    #= looking for 'Connection: keep-alive' =#
                    if c == 'k'
                        h = h_matching_connection_keep_alive
                    #= looking for 'Connection: close' =#
                    elseif c == 'c'
                        h = h_matching_connection_close
                    elseif c == 'u'
                        h = h_matching_connection_upgrade
                    elseif tokens[Int(c)+1] > '\0'
                        h = h_matching_connection_token
                    elseif c == ' ' || c == '\t'
                    #= Skip lws =#
                    else
                        h = h_general
                    end
                #= looking for 'Connection: keep-alive' =#
                elseif h == h_matching_connection_keep_alive
                    parser.index += 1
                    if parser.index > length(KEEP_ALIVE) || c != KEEP_ALIVE[parser.index]
                        h = h_matching_connection_token
                    elseif parser.index == length(KEEP_ALIVE)
                        h = h_connection_keep_alive
                    end

                #= looking for 'Connection: close' =#
                elseif h == h_matching_connection_close
                    parser.index += 1
                    if parser.index > length(CLOSE) || c != CLOSE[parser.index]
                        h = h_matching_connection_token
                    elseif parser.index == length(CLOSE)
                        h = h_connection_close
                    end

                #= looking for 'Connection: upgrade' =#
                elseif h == h_matching_connection_upgrade
                    parser.index += 1
                    if parser.index > length(UPGRADE) || c != UPGRADE[parser.index]
                        h = h_matching_connection_token
                    elseif parser.index == length(UPGRADE)
                        h = h_connection_upgrade
                    end

                elseif h == h_matching_connection_token
                    if ch == ','
                        h = h_matching_connection_token_start
                        parser.index = 1
                    end

                elseif h == h_transfer_encoding_chunked
                    if ch != ' '
                        h = h_general
                    end

                elseif h in (h_connection_keep_alive, h_connection_close, h_connection_upgrade)
                    if ch == ','
                        if h == h_connection_keep_alive
                            parser.flags |= F_CONNECTION_KEEP_ALIVE
                        elseif h == h_connection_close
                            parser.flags |= F_CONNECTION_CLOSE
                        elseif h == h_connection_upgrade
                            parser.flags |= F_CONNECTION_UPGRADE
                        end
                        h = h_matching_connection_token_start
                        parser.index = 1
                    elseif ch != ' '
                        h = h_matching_connection_token
                    end

                else
                    p_state = s_header_value
                    h = h_general
                end
                p += 1
            end
            parser.header_state = h

            @nread(p - start)

            if p == len
                p -= 1
            end

        elseif p_state == s_header_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @errorif(ch != LF, HPE_LF_EXPECTED)
            p_state = s_header_value_lws

        elseif p_state == s_header_value_lws
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == ' ' || ch == '\t'
                p_state = s_header_value_start
                @goto reexecute
            end
            #= finished the header =#
            if parser.header_state == h_connection_keep_alive
                parser.flags |= F_CONNECTION_KEEP_ALIVE
            elseif parser.header_state == h_connection_close
                parser.flags |= F_CONNECTION_CLOSE
            elseif parser.header_state == h_transfer_encoding_chunked
                parser.flags |= F_CHUNKED
            elseif parser.header_state == h_connection_upgrade
                parser.flags |= F_CONNECTION_UPGRADE
            end

            p_state = s_header_field_start
            @goto reexecute

        elseif p_state == s_header_value_discard_ws_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != LF)
            p_state = s_header_value_discard_lws

        elseif p_state == s_header_value_discard_lws
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            if ch == ' ' || ch == '\t'
                p_state = s_header_value_discard_ws
            else
                if parser.header_state == h_connection_keep_alive
                    parser.flags |= F_CONNECTION_KEEP_ALIVE
                elseif parser.header_state == h_connection_close
                    parser.flags |= F_CONNECTION_CLOSE
                elseif parser.header_state == h_connection_upgrade
                    parser.flags |= F_CONNECTION_UPGRADE
                elseif parser.header_state == h_transfer_encoding_chunked
                    parser.flags |= F_CHUNKED
                end

                #= header value was empty =#
                header_value_mark = p
                p_state = s_header_field_start
                parser.state = p_state
                @debug(PARSING_DEBUG, "onheadervalue 3")
                onheadervalue(parser, r, bytes, header_value_mark, p - 1, issetcookie, host, KEY)
                header_value_mark = 0
                @goto reexecute
            end

        elseif p_state == s_headers_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != LF)
            if (parser.flags & F_TRAILING) > 0
                #= End of a chunked request =#
                p_state = s_message_done
                @goto reexecute
            end

            #= Cannot use chunked encoding and a content-length header together
            per the HTTP specification. =#
            @errorif((parser.flags & F_CHUNKED) > 0 && (parser.flags & F_CONTENTLENGTH) > 0, HPE_UNEXPECTED_CONTENT_LENGTH)

            p_state = s_headers_done

            #= Set this here so that on_headers_complete() callbacks can see it =#
            @debug(PARSING_DEBUG, "checking for upgrade...")
            if (parser.flags & F_UPGRADE > 0) && (parser.flags & F_CONNECTION_UPGRADE > 0)
                upgrade = typeof(r) == Request || r.status == 101
            else
                upgrade = typeof(r) == Request && r.method == CONNECT
            end
            @debug(PARSING_DEBUG, upgrade)
            #= Here we call the headers_complete callback. This is somewhat
            * different than other callbacks because if the user returns 1, we
            * will interpret that as saying that this message has no body. This
            * is needed for the annoying case of recieving a response to a HEAD
            * request.
            *
            * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
            * we have to simulate it by handling a change in errno below.
            =#
            onheaderscomplete(r)
            headersdone = true
            if method == HEAD
                parser.flags |= F_SKIPBODY
            elseif method == CONNECT
                upgrade = true
            end
            @goto reexecute

        elseif p_state == s_headers_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            @strictcheck(ch != LF)

            parser.nread = UInt32(0)

            hasBody = parser.flags & F_CHUNKED > 0 ||
                (parser.content_length > 0 && parser.content_length != ULLONG_MAX)
            if upgrade && ((typeof(r) == Request && r.method == CONNECT) ||
                                  (parser.flags & F_SKIPBODY) > 0 || !hasBody)
                #= Exit, the rest of the message is in a different protocol. =#
                p_state = ifelse(http_should_keep_alive(parser, r), start_state, s_dead)
                parser.state = p_state
                onmessagecomplete(r)
                @debug(PARSING_DEBUG, "this 1")
                return errno, true, true, String(bytes[p+1:end])
            end

            if parser.flags & F_SKIPBODY > 0
                p_state = ifelse(http_should_keep_alive(parser, r), start_state, s_dead)
                parser.state = p_state
                onmessagecomplete(r)
                @debug(PARSING_DEBUG, "this 2")
                return errno, true, true, ""
            elseif parser.flags & F_CHUNKED > 0
                #= chunked encoding - ignore Content-Length header =#
                p_state = s_chunk_size_start
            else
                if parser.content_length == 0
                    #= Content-Length header given but zero: Content-Length: 0\r\n =#
                    p_state = ifelse(http_should_keep_alive(parser, r), start_state, s_dead)
                    parser.state = p_state
                    onmessagecomplete(r)
                    @debug(PARSING_DEBUG, "this 3")
                    return errno, true, true, ""
                elseif parser.content_length != ULLONG_MAX
                    #= Content-Length header given and non-zero =#
                    p_state = s_body_identity
                    @debug(PARSING_DEBUG, ParsingStateCode(p_state))
                else
                    if !http_message_needs_eof(parser, r)
                        #= Assume content-length 0 - read the next =#
                        p_state = ifelse(http_should_keep_alive(parser, r), start_state, s_dead)
                        parser.state = p_state
                        onmessagecomplete(r)
                        @debug(PARSING_DEBUG, "this 4")
                        return errno, true, true, String(bytes[p+1:end])
                    else
                        #= Read body until EOF =#
                        p_state = s_body_identity_eof
                        @debug(PARSING_DEBUG, ParsingStateCode(p_state))
                    end
                end
            end

        elseif p_state == s_body_identity
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            to_read = UInt64(min(parser.content_length, len - p + 1))
            to_read > maxbody && @err(HPE_BODY_OVERFLOW)
            assert(parser.content_length != 0 && parser.content_length != ULLONG_MAX)

            #= The difference between advancing content_length and p is because
            * the latter will automaticaly advance on the next loop iteration.
            * Further, if content_length ends up at 0, we want to see the last
            * byte again for our message complete callback.
            =#
            body_mark = p
            parser.content_length -= to_read
            p += Int(to_read) - 1

            if parser.content_length == 0
                p_state = s_message_done

                #= Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
                *
                * The alternative to doing this is to wait for the next byte to
                * trigger the data callback, just as in every other case. The
                * problem with this is that this makes it difficult for the test
                * harness to distinguish between complete-on-EOF and
                * complete-on-length. It's not clear that this distinction is
                * important for applications, but let's keep it for now.
                =#
                @debug(PARSING_DEBUG, "this onbody 1")
                onbody(r, maintask, bytes, body_mark, p)
                body_mark = 0
                @goto reexecute
            end

        #= read until EOF =#
        elseif p_state == s_body_identity_eof
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            body_mark = p
            p = len

        elseif p_state == s_message_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            # p_state = ifelse(http_should_keep_alive(parser, r), start_state, s_dead)
            parser.state = p_state
            onmessagecomplete(r)
            @debug(PARSING_DEBUG, "this 5")
            if upgrade
                #= Exit, the rest of the message is in a different protocol. =#
                parser.state = p_state
                return errno, true, true, String(bytes[p+1:end])
            end

        elseif p_state == s_chunk_size_start
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.nread == 1)
            assert(parser.flags & F_CHUNKED > 0)

            unhex_val = unhex[Int(ch)+1]
            @errorif(unhex_val == -1, HPE_INVALID_CHUNK_SIZE)

            parser.content_length = unhex_val
            p_state = s_chunk_size

        elseif p_state == s_chunk_size
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.flags & F_CHUNKED > 0)
            if ch == CR
                p_state = s_chunk_size_almost_done
            else
                unhex_val = unhex[Int(ch)+1]
                @debug(PARSING_DEBUG, unhex_val)
                if unhex_val == -1
                    if ch == ';' || ch == ' '
                        p_state = s_chunk_parameters
                        @goto breakout
                    end
                    @err(HPE_INVALID_CHUNK_SIZE)
                end
                t = parser.content_length
                t *= UInt64(16)
                t += UInt64(unhex_val)

                #= Overflow? Test against a conservative limit for simplicity. =#
                @debug(PARSING_DEBUG, "this content_length 2")
                @debug(PARSING_DEBUG, Int(parser.content_length))
                if div(ULLONG_MAX - 16, 16) < t
                    @err(HPE_INVALID_CONTENT_LENGTH)
                end
                parser.content_length = t
            end

        elseif p_state == s_chunk_parameters
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.flags & F_CHUNKED > 0)
            #= just ignore this shit. TODO check for overflow =#
            if ch == CR
                p_state = s_chunk_size_almost_done
            end

        elseif p_state == s_chunk_size_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.flags & F_CHUNKED > 0)
            @strictcheck(ch != LF)

            parser.nread = 0

            if parser.content_length == 0
                parser.flags |= F_TRAILING
                p_state = s_header_field_start
            else
                p_state = s_chunk_data
            end

        elseif p_state == s_chunk_data
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            to_read = UInt64(min(parser.content_length, len - p + 1))

            assert(parser.flags & F_CHUNKED > 0)
            assert(parser.content_length != 0 && parser.content_length != ULLONG_MAX)

            #= See the explanation in s_body_identity for why the content
            * length and data pointers are managed this way.
            =#
            body_mark = p
            parser.content_length -= to_read
            p += Int(to_read) - 1

            if parser.content_length == 0
                p_state = s_chunk_data_almost_done
            end

        elseif p_state == s_chunk_data_almost_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.flags & F_CHUNKED > 0)
            assert(parser.content_length == 0)
            @strictcheck(ch != CR)
            p_state = s_chunk_data_done
            @debug(PARSING_DEBUG, "this onbody 2")
            body_mark > 0 && onbody(r, maintask, bytes, body_mark, p - 1)
            body_mark = 0

        elseif p_state == s_chunk_data_done
            @debug(PARSING_DEBUG, ParsingStateCode(p_state))
            assert(parser.flags & F_CHUNKED > 0)
            @strictcheck(ch != LF)
            parser.nread = 0
            p_state = s_chunk_size_start

        else
            error("unhandled state")
        end
        @label breakout
        p += 1
    end

    #= Run callbacks for any marks that we have leftover after we ran our of
     * bytes. There should be at most one of these set, so it's OK to invoke
     * them in series (unset marks will not result in callbacks).
     *
     * We use the NOADVANCE() variety of callbacks here because 'p' has already
     * overflowed 'data' and this allows us to correct for the off-by-one that
     * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
     * value that's in-bounds).
     =#

    assert(((header_field_mark > 0 ? 1 : 0) +
            (header_value_mark > 0 ? 1 : 0) +
            (url_mark > 0 ? 1 : 0)  +
            (body_mark > 0 ? 1 : 0) +
            (status_mark > 0 ? 1 : 0)) <= 1)

    header_field_mark > 0 && onheaderfield(parser, bytes, header_field_mark, min(len, p))
    @debug(PARSING_DEBUG, "onheadervalue 4")
    @debug(PARSING_DEBUG, len)
    @debug(PARSING_DEBUG, p)
    header_value_mark > 0 && onheadervalue(parser, bytes, header_value_mark, min(len, p))
    url_mark > 0 && (min(len, p) - url_mark > maxuri) && @err(HPE_URI_OVERFLOW)
    url_mark > 0 && onurl(r, bytes, url_mark, min(len, p))
    @debug(PARSING_DEBUG, "this onbody 3")
    body_mark > 0 && onbody(r, maintask, bytes, body_mark, min(len, p - 1))
    status_mark > 0 && onstatus(r)

    parser.state = p_state
    @debug(PARSING_DEBUG, "exiting maybe unfinished...")
    @debug(PARSING_DEBUG, ParsingStateCode(p_state))
    b = p_state == start_state || p_state == s_dead
    he = b | (headersdone || p_state >= s_headers_done)
    m = b | (p_state >= s_body_identity_eof)
    return errno, he, m, String(bytes[p:end])

    @label error
    if errno == HPE_OK
        errno = HPE_UNKNOWN
    end

    parser.state = s_start_req_or_res
    parser.header_state = 0x00
    @debug(PARSING_DEBUG, "exiting due to error...")
    @debug(PARSING_DEBUG, errno)
    return errno, false, false, ""
end

#= Does the parser need to see an EOF to find the end of the message? =#
http_message_needs_eof(parser, r::Request) = false
function http_message_needs_eof(parser, r::Response)
    #= See RFC 2616 section 4.4 =#
    if (div(r.status, 100) == 1 || #= 1xx e.g. Continue =#
        r.status == 204 ||     #= No Content =#
        r.status == 304 ||     #= Not Modified =#
        parser.flags & F_SKIPBODY > 0)       #= response to a HEAD request =#
        return false
    end

    if (parser.flags & F_CHUNKED > 0) || parser.content_length != ULLONG_MAX
        return false
    end

    return true
end

function http_should_keep_alive(parser, r)
    if r.major > 0 && r.minor > 0
        #= HTTP/1.1 =#
        if parser.flags & F_CONNECTION_CLOSE > 0
            return false
        end
    else
        #= HTTP/1.0 or earlier =#
        if !(parser.flags & F_CONNECTION_KEEP_ALIVE > 0)
            return false
        end
    end

  return !http_message_needs_eof(parser, r)
end


text:
using NCDatasets
using CFTime
using Dates

output_file = "forcing.nc"
domain_file = "domain.ocn_aqua.fv4x5_gx3v7.091218.nc"

#output_file = "forcing_gx1v6.nc"
#domain_file = "CESM_domains/domain.ocn.gx1v6.090206.nc"

z_w = collect(Float64, 0:-10:-350)

Dataset(domain_file, "r") do ds
    global Nx = ds.dim["ni"]
    global Ny = ds.dim["nj"]
    global lat = ds["yc"][:]
    global lon = ds["xc"][:]
    global mask = convert(Array{Float64}, replace(ds["mask"][:], missing=>NaN))
end

Nz = length(z_w) - 1

dom = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
sum(dom) == 365 || throw(ErrorException("Sum is not 365"))

beg_of_mon = zeros(Float64, 12)
for m = 2:12
    beg_of_mon[m] = beg_of_mon[m-1] + dom[m-1]
end

mid_of_mon = [ beg_of_mon[m] + dom[m]/2.0 for m=1:12 ]

HMXL = zeros(Float64, Nx, Ny, 12)
TEMP = zeros(Float64, Nx, Ny, Nz, 12)
SALT = zeros(Float64, Nx, Ny, Nz, 12)
QFLX_TEMP = zeros(Float64, Nx, Ny, Nz, 12)
QFLX_SALT = zeros(Float64, Nx, Ny, Nz, 12)

for t=1:12

    c = cos.(deg2rad.(lat))
    HMXL[:, :, t] = 50.0 .+ 25.0 * sin.(2*deg2rad.(lon) .+ deg2rad.(lat) .- 2π/365 * mid_of_mon[t])
    TEMP[:, :, :, t] = ( 20.0 .+
         5.0 * reshape( c .* (1.0 .+ sin.(2*deg2rad.(lon) .+ deg2rad.(lat) .- 2π/365 * mid_of_mon[t]) ) / 2.0, Nx, Ny, 1 ) .* reshape(exp.(z_w[1:end-1] / 100.0), 1, 1, :)
    )
    SALT[:, :, :, t] = ( 34.0 .+
         5.0 * reshape( c .* (1.0 .+ sin.(2*deg2rad.(lon) .+ deg2rad.(lat) .- 2π/365 * mid_of_mon[t]) ) / 2.0, Nx, Ny, 1 ) .* reshape(exp.(z_w[1:end-1] / 100.0), 1, 1, :)
    )

    QFLX_TEMP[:, :, :, t] = ( 15 .+
         5.0 * reshape( c .* (1.0 .+ sin.(5*deg2rad.(lon) .+ 2*deg2rad.(lat) .- 5π/365 * mid_of_mon[t]) ) / 2.0, Nx, Ny, 1 ) .* reshape(exp.(z_w[1:end-1] / 100.0), 1, 1, :)
    )

    QFLX_SALT[:, :, :, t] = ( 5 .+
         5.0 * reshape( c .* (1.0 .+ sin.(5*deg2rad.(lon) .+ 2*deg2rad.(lat) .- 5π/365 * mid_of_mon[t]) ) / 2.0, Nx, Ny, 1 ) .* reshape(exp.(z_w[1:end-1] / 100.0), 1, 1, :)
    )

end

Dataset(output_file, "c") do ds

    defDim(ds, "time", Inf)
    defDim(ds, "nlon", Nx)
    defDim(ds, "nlat", Ny)
    defDim(ds, "z_t",  Nz)

    for (varname, vardata, vardim, attrib) in [

        ("time", mid_of_mon, ("time",), Dict(
            "units"     => "days since 0001-01-01 00:00:00",
            "calendar"  => "noleap",
            "long_name" => "total seaice area",
        )),

        ("HMXL", HMXL, ("nlon", "nlat", "time",), Dict(
            "units"     => "m",
            "long_name" => "Mixed layer depth",
        )),

        ("TEMP", TEMP, ("nlon", "nlat", "z_t", "time",), Dict(
            "units"     => "degC",
            "long_name" => "Temperature",
        )),

        ("SALT", SALT, ("nlon", "nlat", "z_t", "time",), Dict(
            "units"     => "PSU",
            "long_name" => "Salinity",
        )),

        ("QFLX_TEMP", QFLX_TEMP, ("nlon", "nlat", "z_t", "time",), Dict(
            "units"     => "degC",
            "long_name" => "Temperature QFLX",
        )),

        ("QFLX_SALT", QFLX_SALT, ("nlon", "nlat", "z_t", "time",), Dict(
            "units"     => "degC",
            "long_name" => "Salinity QFLX",
        )),

    ]

        var = defVar(ds, varname, Float64, vardim)
        var.attrib["_FillValue"] = 1e20

        var = ds[varname]

        for (k, v) in attrib
            var.attrib[k] = v
        end

        println(var.attrib)

        rng = []
        for i in 1:length(vardim)-1
            push!(rng, Colon())
        end
        push!(rng, 1:size(vardata)[end])
        var[rng...] = vardata

    end

end

ext: jl

max_stars_repo_name: meteorologytoday/IOM

max_issues_repo_name: meteorologytoday/IOM

max_forks_repo_name: meteorologytoday/IOM


text:
using DataFrames
using Libz
"""
  parse_gtf_gene_features_to_df

  Arguments:
    filename: path to gtf file
    gene_ids: An array of gene_ids - if an empty array, then everything is parsed.
    feature_types: Set of feature types - e.g.  Set({"gene"})

  Returns:

"""
function parse_gtf_gene_features_to_df(filename, gene_ids, feature_types)
    gene_id_pat   = r"gene_id\s+\"?([^\s]+)\";"
    header_pat    = r"^#" #<-bug2
    gene_name_pat = r"gene_name\s+\"?([^\s]+)\";"
    gene_type_pat = r"gene_type\s+\"?([^\s]+)\";"
    features_df =DataFrame(seq_id=String[], start_pos=Int[],stop_pos=Int[],
                           strand=String[], gene_id=String[],
                           feature_type=String[], gene_name=String[],gene_type=String[])
    line_count = 0
    io=open(filename)
    if ismatch(Regex(".gz\$"),filename)
        io=ZlibInflateInputStream(io,reset_on_end=true)
    end

    for line in eachline(io)
        if ismatch(header_pat,line)
          continue
        end
        row = split(line, '\t')
        feature_type = row[3]
        if !(feature_type in feature_types)
           continue
        end
        m = match(gene_id_pat, row[9])
        @assert m != nothing
        row_gene_id = m.captures[1]

        match_gene_name = match(gene_name_pat, row[9])
        row_gene_name = match_gene_name.captures[1]

        match_gene_type = match(gene_type_pat, row[9])
        row_gene_type = match_gene_type.captures[1]

        if length(gene_ids) > 0
           if !(row_gene_id in gene_ids)
               continue
           end
       end
       #println("Got $row_gene_id")
       push!(features_df, [row[1],parse(Int64,row[4]), parse(Int64,row[5]),row[7], row_gene_id,feature_type,row_gene_name,row_gene_type])
    end
    return features_df
end


text:
# Autogenerated wrapper script for difmap_jll for i686-linux-gnu-libgfortran5
export difmap

using PGPLOT_jll
using Xorg_libX11_jll
using Ncurses_jll
using CompilerSupportLibraries_jll
## Global variables
PATH = ""
LIBPATH = ""
LIBPATH_env = "LD_LIBRARY_PATH"
LIBPATH_default = ""

# Relative path to `difmap`
const difmap_splitpath = ["bin", "difmap"]

# This will be filled out by __init__() for all products, as it must be done at runtime
difmap_path = ""

# difmap-specific global declaration
function difmap(f::Function; adjust_PATH::Bool = true, adjust_LIBPATH::Bool = true)
    global PATH, LIBPATH
    env_mapping = Dict{String,String}()
    if adjust_PATH
        if !isempty(get(ENV, "PATH", ""))
            env_mapping["PATH"] = string(PATH, ':', ENV["PATH"])
        else
            env_mapping["PATH"] = PATH
        end
    end
    if adjust_LIBPATH
        LIBPATH_base = get(ENV, LIBPATH_env, expanduser(LIBPATH_default))
        if !isempty(LIBPATH_base)
            env_mapping[LIBPATH_env] = string(LIBPATH, ':', LIBPATH_base)
        else
            env_mapping[LIBPATH_env] = LIBPATH
        end
    end
    withenv(env_mapping...) do
        f(difmap_path)
    end
end

"""
Open all libraries
"""
function __init__()
    global artifact_dir = abspath(artifact"difmap")

    # Initialize PATH and LIBPATH environment variable listings
    global PATH_list, LIBPATH_list
    # From the list of our dependencies, generate a tuple of all the PATH and LIBPATH lists,
    # then append them to our own.
    foreach(p -> append!(PATH_list, p), (PGPLOT_jll.PATH_list, Xorg_libX11_jll.PATH_list, Ncurses_jll.PATH_list, CompilerSupportLibraries_jll.PATH_list,))
    foreach(p -> append!(LIBPATH_list, p), (PGPLOT_jll.LIBPATH_list, Xorg_libX11_jll.LIBPATH_list, Ncurses_jll.LIBPATH_list, CompilerSupportLibraries_jll.LIBPATH_list,))

    global difmap_path = normpath(joinpath(artifact_dir, difmap_splitpath...))

    push!(PATH_list, dirname(difmap_path))
    # Filter out duplicate and empty entries in our PATH and LIBPATH entries
    filter!(!isempty, unique!(PATH_list))
    filter!(!isempty, unique!(LIBPATH_list))
    global PATH = join(PATH_list, ':')
    global LIBPATH = join(vcat(LIBPATH_list, [joinpath(Sys.BINDIR, Base.LIBDIR, "julia"), joinpath(Sys.BINDIR, Base.LIBDIR)]), ':')

end  # __init__()


text:
module ElasticFDA

## Requirements
using Interpolations
using Dierckx
using NLopt
using ProgressMeter
using Distributions

export
    smooth_data!,
    smooth_data,
    f_to_srsf,
    srsf_to_f,
    optimum_reparam,
    warp_q_gamma,
    warp_f_gamma,
    vert_fPCA,
    horiz_fPCA,
    gauss_model,
    group_warping_bayes,
    pair_warping_bayes,
    elastic_distance,
    elastic_regression,
    elastic_logistic,
    elastic_mlogistic,
    elastic_prediction,
    srsf_align,
    align_fPCA,
    trapz,
    rgam,
    curve_karcher_mean,
    curve_srvf_align,
    curve_principal_directions,
    curve_karcher_cov,
    sample_shapes,
    curve_to_q,
    q_to_curve,
    calc_shape_dist,
    curve_pair_align,
    curve_geodesic,
    resamplecurve,
    reparam_image,
    pair_align_image,
    bs,
    project_curve,
    @cpp

# load fdasrsf library
if (is_unix())
    path = "../deps/src/fdasrsf/fdasrsf"
else
    path = "..\\deps\\fdasrsf"
end
const libfdasrsf = joinpath(dirname(@__FILE__), path)

# load gropt library
if (is_unix())
    path1 = "../deps/src/gropt/gropt"
else
    path1 = "..\\deps\\gropt"
end
const libgropt = joinpath(dirname(@__FILE__), path1)

# load fdaqmap library
if (is_unix())
    path2 = "../deps/src/fdaqmap/fdaqmap"
else
    path2 = "..\\deps\\fdaqmap"
end
const libfdaqmap = joinpath(dirname(@__FILE__), path2)

# Ensure library is available.
function __init__()
    if (Libdl.dlopen_e(libfdasrsf) == C_NULL)
        error("libfdasrsf not properly installed. Run Pkg.build(\"ElasticFDA\")")
    end

    if (Libdl.dlopen_e(libgropt) == C_NULL)
        error("libgropt not properly installed. Run Pkg.build(\"ElasticFDA\")")
    end

    if (Libdl.dlopen_e(libfdaqmap) == C_NULL)
        error("libfdaqmap not properly installed. Run Pkg.build(\"ElasticFDA\")")
    end

end

# CPP.jl workaround not compiling on windows
# the following macro was copied from https://github.com/timholy/Cpp.jl with the
# following modifications:
# 1) Update const for new julia v0.4 warnings
# 2) Formating
# the licensce for the code is included in the package directory
# Useful references:
# http://www.agner.org/optimize/calling_conventions.pdf
# http://mentorembedded.github.io/cxx-abi/abi.html#mangling

const SUBSTITUTION = [""; map(string, collect(0:9)); map(string, collect('A':'Z'))];

# Allow calling of functions in C++ shared libraries.
# Usage: a = @cpp ccall((:mysymbol,mylib),...)
macro cpp(ex)
    # If you get "undefined symbol" errors, use nm or readelf to view
    # the names of the symbols in the library. Then use the resulting
    # information to help improve this macro!
    # Note: for global objects without class or namespace qualifiers
    # (e.g., global consts), you do not need to use this macro (it
    # will work with a plain ccall, even though it is a C++ library)
    msg = "@cpp requires a ccall((:mysymbol, mylib),...)  expression"
    if !isa(ex,Expr) || ex.head != :ccall
        error(msg)
    end

    # Parse the library symbol's name
    exlib = ex.args[1]
    if !(isa(exlib,Expr) && exlib.head == :tuple)
        error(msg)
    end
    sym = exlib.args[1]
    fstr = string(eval(sym))
    #GNU3-4 ABI
    fstr = string("_Z",length(fstr),fstr)

    # Parse the arguments to ccall and construct the parameter type string
    exargtypes = ex.args[3]
    if exargtypes.head != :tuple
        error(msg)
    end
    exargs = exargtypes.args
    pstr = ""
    symtable = (:Void,:Bool,:Cchar,:Char,:String,:Int,:Int8,:Uint8,:Int16,:Uint16,:Int32,:Cint,:Uint32,:Int64,:Uint64,:Float32,:Float64)
    # GNU3-4 ABI v.3 and v.4
    ptable =   ('v', 'b', 'c', 'w', "Pc", 'i', 'a', 'h', 's', 't', 'i', 'i', 'j', 'l', 'm', 'f', 'd')
    msub = String[]
    for iarg = 1:length(exargs)
        thisarg = exargs[iarg]
        thisargm = ""
        while isa(thisarg,Expr) && thisarg.head == :curly && thisarg.args[1] == :Ptr
            thisargm = string(thisargm,'P')
            thisarg = thisarg.args[2]
        end
        matched = false
        for isym = 1:length(symtable)
            if thisarg == symtable[isym]
                matched = true
                thisargm = string(thisargm, ptable[isym])
                # Cchar is a special notation just for name mangling,
                # convert back to :Int8
                if thisarg == :Cchar
                    ex.args[3].args[iarg] = :Int8
                end
                break
            end
        end
        if matched
            if length(thisargm) > 1
                # Use substitution
                idx = indexin([thisargm], msub)[1]
                if idx != 0
                    pstr = string(pstr, "S"*SUBSTITUTION[idx]*"_")
                else
                    push!(msub, thisargm)
                    pstr = string(pstr, thisargm)
                end
            else
                pstr = string(pstr, thisargm)
            end
        else
            println(thisarg)
            error("@cpp: argument not recognized")
        end
    end
    ex.args[1].args[1] = Expr(:quote, Symbol(string(fstr,pstr)))
    ex.args[1].args[2] = esc(ex.args[1].args[2])
    for ival = 4:length(ex.args)
        ex.args[ival] = esc(ex.args[ival])
    end
    ex
end

### source files
include("curve_funcs.jl")
include("curve_stats.jl")
include("srsf_funcs.jl")
include("misc_funcs.jl")
include("fPCA.jl")
include("bspline.jl")
include("dp_bayes.jl")
include("mcmc_align.jl")
include("gauss_model.jl")
include("group_warping_bayes.jl")
include("pair_warping_bayes.jl")
include("regression.jl")
include("regression_funcs.jl")
include("time_warping.jl")
include("image_funcs.jl")
include("image_stats.jl")

end # module


text:
using Documenter, GeneticVariation

makedocs(
    modules = [GeneticVariation],
    format = :html,
    sitename = "GeneticVariation.jl",
    pages = [
        "Home" => "index.md",
        "Manual" => [
            "I/O" => [
                "VCF and BCF formatted files" => "man/io/vcf-bcf.md"
            ],
            "Mutation Counting" => "man/site_counting.md",
            "Genetic Diversity" => "man/diversity.md"
        ]
    ],
    authors = "Kenta Sato, Ben J. Ward, The BioJulia Organisation and other contributors."
)

deploydocs(
    repo = "github.com/BioJulia/GeneticVariation.jl.git",
    julia = "1.0",
    osname = "linux",
    target = "build",
    deps = nothing,
    make = nothing
)


text:
using DataFrames, Gadfly, Distributions, Random

set_default_plot_size(3.5inch, 16inch)

Random.seed!(1234)

x = rand(Normal(), 100)
y = rand(Normal(10), 100)

# two numeric vectors
pl1 = plot(x=x, y=y, Stat.qq, Geom.point)

y = randn(100).+5
ds = fit.([Normal, LogNormal], [y])
df = [DataFrame(y=randn(100), g=g) for g in ["Sample1", "Sample2"]]

theme = Theme(discrete_highlight_color=c->nothing, alphas=[0.5],
    point_size=2pt, key_position=:inside)
yeqx(x=-3:3) = layer(x=x, Geom.abline(color="gray80"))
gck = Guide.colorkey(title="")

# Plot title describes plots
pl2 = plot(x=Normal(), y=randn(100), Stat.qq, yeqx, theme,
    Guide.title("1 sample, 1 Distribution"))
pl3 = plot(vcat(df...), x=Normal(), y=:y, Stat.qq, color=:g, yeqx,
    gck, theme, Guide.title("2 samples, 1 Distribution"))
pl4 = plot(x=ds, y=y, color=["Normal", "LogNormal"], Stat.qq, yeqx(3:8),
    gck, theme, Guide.title("1 sample, 2 Distributions"))

# Apply scales to Distributions
z = rand(Exponential(), 100)
pl5 = plot(x=z, y=Exponential(), Stat.qq, Geom.point)
pl6 = plot(x=log.(z), y=Exponential(), Stat.qq, Geom.point)
pl7 = plot(x=log.(z), y=Exponential(), Stat.qq, Geom.point, Scale.y_log)
pl8 = plot(x=z, y=Exponential(), Stat.qq, Geom.point, Scale.x_log, Scale.y_log)

# by analogy with Stat.func, computed (.y) aes should be scaled:
#plot(xmin=[1], xmax=[15], y=[exp], Stat.func, Geom.line)
# (y value is log(exp(x), or y=x)
#plot(xmin=[1], xmax=[15], y=[exp], Stat.func, Geom.line, Scale.y_log)

vstack(pl1, pl2, pl3, pl4, pl5, pl6, pl7, pl8)


text:
function imfilter(A, kern)
    size(kern,1) == size(kern,2) || throw(ArgumentError("Only square kernels supported, you provided $(size(kern))."))
    isodd(size(kern,1)) || throw(ArgumentError("Only odd-sized kernels supported, you provided $(size(kern))."))
    A2 = LocalFilters.convolve(A, kern)
    pad = size(kern, 1) ÷ 2
    @views A2[1:pad, :] .= A[1:pad, :]
    @views A2[end-pad+1:end, :] .= A[end-pad+1:end, :]
    @views A2[:, 1:pad] .= A[:, 1:pad]
    @views A2[:, end-pad+1:end] .= A[:, end-pad+1:end]
    A2
end

function gaussian2(n)
    t = range(-2,stop=2, length=n)
    k = exp.(-t.^2)
    K = k*k'
    K ./= sum(K)
end

function gaussian(n)
    t = range(-2,stop=2, length=n)
    K = [exp(-(x-y)^2) for x in t, y in t]
    K ./= sum(K)
end


text:
# # Some Radial Functions

# The **Gaussian** is defined by ``φ(ρ) = \exp \left( - (αρ)^2 \right)``, where
# ``α`` is a shape parameter to fine-tune the function.

"""
    Gaussian( α = 1 ) <: RadialFunction

A `RadialFunction` with
```math
    φ(ρ) = \\exp( - (α ρ)^2 ).
```
"""
@with_kw struct Gaussian{R<:Real} <: RadialFunction
    α :: R = 1
    @assert α > 0 "The shape parameter `α` must be positive."
end

function ( φ :: Gaussian )( ρ :: Real )
    exp( - (φ.α * ρ)^2 )
end

cpd_order( :: Gaussian ) = 0
df(φ :: Gaussian, ρ :: Real) = - 2 * φ.α^2 * ρ * φ( ρ )

# The **Multiquadric** is ``φ(ρ) = - \sqrt{ 1 + (αρ)^2 }`` and also has a positive shape
# parameter. We can actually generalize it to the following form:

"""
    Multiquadric( α = 1, β = 1//2 ) <: RadialFunction

A `RadialFunction` with
```math
    φ(ρ) = (-1)^{ \\lceil β \\rceil } ( 1 + (αρ)^2 )^β
```
"""
@with_kw struct Multiquadric{R<:Real,S<:Real} <: RadialFunction
    α :: R  = 1     # shape parameter
    β :: S  = 1//2  # exponent

    @assert α > 0 "The shape parameter `α` must be positive."
    @assert β % 1 != 0 "The exponent must not be an integer."
    @assert β > 0 "The exponent must be positive."
end

function ( φ :: Multiquadric )( ρ :: Real )
    (-1)^(ceil(Int, φ.β)) * ( 1 + (φ.α * ρ)^2 )^φ.β
end

cpd_order( φ :: Multiquadric ) = ceil( Int, φ.β )
df(φ :: Multiquadric, ρ :: Real ) = (-1)^(ceil(Int, φ.β)) * 2 * φ.α * φ.β * ρ * ( 1 + (φ.α * ρ)^2 )^(φ.β - 1)

# Related is the **Inverse Multiquadric** `` φ(ρ) = (1+(αρ)^2)^{-β}``:
"""
    InverseMultiquadric( α = 1, β = 1//2 ) <: RadialFunction

A `RadialFunction` with
```math
    φ(ρ) = ( 1 + (αρ)^2 )^{-β}
```
"""
@with_kw struct InverseMultiquadric{R<:Real,S<:Real} <: RadialFunction
    α :: R  = 1
    β :: S  = 1//2

    @assert α > 0 "The shape parameter `α` must be positive."
    @assert β > 0 "The exponent must be positive."
end

function ( φ :: InverseMultiquadric )( ρ :: Real )
   ( 1 + (φ.α * ρ)^2 )^(-φ.β)
end

cpd_order( :: InverseMultiquadric ) = 0
df(φ :: InverseMultiquadric, ρ :: Real ) = - 2 * φ.α^2 * φ.β * ρ * ( 1 + (φ.α * ρ)^2 )^(-φ.β - 1)

# The **Cubic** is ``φ(ρ) = ρ^3``.
# It can also be generalized:
"""
    Cubic( β = 3 ) <: RadialFunction

A `RadialFunction` with
```math
    φ(ρ) = (-1)^{ \\lceil β \\rceil /2 } ρ^β
```
"""
@with_kw struct Cubic <: RadialFunction
    β :: Int = 3

    @assert β > 0 "The exponent `β` must be positive."
    @assert β % 2 != 0 "The exponent `β` must not be an even number."
end

function ( φ :: Cubic )( ρ :: Real )
    (-1)^ceil(Int, φ.β/2 ) * ρ^φ.β
end

cpd_order( φ :: Cubic ) = ceil( Int, φ.β/2 )
df(φ :: Cubic, ρ :: Real ) = (-1)^(ceil(Int, φ.β/2)) * φ.β * ρ^(φ.β - 1)

# The thin plate spline is usually defined via
# ``φ(ρ) = ρ^2 \log( ρ )``.
# We provide a generalized version, which defaults to
# ``φ(ρ) = - ρ^4 \log( ρ )``.
"""
    ThinPlateSpline( k = 2 ) <: RadialFunction

A `RadialFunction` with
```math
    φ(ρ) = (-1)^{k+1} ρ^{2k} \\log(ρ)
```
"""
@with_kw struct ThinPlateSpline <: RadialFunction
    k :: Int = 2

    @assert k > 0 && k % 1 == 0 "The parameter `k` must be a positive integer."
end

function (φ :: ThinPlateSpline )( ρ :: T ) where T<:Real
    ρ == 0 ? zero(T) : (-1)^(φ.k+1) * ρ^(2*φ.k) * log( ρ )
end

cpd_order( φ :: ThinPlateSpline ) = φ.k + 1
df(φ :: ThinPlateSpline, ρ :: Real ) = ρ == 0 ? 0 : (-1)^(φ.k+1) * ρ^(2*φ.k - 1) * ( 2 * φ.k * log(ρ) + 1)

# !!! note
#     The thin plate spline with `k = 1` is not differentiable at `ρ=0` but we define the derivative
#     as 0, which results in a continuous extension.


text:
# This file is a part of Julia. License is MIT: https://julialang.org/license

@testset "invalid utf8" begin
    let io = IOBuffer()
        show(io, UnicodeError(Base.UTF_ERR_SHORT, 1, 10))
        check = "UnicodeError: invalid UTF-8 sequence starting at index 1 (0xa missing one or more continuation bytes)"
        @test String(take!(io)) == check
    end
end

max_stars_repo_name: caodi/julia

max_issues_repo_name: caodi/julia

max_forks_repo_name: caodi/julia


text:
"""
    plot_sol(sol::ReachSolution{<:CartesianProductArray}, ...)

Plots the solution of a reachability problem in high-dimensions.

### Input

- `sol` -- the solution of a reachability problem
           as a cartesian product array
"""
@recipe function plot_sol(sol::ReachSolution{<:CartesianProductArray})

    error("There is no user recipe defined for $(typeof(sol)); you need to project first into 2D using `project`")
end

"""
    plot_sol(sol::ReachSolution{<:AbstractPolytope}; ...)

Plots the solution of a reachability problem in 2D with the given options.

### Input

- `sol`            --  the solution of a reachability problem, projected into
                       two dimensions
- `seriescolor`     -- (optional, default: "blue"): color of the polygons; by
                       default, the same color is used for all of them
- `label`           -- (optional, default: nothing): the legend label
- `grid`            -- (optional, default: true): to use gridlines or not
- `alpha`           -- (optional, default: 0.5): the transparency of the polygons
- `indices`         -- (optional, default: nothing): if given, plot only a sample of
                       the array; this option can be passed through `sol.options`
- `vars`            -- (optional, default: nothing): if given, label the x and y
                       axes with the variables being plotted; this option can be
                       passed through `sol.options`, or as a pair of integers,
                       where 0 indicates the time variable

### Notes

To define your own x and y labels, use the `xlabel` (resp. `ylabel`) keyword
argument. For additional options, consult the Plots.jl reference manual.
"""
@recipe function plot_sol(sol::ReachSolution{<:AbstractPolytope};
                          seriescolor=:auto,
                          fillcolor=:auto,
                          seriestype=:shape,
                          label="", grid=true, alpha=0.5,
                          indices=nothing, vars=nothing)

    options = check_aliases_and_add_default_value(sol)

    if vars != nothing
        vars = add_plot_labels(vars)
        xguide --> vars[1]; yguide --> vars[2]
    elseif options.dict[:plot_vars] != nothing
        vars = add_plot_labels(options.dict[:plot_vars])
        xguide --> vars[1]; yguide --> vars[2]
    end

    if indices == nothing
        indices = options.dict[:plot_indices]
    end

    N = length(indices)
    # rule of thumb for linecolor and fillcolor overlap when plotting many sets
    if N < 300
        linecolor --> :black
    else
        linecolor --> :match
    end

    # Using single list and NaN separators
    vlist = Vector{Vector{Float64}}()
    for i in indices
        append!(vlist, convex_hull(vertices_list(sol.Xk[i])))
        push!(vlist, [NaN; NaN])
    end
    vlist = hcat(vlist...)'
    vlist[:, 1], vlist[:, 2]

end

"""
    check_aliases_and_add_default_value(sol::ReachSolution)

Creates a copy of an options structure where aliases have been converted to the
symbol that we use internally.

### Input

- `sol`       -- the solution of a reachability problem, projected in 2D
- `plot_vars` -- (optional) variables for plotting
- `alias`     -- (optional) output_variables
"""
function check_aliases_and_add_default_value(sol::ReachSolution)::Options
    dict = sol.options.dict
    options_copy = Options()
    dict_copy = options_copy.dict

    check_aliases!(dict, dict_copy, [:plot_vars, :output_variables])
    check_aliases_and_add_default_value!(dict, dict_copy, [:plot_indices], 1:length(sol.Xk), false)

    return options_copy
end


text:
using UnifiedChineseDictionary

using Test

@testset "unit tests" begin

end


text:
using Indicators
using Temporal
using Test
using Random
using Statistics

const global N = 1_000
const global X0 = 50.0
const global SEED = 1

@testset "Utilities" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N)) .+ X0
        y = x + randn(N)
        cxo = crossover(x, y)
        cxu = crossunder(x, y)
        @test any(cxo)
        @test any(cxu)
        @test !any(cxo .* cxu)  # ensure crossovers and crossunders never coincide
    end
end

# trendy
@testset "Trendlines" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        tmp = resistance(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = support(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = minima(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = maxima(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
    end
end

# analytical
@testset "Chaos" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = randn(252)
        # helpers
        a, b = Indicators.divide(x)
        @test [a; b] == x
        x = randn(101)
        a, b = Indicators.divide(x)
        @test [a; b] == x
        # workhorses
        h = hurst(x, n=100)
        @test size(h) == size(x)
        rs = rsrange(x)
        @test size(rs) == size(x)
        x = randn(100)
    end
    @testset "Temporal" begin
        # chaos indicators
        x = TS(randn(N))
        tmp = hurst(x)
        @test size(tmp,1) == size(x,1)
        @test size(tmp,2) == 1
        tmp = rsrange(x)
        @test size(tmp,1) == size(x,1)
        @test size(tmp,2) == 1
    end
end

# moving regressions
@testset "Regressions" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        tmp = mlr_beta(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
        tmp = mlr_slope(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_intercept(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_se(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_ub(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_lb(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_bands(tmp)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = mlr_rsq(x, adjusted=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mlr_rsq(x, adjusted=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
    end
    @testset "Temporal" begin
        x = TS(cumsum(randn(N)))
        # moving regressions
        tmp = mlr_beta(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        tmp = mlr_slope(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_intercept(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_se(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_ub(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_lb(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_bands(tmp)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = mlr_rsq(x, adjusted=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mlr_rsq(x, adjusted=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
    end
end

@testset "Running Calculations" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        X = cumsum(randn(N, 4), dims=1)
        tmp = diffn(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = diffn(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == size(X,2)
        @test sum(isnan.(tmp)) != N
        tmp = runmean(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmean(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runsum(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = wilder_sum(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmad(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmad(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runvar(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runvar(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runsd(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runcov(x, x.*rand(N), cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runcov(x, x.*rand(N), cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runcor(x, x.*rand(N), cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runcor(x, x.*rand(N), cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmin(x, cumulative=true, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmin(x, cumulative=true, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmin(x, cumulative=false, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmin(x, cumulative=false, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmax(x, cumulative=true, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmax(x, cumulative=true, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmax(x, cumulative=false, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = runmax(x, cumulative=false, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mode(map(xi->round(xi), x))
        @test size(tmp, 1) == 1
        @test size(tmp, 2) == 1
        @test !isnan(tmp)
        tmp = runquantile(x, cumulative=true)
        @test !isnan(tmp[2]) && isnan(tmp[1])
        @test tmp[10] == quantile(x[1:10], 0.05)
        tmp = runquantile(x, cumulative=false)
        @test tmp[10] == quantile(x[1:10], 0.05)
        n = 20
        tmp = runacf(x, n=n, maxlag=15, cumulative=true)
        @test all(tmp[n:end,1] .== 1.0)
        tmp = runacf(x, n=n, maxlag=15, cumulative=false)
        @test all(tmp[n:end,1] .== 1.0)
    end
    @testset "Temporal" begin
        # running calculations
        x = TS(cumsum(randn(N)))
        X = TS(cumsum(randn(N, 4), dims=1))
        tmp = runmean(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmean(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runsum(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = wilder_sum(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmad(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmad(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runvar(x, cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runvar(x, cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runsd(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runcov(X[:,1], X[:,4], cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runcov(X[:,1], X[:,4], cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runcor(X[:,1], X[:,4], cumulative=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runcor(X[:,1], X[:,4], cumulative=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmin(x, cumulative=true, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmin(x, cumulative=true, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmin(x, cumulative=false, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmin(x, cumulative=false, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmax(x, cumulative=true, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmax(x, cumulative=true, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmax(x, cumulative=false, inclusive=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runmax(x, cumulative=false, inclusive=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = runquantile(x, cumulative=true)
        @test !isnan(tmp.values[2,1]) && isnan(tmp.values[1,1])
        @test tmp.values[10,1] == quantile(x.values[1:10,1], 0.05)
        tmp = runquantile(x, cumulative=false)
        @test tmp.values[10,1] == quantile(x.values[1:10,1], 0.05)
        n = 20
        tmp = runacf(x, n=n, maxlag=15, cumulative=true)
        @test all(tmp.values[n:end,1] .== 1.0)
        tmp = runacf(x, n=n, maxlag=15, cumulative=false)
        @test all(tmp.values[n:end,1] .== 1.0)
    end
end

# moving average functions
@testset "Moving Averages" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        tmp = sma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mama(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
        tmp = ema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = wma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = hma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = trima(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = mma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = tema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = dema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = swma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = kama(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = alma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = zlema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
    end
    @testset "Temporal" begin
        x = TS(cumsum(randn(N)))
        # moving average functions
        tmp = sma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mama(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        tmp = ema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = wma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = hma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = trima(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = mma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = tema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = dema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = swma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = kama(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = alma(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = zlema(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
    end
end

# momentum function
@testset "Momentum" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))     # close
        Y = cumsum(randn(N, 2), dims=1)  # high-low
        Z = cumsum(randn(N, 3), dims=1)  # high-low-close
        tmp = aroon(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = donch(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = momentum(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = roc(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = macd(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = rsi(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = adx(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = adx(Z, wilder=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = psar(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = kst(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = wpr(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = cci(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = stoch(Z, kind=:fast)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
        tmp = stoch(Z, kind=:slow)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
        tmp = smi(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        @test sum(isnan.(tmp)) != N
    end
    @testset "Temporal" begin
        x = TS(cumsum(randn(N)))
        Y = TS(cumsum(randn(N, 2), dims=1))
        Z = TS(cumsum(randn(N, 3), dims=1))
        # momentum function
        tmp = aroon(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = donch(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = momentum(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = roc(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = macd(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = rsi(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = adx(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = adx(Z, wilder=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = psar(Y)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = kst(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = wpr(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = cci(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = stoch(Z, kind=:fast)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        tmp = stoch(Z, kind=:slow)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
        tmp = smi(Z)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 2
    end
end

# volatility functions
@testset "Volatility" begin
    Random.seed!(SEED)
    @testset "Array" begin
        x = cumsum(randn(N))
        X = cumsum(randn(N, 3), dims=1)
        tmp = bbands(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
        tmp = tr(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = atr(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = keltner(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        @test sum(isnan.(tmp)) != N
    end
    @testset "Temporal" begin
        x = TS(cumsum(randn(N)))
        X = TS(cumsum(randn(N, 3), dims=1))
        tmp = bbands(x)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
        tmp = tr(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = atr(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        tmp = keltner(X)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 3
    end
end

# chart patterns functions
@testset "Charting" begin
    Random.seed!(SEED)
    @testset "Array" begin
        X = cumsum(randn(N, 3), dims=1)
        tmp = renko(X, use_atr=true)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
        tmp = renko(X, use_atr=false)
        @test size(tmp, 1) == N
        @test size(tmp, 2) == 1
        @test sum(isnan.(tmp)) != N
    end
end