-- 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.
--         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 class = require 'mc.class'
local cmn = require 'common'
local log = require 'mc.logging'
local RETIMER_CONSTANTS = require 'retimer.retimer_constants'
local context = require 'mc.context'
local intf_client = require 'general_hardware.client'
local agent = require 'retimer.agent'
local mdb = require 'mc.mdb'

local SINGLE_DATA_MAX_LEN<const> = 1024

local retimer = class(nil, nil, true)

function retimer:ctor(object, bus, pos)
    self.retimer_obj = object
    self.name = object.name
    self.bus = bus
    self.pos = pos
    self.upgrade_status = RETIMER_CONSTANTS.UPGRADE_STATUS.IDLE
    -- 创建代理，启动代理任务，定时刷新属性
    self.agent = agent.new(self.retimer_obj.ChipType, self, bus)
    self.component_idex = self.retimer_obj.ComponentIDEx
end

function retimer:init()
    self.fw_id = self:get_fw_id(self.retimer_obj.ChipName)
    if self.agent then 
        self.agent:start()
    end
end

function retimer:cleanup()
    if self.agent then
        self.agent:stop()
    end
end

function retimer:get_upgrade_status()
    return self.upgrade_status
end

function retimer:get_chip_type()
    return self.component_idex
end

function retimer:get_chip_temp()
    return self.retimer_obj.TemperatureCelsius
end

function retimer:get_parent_board_path(pos, board_type)
    local path = "/bmc/kepler/Systems/1/Boards/" .. board_type
    local interface = 'bmc.kepler.Object.Properties'
    local ok, obj_list = pcall(mdb.get_sub_objects, self.bus, path, interface)
    if not ok or not next(obj_list) then
        log:error("[Retimer] fail to get board info")
        return nil
    end
    for _, obj in pairs(obj_list) do 
        -- 通过position匹配到retimer所在的board对象，返回board对象的资源树路径
        if obj.ObjectIdentifier[4] == pos then
            return path .. "/" .. obj.ObjectName
        end
    end
    log:error("[Retimer] fail to match the same board pos: %s", pos)
    return nil
end

function retimer:get_board_prop(interface, prop, board_type)
    local parent_board_path = self:get_parent_board_path(self.pos, board_type)
    if parent_board_path then
        return mdb.get_object(self.bus, parent_board_path, interface)[prop]
    end
    log:error("[Retimer] fail to get board path")
    return ""
end

--- @function 生成FirmwareInventory对象的id，如输入ExpBoard6 Retimer1,输出EXU_Retimer1_0101
function retimer:get_fw_id(name)
    local switch = {
        ExpBoard = "EXU"
    }

    local board = string.match(name, "(%a*)%d+%a*")
    local unit = switch[board]
    if not unit then
        return string.gsub(name, "%s+", "") .. "_" .. self.pos
    end
    local retimer = string.match(name, "Retimer%d+")
    retimer = retimer and retimer or "Retimer"
    return unit .. "_" .. retimer .. "_" ..self.pos
end

--- @function 生成FirmwareInventory对象的SoftwareId属性
function retimer:get_software_id(name)
    local board_type = string.match(name, "(%a*)%d+%a*")
    if not board_type then
        log:error("[Retimer] fail to get SoftwareId")
        return ""
    end
    local interface = 'bmc.kepler.Systems.Board'
    local board_name = self:get_board_prop(interface, "Name", board_type)
    if board_name ~= "" then
        return "Retimer-" .. board_name
    end
    return ""
end

--- @function 向FirmwareInventory注册
function retimer:register_firmware_info()
    cmn.skynet.fork(function()
        log:notice('[Retimer] register retimer version to firmware inventory')

        local param = {
            Id = self.fw_id,
            Name = self.retimer_obj.ChipName,
            Version = '',
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = self:get_software_id(self.retimer_obj.ChipName),
            Manufacturer = 'Huawei',
            Location = self.retimer_obj.ChipLocation,
            State = 'Enabled',
            Severity = 'Informational'
        }
        local retries = 0
        local ok
        repeat
            ok, _ = intf_client:PFirmwareInventoryFirmwareInventoryAdd(context.new(),
                param, true, 1, 10) -- retimer固件包大小暂时不知，先预留10MB
            if not ok then
                retries = retries + 1
                cmn.skynet.sleep(100)
            end
        until ok or retries > 120  -- 最多重试2分钟
    end)
end

--- @function 更新FirmwareInventory的Version属性
function retimer:update_firmware_version()
    local path = '/bmc/kepler/UpdateService/FirmwareInventory/' .. self.fw_id
    local ok, obj = pcall(mdb.get_cached_object, self.bus, path, 'bmc.kepler.UpdateService.FirmwareInfo')
    if not ok then
        log:error('get firmware object failed, path: %s, obj: %s',
            path, obj)
        return
    end
    local version = self.retimer_obj.FirmwareVersion
    if version ~= obj.Version then
        obj.Version = version
        log:notice('update retimer firmware version %s to FirmwareInventory, id: %s', obj.Version, self.name)
    end
end

--- @function 更新FirmwareInventory的Severity属性
function retimer:update_firmware_severity()
    local path = '/bmc/kepler/UpdateService/FirmwareInventory/' .. self.fw_id
    local ok, obj = pcall(mdb.get_cached_object, self.bus, path, 'bmc.kepler.UpdateService.FirmwareInfo')
    if not ok then
        log:error('get firmware object failed, path: %s, obj: %s',
            path, obj)
        return
    end
    obj.Severity = self.retimer_obj.HealthStatus == 0 and "Informational" or "Major"
end

function retimer:trans_fw_to_ref_eeprom(data)
    local data_len = string.len(data)
    local max_frame_num = math.ceil(data_len / SINGLE_DATA_MAX_LEN)

    local addr = 0
    local retry_times = RETIMER_CONSTANTS.UPGRADE_RETRY_TIMES[self.retimer_obj.ChipType]

    log:notice("[trans_fw_to_ref_eeprom]: data_len:%s, max_frame_num:%s", data_len, max_frame_num)

    local valid_data_len, ok, ret
    for frame_count = 1, max_frame_num do 
        if frame_count == max_frame_num then 
            valid_data_len = data_len - (frame_count - 1) * SINGLE_DATA_MAX_LEN
        else
            valid_data_len = SINGLE_DATA_MAX_LEN
        end
        for _ = 1, retry_times do 
            ok, ret = pcall(self.retimer_obj.FirmwareStorageChip.Write,
                self.retimer_obj.FirmwareStorageChip,
                context.new(),
                addr,
                string.sub(data, (frame_count - 1) * SINGLE_DATA_MAX_LEN + 1,
                    (frame_count - 1) * SINGLE_DATA_MAX_LEN + valid_data_len)
            )
            if ok then
                break
            end
        end
        if not ok then
            log:error('%s write data fail, frame_count: %s, max_frame_num: %s, error: %s',
                self.name, frame_count, max_frame_num, ret)
            return false
        end
        log:info('%s write data success, frame_count: %s, max_frame_num: %s',
            self.name, frame_count, max_frame_num)
        addr = addr + SINGLE_DATA_MAX_LEN
    end
    log:notice('%s transfer frame done', self.name)
    return true
end

return retimer
