-- 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 class = require 'mc.class'
local smbus = require 'protocol.smbus_5902'
local log = require 'mc.logging'
local utils = require 'mc.utils'
local bs = require 'mc.bitstring'
local skynet = require 'skynet'
local def = require 'retimer.retimer_constants'
local client = require 'general_hardware.client'

local NOTIFY_DELAY_TIME<const> = 120 -- 1200毫秒
local SMBIOS_WRITE_FINISH<const> = 3
local IMU_RESET<const> = 1
local INVALID_TEMP = 0

local agent = class()

local function parse_version(msg)
    
    return string.format('%02d.%02d.%02d.%02d', string.byte(msg, 1), string.byte(msg, 2), string.byte(msg, 3),
        string.byte(msg, 4))
end

local function parse_temperature(msg)
    return bs.new([[<<temp:16/signed>>]]):unpack(msg, true).temp
end

local function parse_dieid(msg)
    local dieid = {}
    local pattern = bs.new([[<<id:32>>]])
    -- dieid由16组4字节数共64字节组成，数据长度以及在协议中校验，回调不在重复校验
    for i = 1, 64, 4 do
        local id = pattern:unpack(string.sub(msg, i, i + 3), true).id -- 截取起始位置，起始位置+3的4个字节
        dieid[#dieid + 1] = string.format('%08x', id)
    end
    return table.concat(dieid, ' ')
end

local parameters <const> = {
    -- 获取版本号，操作码0x5，数据长度16
    ['FirmwareVersion'] = {args = {opcode = 0x5, length = 16}, callback = parse_version},
    -- 获取温度，操作码0x3，数据长度2
    ['TemperatureCelsius'] = {args = {opcode = 0x3, length = 2}, callback = parse_temperature},
    -- 获取DIEID，操作码0x2B，偏移0x28000B00，数据长度64
    ['DieID'] = {args = {opcode = 0x2B, offset = 0x28000B00, length = 64}, callback = parse_dieid}
}

local function get_retimer_prop(protocol, obj_name, prop_name, params)
    local ok, msg = protocol:write(params)
    if not ok then
        log:error('fail to get retimer: %s, property: %s, error: %s', obj_name, prop_name, msg)
        return false
    end

    ok, msg = protocol:read(params.length)
    if not ok then
        log:error('fail to get retimer: %s, property: %s, error: %s', obj_name, prop_name, msg)
        return false
    end

    log:debug('get retimer: %s, property: %s, length: %s, hex: %s', obj_name, prop_name, string.len(msg),
        utils.to_hex(msg))
    return true, msg
end

local fail_parameters <const> = {
    --仅温度为动态获取，获取失败时仅将温度置为无效值
    ['TemperatureCelsius'] = INVALID_TEMP
}
local function set_invalid_prop(obj,prop_name)
    if fail_parameters[prop_name] then
        obj.retimer.retimer_obj[prop_name] = fail_parameters[prop_name]
        log:debug('set retimer %s to invalid, val: %s', prop_name, obj.retimer.retimer_obj[prop_name])
    end
end

function agent:update_retimer_info(obj)
    for prop_name, params in pairs(parameters) do
        local ok, msg = get_retimer_prop(obj.protocol, obj.name, prop_name, params.args)
        if ok and params.callback then
            obj.retimer.retimer_obj[prop_name] = params.callback(msg)
            log:debug('get retimer: %s, %s: %s', obj.name, prop_name, obj.retimer.retimer_obj[prop_name])
        else
            set_invalid_prop(obj,prop_name)
        end
    end
end

local function is_imu_reset()
    local ok, res = pcall(function()
        local bios_objs = client:GetBiosUpgradeServiceObjects()
        for _, bios_obj in pairs(bios_objs) do
            if bios_obj.ActivatedStatus == IMU_RESET then
                log:info('Imu reset, retimer monitor task will try later')
                return true
            else
                return false
            end
        end
    end)
    if not ok then
        return false
    end
    return res
end

local function try_notify(obj)
    for _ = 1, 10 do
        local ok, _ = pcall(function()
            obj.retimer:notify_upgrading(def.NOTIFY_STATUS.IDLE)
        end)
        if ok then
            return
        end
        skynet.sleep(50)
    end
    log:error('[retimer] try notify idle fail')
end

function agent:retimer_monitor_task(obj)
    -- 当前正在升级，暂不切换通道
    if obj.retimer.retimer_obj.ReqAccNotify ~= def.NOTIFY_STATUS.IDLE then
        log:info('retimer: %s is not idle, monitor will try later', obj.retimer.name)
        return
    end

    local smbios_objs = client:GetSmBiosObjects()
    for _, smbios_obj in pairs(smbios_objs) do
        if smbios_obj.SmBiosStatus ~= SMBIOS_WRITE_FINISH then
            -- 确保DC上电通道切换始终切换到CPU
            local ok, err = pcall(function()
                obj.retimer:channel_switch(def.CHANNEL_STATUS.CLOSE)
            end)
            if not ok then
                log:info('channel switch failed, error :%s', err)
            end
            try_notify(obj)
            log:info('Smbios write not finished, retimer monitor task will try later')
            --当下电时，retimer温度置为无效值
            set_invalid_prop(obj, 'TemperatureCelsius')
            return
        end
    end
    if is_imu_reset() then
        return
    end

    -- 通知1620释放总线
    obj.retimer:notify_upgrading(def.NOTIFY_STATUS.UPGRADING)
    local ok, _ = pcall(function()
        -- 等待1620释放总线完成
        skynet.sleep(NOTIFY_DELAY_TIME)
        -- 切换Retimer访问通道
        obj.retimer:channel_switch(def.CHANNEL_STATUS.OPEN)
    end)

    if not ok then
        try_notify(obj)
        return
    end

    -- 异常场景需要确保切换通道和释放总线
    pcall(function(retimer)
        -- 更新Retimer属性信息
        self:update_retimer_info(retimer)
    end, obj)

    -- 切换Retimer访问通道
    pcall(function()
        obj.retimer:channel_switch(def.CHANNEL_STATUS.CLOSE)
    end)
    -- 通知1620总线使用完成
    try_notify(obj)
end

function agent:start()
    if self.monitor_task then
        return
    end
    self.monitor_task = skynet.fork_loop({count = 0}, function(obj)
        while true do
            -- 执行监控任务
            self:retimer_monitor_task(obj)

            -- 500, 5秒执行一次
            skynet.sleep(500)
        end
    end, self)
end

function agent:stop()
    if self.monitor_task then
        skynet.killthread(self.monitor_task)
        self.monitor_task = nil
    end
end

function agent:ctor(retimer)
    self.name = retimer.name
    self.retimer = retimer
    self.log_list = {}
    self.protocol = smbus.new(retimer.retimer_obj.RefChip)
end

return agent
