-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local log = require 'mc.logging'
local singleton = require 'mc.singleton'
local bs_util = require 'util.base_util'
local prop_def = require "macros.property_def"
local ipmi = require 'ipmi'
local comp_code = ipmi.types.Cc
local struce_def = require "pojo.struct_def"
local bios_factory = require 'factory.bios_factory'
local class = require "mc.class"

local bdf_service = class()

function bdf_service:ctor()
    self.data_opts = {}
end

function bdf_service:get_data_opt(system_id)
    local buf = self.data_opts[system_id]
    if buf then
        return buf
    end
    self.data_opts[system_id] = struce_def.bios_data_operator.new()
    return self.data_opts[system_id]
end

local function response(err_code)
    local bios_service = bios_factory.get_service('bios_service')
    local maun_id = bios_service:get_manu_id()
    return err_code, maun_id
end

local function judge_bdf_len(req, frame_data)
    local bdf_data = req.BdfData
    local bdf_len = #bdf_data
    local bdf_real_len = frame_data.data_len
    if bdf_real_len > bdf_len then
        log:error("judge_bdf_len: data real len (%u) is out of range (%u)", bdf_real_len, bdf_len)
        return prop_def.E_FAILURE
    end

    return prop_def.E_OK
end

local function write_bdf_data(bdf_type, bdf_data, obj)
    local bdf_map = {}
    bdf_map[prop_def.BIOS_IPMI_CMD_PCIECARD] = prop_def.PROPERTY_BIOS_PCIECARD_BDF
    bdf_map[prop_def.BIOS_IPMI_CMD_PCIEDISK] = prop_def.PROPERTY_BIOS_PCIEDISK_BDF
    bdf_map[prop_def.BIOS_IPMI_CMD_OCPCARD] = prop_def.PROPERTY_BIOS_OCPCARD_BDF

    local bdf_prop = bdf_map[bdf_type]
    if not bdf_prop then
        log:error('write_bdf_data: bdf_type(%u) invalid.', bdf_type)
        return prop_def.E_FAILURE
    end

    obj:set_prop(bdf_prop, bdf_data)
    return prop_def.E_OK
end

function bdf_service:bdf_write_first_frame(req, frame_data, data_opt)
    data_opt.data_flag = prop_def.BIOS_FLAG_DOING
    data_opt.data_offset = 0
    data_opt.data_buf = ''

    if judge_bdf_len(req, frame_data) ~= prop_def.E_OK then
        return response(comp_code.CommandNotAvailable)
    end

    local bdf_data = req.BdfData
    data_opt.data_buf = data_opt.data_buf .. bdf_data
    data_opt.data_offset = data_opt.data_offset + frame_data.data_len
    data_opt.data_len = req.CurrentFrame

    return response(comp_code.Success)
end

local function judge_opt(data_opt, req, frame_data)
    if not data_opt then
        log:error("judge_opt: data_opt is null.")
        return prop_def.E_FAILURE, response(comp_code.CommandNotAvailable)
    end

    if data_opt.data_flag ~= prop_def.BIOS_FLAG_DOING then
        log:error("judge_opt: DataFlag:%d(ShouldBe:%d) is invalid!", data_opt.data_flag, prop_def.BIOS_FLAG_DOING)
        return prop_def.E_FAILURE, response(comp_code.CommandNotAvailable)
    end

    local offset = data_opt.data_len + 1
    if offset ~= req.CurrentFrame then
        log:error("judge_opt: offset:%d(ShouldBe:%d) is invalid!", req.CurrentFrame, offset)
        return prop_def.E_FAILURE, response(prop_def.BIOS_ERR_WRONG_OFFSET)
    end

    if judge_bdf_len(req, frame_data) ~= prop_def.E_OK then
        log:error("judge_opt: judge_bdf_len fail.")
        return prop_def.E_FAILURE, response(comp_code.CommandNotAvailable)
    end

    local bdf_data_len = data_opt.data_offset + frame_data.data_len
    if bdf_data_len > prop_def.BDF_DATA_SIZE then
        log:error("judge_opt: RealLen:%d(Max:%d,DataOffset:%d) is invalid!", frame_data.data_len,
            prop_def.BDF_DATA_SIZE, data_opt.data_offset)
        return prop_def.E_FAILURE, response(prop_def.BIOS_ERR_IV_LEN)
    end

    return prop_def.E_OK, nil
end

function bdf_service:bdf_write_data(req, frame_data, data_opt)
    local err_code, ipmi_code, maun_id = judge_opt(data_opt, req, frame_data)
    if err_code ~= prop_def.E_OK then
        log:error("bdf_write_data: judge_opt fail.")
        return ipmi_code, maun_id
    end

    local bdf_data = req.BdfData
    data_opt.data_buf = data_opt.data_buf .. bdf_data
    data_opt.data_offset = data_opt.data_offset + frame_data.data_len
    data_opt.data_len = req.CurrentFrame

    return response(comp_code.Success)
end

