-- Copyright (C) idevz (idevz.org)

local consts = require "motan.consts"
local header = require "motan.protocol.motan2.header"
local message = require "motan.protocol.motan2.message"
local utils = require "motan.utils"
local setmetatable = setmetatable
local tab_concat = table.concat
local tab_insert = table.insert
local newtab = require "table.new"
local bit = require "bit"
local band = bit.band

local _M = {
    _VERSION = "0.1.0"
}

local mtb = {__index = _M}

function _M.new(self)
    return setmetatable({}, mtb)
end

function _M.encode(self, msg)
    local buffer = msg.header:pack_header()
    local mt = {}
    local build_buf = {buffer}
    for k, v in pairs(msg.metadata) do
        if type(v) ~= "table" then
            mt[#mt+1] = k
            mt[#mt+1] = "\n"
            mt[#mt+1] = v
            mt[#mt+1] = "\n"
        end
    end
    local mt_str = tab_concat(mt)
    build_buf[#build_buf+1] = utils.msb_numbertobytes(#mt_str, 4)
    build_buf[#build_buf+1] = mt_str
    local b_len, b = 0, ""
    if msg.body ~= nil then
        b_len = #msg.body
        b = msg.body
    end
    build_buf[#build_buf+1] = utils.msb_numbertobytes(b_len, 4)
    build_buf[#build_buf+1] = b

    return tab_concat(build_buf)
end

function _M.decode(self, sock)
    local magic_buffer, mgc_err = sock:receive(consts.MOTAN_HEADER_MAGIC_NUM_BYTE)
    if mgc_err == "closed" then
        ngx.log(ngx.NOTICE, mgc_err)
        return nil, mgc_err
    end
    if not magic_buffer then
        ngx.log(ngx.ERR, mgc_err)
        return nil, mgc_err
    end
    local magic = utils.msb_stringtonumber(magic_buffer)

    local msg_type_buf, typ_err = sock:receive(consts.MOTAN_HEADER_MSG_TYPE_BYTE)
    if not msg_type_buf then
        ngx.log(ngx.ERR, typ_err)
        return nil, typ_err
    end
    local msg_type = utils.msb_stringtonumber(msg_type_buf)

    local version_status_buf, vs_err = sock:receive(consts.MOTAN_HEADER_VERSION_STATUS_BYTE)
    if not version_status_buf then
        ngx.log(ngx.ERR, vs_err)
        return nil, vs_err
    end
    local version_status = utils.msb_stringtonumber(version_status_buf)

    local serialize_buf, s_err = sock:receive(consts.MOTAN_HEADER_SERIALIZE_BYTE)
    if not serialize_buf then
        ngx.log(ngx.ERR, s_err)
        return nil, s_err
    end
    local serialize = utils.msb_stringtonumber(serialize_buf)

    local request_id_buf, rid_err = sock:receive(consts.MOTAN_HEADER_REQUEST_ID_BYTE)
    if not request_id_buf then
        ngx.log(ngx.ERR, rid_err)
        return nil, rid_err
    end

    local request_id = utils.unpack_request_id(request_id_buf)

    local header_obj =
        header:new {
        magic = magic,
        msg_type = msg_type,
        version_status = version_status,
        serialize = serialize,
        request_id = request_id
    }

    if band(msg_type, 0x08) == 0x08 then
        header_obj:set_gzip(true)
    end

    local metadata_size_buffer, m_err = sock:receive(consts.MOTAN_META_SIZE_BYTE)
    if not metadata_size_buffer then
        ngx.log(ngx.ERR, m_err)
        return nil, m_err
    end
    local metasize = utils.msb_stringtonumber(metadata_size_buffer)

    local metadata = {}
    if metasize > 0 then
        local metadata_buffer, mb_err = sock:receive(metasize)
        if not metadata_buffer then
            return nil, mb_err
        end
        local metadata_arr = utils.explode("\n", metadata_buffer)
        for i = 1, #metadata_arr, 2 do
            local key = metadata_arr[i]
            metadata[key] = metadata_arr[i + 1]
        end
    end
    local bodysize_buffer, b_err = sock:receive(consts.MOTAN_BODY_SIZE_BYTE)
    if not bodysize_buffer then
        ngx.log(ngx.ERR, b_err)
        return nil, b_err
    end
    local body_size = utils.msb_stringtonumber(bodysize_buffer)
    local buffer_arr = newtab(body_size / 4096 - body_size / 4096 % 1 + 100, 0)
    local recv_size = 4096
    if body_size > 0 then
        local remaining = body_size
        while remaining > 0 do
            if recv_size > remaining then
                recv_size = remaining
            end
            local buffer, bf_err = sock:receive(recv_size)
            if not buffer then
                ngx.log(ngx.ERR, "receive fail from peer, error:", bf_err)
                return nil, bf_err
            end
            tab_insert(buffer_arr, buffer)
            remaining = remaining - #buffer
        end
    end

    local msg =
        message:new {
        header = header_obj,
        metadata = metadata,
        body = nil
    }
    local sucess
    -- error info is in the meta consts.M2_DESERIALIZE_BODY_ERROR
    sucess, msg.body = pcall(tab_concat, buffer_arr)
    buffer_arr = nil
    if not sucess then
        msg.metadata[consts.M2_DESERIALIZE_BODY_ERROR] = msg.body
        ngx.log(
            ngx.ERR,
            "motan deserialize error, table concat body buffer array fail, error:",
            msg.body,
            ", request_id:",
            msg.header.request_id
        )
        msg.body = nil
        collectgarbage("collect")
    end

    -- when body size bigger then 5M, collectgarbage will run once.
    if body_size > 5 * 1024 * 1024 then
        collectgarbage("collect")
    end
    return msg, nil
end

return _M
