--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements.  See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License.  You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local core = require("apisix.core")
local to_hex = require "resty.string".to_hex
local new_span_context = require("opentracing.span_context").new
local ngx    = ngx
local string = string
local pairs = pairs
local tonumber = tonumber

local function hex_to_char(c)
    return string.char(tonumber(c, 16))
end

local function from_hex(str)
    if str ~= nil then -- allow nil to pass through
        str = str:gsub("%x%x", hex_to_char)
    end
    return str
end

local function new_extractor()
    return function(ctx)
        local had_invalid_id = false

        local zipkin_ctx = ctx.zipkin
        local trace_id = zipkin_ctx.trace_id
        local parent_span_id = zipkin_ctx.parent_span_id
        local request_span_id = zipkin_ctx.request_span_id

        -- Validate trace id
        if trace_id and
            ((#trace_id ~= 16 and #trace_id ~= 32) or trace_id:match("%X")) then
            core.log.warn("x-b3-traceid header invalid; ignoring.")
            had_invalid_id = true
        end

        -- Validate parent_span_id
        if parent_span_id and
            (#parent_span_id ~= 16 or parent_span_id:match("%X")) then
            core.log.warn("x-b3-parentspanid header invalid; ignoring.")
            had_invalid_id = true
        end

        -- Validate request_span_id
        if request_span_id and
            (#request_span_id ~= 16 or request_span_id:match("%X")) then
            core.log.warn("x-b3-spanid header invalid; ignoring.")
            had_invalid_id = true
        end

        if trace_id == nil or had_invalid_id then
            return nil
        end

        -- Process jaegar baggage header
        local baggage = {}
        local headers = core.request.headers(ctx)
        for k, v in pairs(headers) do
            local baggage_key = k:match("^uberctx%-(.*)$")
            if baggage_key then
                baggage[baggage_key] = ngx.unescape_uri(v)
            end
        end

        core.log.info("new span context: trace id: ", trace_id,
                      ", span id: ", request_span_id,
                      ", parent span id: ", parent_span_id)

        trace_id = from_hex(trace_id)
        parent_span_id = from_hex(parent_span_id)
        request_span_id = from_hex(request_span_id)

        return new_span_context(trace_id, request_span_id, parent_span_id,
                                baggage)
    end
end

local function new_injector()
    return function(span_context, headers)
        -- We want to remove headers if already present
        headers["x-b3-traceid"] = to_hex(span_context.trace_id)
        headers["x-b3-parentspanid"] = span_context.parent_id
                                    and to_hex(span_context.parent_id) or nil
        headers["x-b3-spanid"] = to_hex(span_context.span_id)
        headers["x-b3-sampled"] = span_context:get_baggage_item("x-b3-sampled")
        for key, value in span_context:each_baggage_item() do
            -- skip x-b3-sampled baggage
            if key ~= "x-b3-sampled" then
                -- XXX: https://github.com/opentracing/specification/issues/117
                headers["uberctx-"..key] = ngx.escape_uri(value)
            end
        end
    end
end

return {
    new_extractor = new_extractor,
    new_injector = new_injector,
}