function bdf_service:bdf_write_finish(req, frame_data, obj, data_opt)
    local err_code, ipmi_code, maun_id = judge_opt(data_opt, req, frame_data)
    if err_code ~= prop_def.E_OK then
        bs_util.clear_data_operate_res(data_opt)
        log:error("bdf_write_finish: judge_opt fail.")
        return ipmi_code, maun_id
    end

    local bdf_data = req.BdfData
    data_opt.data_buf = data_opt.data_buf .. bdf_data
    data_opt.data_offset = data_opt.data_offset + frame_data.data_len
    data_opt.data_len = data_opt.data_offset

    err_code = write_bdf_data(req.ParameterSelector, data_opt.data_buf, obj)
    if err_code ~= prop_def.E_OK then
        bs_util.clear_data_operate_res(data_opt)
        log:error("bdf_write_finish: Set PROPERTY_BIOS_PCIECARD_BDF fail")
        return response(comp_code.CommandNotAvailable)
    end

    bs_util.clear_data_operate_res(data_opt)
    return response(comp_code.Success)
end

-- bdf多于一帧的情况
function bdf_service:write_bdf_frame(req, frame_data, obj, system_id)
    local frame_num = frame_data.frame_num
    local max_frame = frame_data.max_frame

    local data_opt = self:get_data_opt(system_id)
    if frame_num == prop_def.BDF_FIRST_FRAME then
        return self:bdf_write_first_frame(req, frame_data, data_opt)
    elseif frame_num < max_frame then
        return self:bdf_write_data(req, frame_data, data_opt)
    elseif frame_num == max_frame then
        return self:bdf_write_finish(req, frame_data, obj, data_opt)
    else
        return response(comp_code.CommandNotAvailable)
    end
end

local function validate(req, ctx)
    if not req or not ctx then
        log:error('validate: req or ctx is null.')
        return comp_code.InvalidFieldRequest
    end

    local res = bs_util.filter_msg_by_sys_channel(ctx.chan_num)
    if not res then
        log:error('bdf: channel num(%s) invalid', ctx.chan_num)
        return comp_code.CommandNotAvailable
    end
    log:debug('bdf: channel num(%s)', ctx.chan_num)

    if not req.MaxFrame or not req.CurrentFrame or not req.PCIeSlotNumber then
        log:error('validate: ipmi len invalid.')
        return comp_code.InvalidFieldRequest
    end

    return comp_code.Success
end

local function get_frame_data(req)
    local frame_data = {}
    local max_frame = req.MaxFrame & 0x7F
    local frame_num = req.CurrentFrame & 0x7F
    local format_type = req.CurrentFrame & 0x80
    local data_len = req.PCIeSlotNumber * 5

    frame_data.max_frame = max_frame
    frame_data.frame_num = frame_num
    frame_data.format_type = format_type
    frame_data.data_len = data_len

    return frame_data
end

local function get_system_id(ctx)
    if not ctx then
        return 1
    end
    return ctx.HostId or 1
end

-- bdf只有一帧的情况
function bdf_service:write_one_frame(req, frame_data, obj)
    local bdf_data = req.BdfData
    if not bdf_data then
        return response(comp_code.CommandNotAvailable)
    end

    local err_code = write_bdf_data(req.ParameterSelector, bdf_data, obj)
    if err_code ~= prop_def.E_OK then
        log:error("write_one_frame: Set PROPERTY_BIOS_PCIECARD_BDF fail")
        return response(comp_code.CommandNotAvailable)
    end

    return response(comp_code.Success)
end

function bdf_service:get_obj(system_id)
    local bios_ser = bios_factory.get_service('bios_service')
    if not bios_ser then
        return
    end
    return bios_ser:get_obj(system_id)
end

function bdf_service:bios_write_bdf_to_bmc(req, ctx)
    local system_id = get_system_id(ctx)
    local obj = self:get_obj(system_id)
    if not obj then
        log:error('bios_write_bdf_to_bmc get obj fail, system id is %s', system_id)
        return response(comp_code.CommandNotAvailable)
    end

    local err_code = validate(req, ctx)
    if err_code ~= comp_code.Success then
        return response(err_code)
    end

    local frame_data = get_frame_data(req)

    if req.ParameterSelector == prop_def.BIOS_IPMI_CMD_PCIEDISK and
        frame_data.format_type == prop_def.BIOS_PCIEDISK_BDF_ABNORMAL then
        log:error('The current version of the BIOS does not support the device.')
        return response(comp_code.CommandNotAvailable)
    end

    if req.ParameterSelector == prop_def.BIOS_IPMI_CMD_PCIECARD and
        frame_data.frame_num == frame_data.max_frame then
        obj:set_prop(prop_def.PROPERTY_BIOS_BOOT_STAGE, prop_def.BIOS_BOOT_STAGE_PCIE_INFO_REPORTED)
        log:notice('write_one_frame: pcie bdf reported, set bios boot stage completed.')
    end

    log:notice('bios_write_bdf_to_bmc: sht bios_write_bdf_data_to_bmc max_frame=%d frame_num = %d',
        frame_data.max_frame, frame_data.frame_num)

    if frame_data.frame_num == prop_def.BDF_FIRST_FRAME and frame_data.max_frame == prop_def.BDF_FIRST_FRAME then
        return self:write_one_frame(req, frame_data, obj)
    else
        return self:write_bdf_frame(req, frame_data, obj, system_id)
    end
end

return singleton(bdf_service)