-- 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 bs = require "mc.bitstring"
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 chip_lock_singleton = require 'chip_lock'

local RETIMER_CMD_CODE_RW_REQ<const> = 0x20
local RETIMER_CMD_CODE_GETSTAT<const> = 0x22

local STD_SMBUS_NOT_LAST_FRAME<const> = 0x00 -- 非最后一帧请求数据
local STD_SMBUS_LAST_FRAME<const> = 0x80 -- 最后一帧请求数据
local CDR5902_SINGLE_DATA_MAX_LEN<const> = 240

local RETIMER_CMD_GET_FW_VER<const> = 0x5
local RETIMER_CMD_UPG_TRANS_FW<const> = 0x18
local RETIMER_CMD_UPG_CTRL<const> = 0x19
local RETIMER_CMD_UPG_GET_RES<const> = 0x1A

local RETIMER_STAT_IDLE<const> = 0x10

local RW_DELAY_MS<const> = 1

--- @class Mcu对象
local retimer = {}
retimer.__index = retimer

local bs_write_data = bs.new([[<<
    len:8,
    head_and_body/string
>>]])

local RETRY_REQ_TIMES<const> = 3

local BS_REQ_LEN<const> = 12
local bs_req = bs.new([[<<
    lun:8,
    arg:8,
    opcode:16,
    offset:32,
    data_length:32
>>]])

local BS_RESP_LEN<const> = 12

local RETIMER_5902_INNER_ERR_INDEX<const> = BS_RESP_LEN + 2
local RETIMER_5902_IN_UPG_STATE_INDEX<const> = RETIMER_5902_INNER_ERR_INDEX + 1
local RETIMER_5902_RECV_DATA_LEN_INDEX<const> = RETIMER_5902_IN_UPG_STATE_INDEX + 1
local RETIMER_5902_INNER_UPG_STATUS_LEN<const> = 18
local RETIMER_5902_INNER_UPG_STATUS_ALL_LEN<const> = RETIMER_5902_INNER_UPG_STATUS_LEN + 1

local REQ_DATA_HEAD = {
    GET_5902L_VER_HEAD = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = RETIMER_CMD_GET_FW_VER,
                            offset = 0,
                            data_length = 16}),
    GET_5902H_VER_HEAD = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = RETIMER_CMD_GET_FW_VER,
                            offset = 0,
                            data_length = 20}),
    SET_5902_UPG_FORCE_HEAD = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = RETIMER_CMD_UPG_CTRL,
                            offset = 0,
                            data_length = 1}),
    SET_5902_UPG_HEAD = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 1,
                            opcode = RETIMER_CMD_UPG_CTRL,
                            offset = 0,
                            data_length = 1}),
    GET_5902_UPG_STATE_HEAD = bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 0,
                            opcode = RETIMER_CMD_UPG_GET_RES,
                            offset = 0,
                            data_length = 6}),
    SEND_FIRMWARE_FRAME_HEAD = bs_req:pack({lun = STD_SMBUS_NOT_LAST_FRAME,
                            arg = 1,
                            opcode = RETIMER_CMD_UPG_TRANS_FW,
                            offset = 0,
                            data_length = CDR5902_SINGLE_DATA_MAX_LEN}),
    SEND_LAST_FIRMWARE_FRAME_HEAD = function(data_length)
        return bs_req:pack({lun = STD_SMBUS_LAST_FRAME,
                            arg = 1,
                            opcode = RETIMER_CMD_UPG_TRANS_FW,
                            offset = 0,
                            data_length = data_length})
    end,
}

function retimer.new(object)
    local obj = setmetatable({retimer_obj = object}, retimer)
    obj.name = object.name
    obj.upgrade_status = RETIMER_CONSTANTS.UPGRADE_STATUS.IDLE
    obj:notify_upgrading(RETIMER_CONSTANTS.NOTIFY_STATUS.IDLE)
    -- 创建代理，启动代理任务，定时刷新属性
    obj.agent = agent.new(obj.retimer_obj.ChipType, obj)
    if obj.agent then
        obj.agent:start()
    end
    return obj
end

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

function retimer:notify_upgrading(is_upgrading)
    if is_upgrading ~= RETIMER_CONSTANTS.NOTIFY_STATUS.IDLE and
        is_upgrading ~= RETIMER_CONSTANTS.NOTIFY_STATUS.UPGRADING then
        log:raise('Retimer upgrade state(%s) is not correct.', is_upgrading)
    end
    self.retimer_obj.ReqAccNotify = is_upgrading
end

function retimer:channel_switch(switch)
    if switch ~= RETIMER_CONSTANTS.CHANNEL_STATUS.OPEN and
        switch ~= RETIMER_CONSTANTS.CHANNEL_STATUS.CLOSE then
        log:raise('Retimer channel switch(%s) is not correct.', switch)
    end
    self.retimer_obj.Switch = switch
end

function retimer:set_upgrade_status(upgrade_status)
    if upgrade_status ~= RETIMER_CONSTANTS.UPGRADE_STATUS.IDLE and
        upgrade_status ~= RETIMER_CONSTANTS.UPGRADE_STATUS.ON_GOING and
        upgrade_status ~= RETIMER_CONSTANTS.UPGRADE_STATUS.DONE and
        upgrade_status ~= RETIMER_CONSTANTS.UPGRADE_STATUS.VALID then
        log:raise('Retimer upgrade_status(%s) is not correct.', upgrade_status)
    end
    self.upgrade_status = upgrade_status
end

function retimer:get_upgrade_status()
    return self.upgrade_status
end

function retimer:get_chip_type()
    return self.retimer_obj.ChipType
end

-- 5902 实现了特殊的协议格式来支持查询SMBUS (slave)的busy状态
-- Master在发出一笔Request 数据后，可以通过获取slave的busy状态来决定什么时候发起下一笔操作
-- 如果读取SMBUS状态的时候，SMBUS正处于busy状态，将无法响应获取状态的请求，即不会回ACK
-- 所以判断SMBUS为idle的条件必须是成功读取到0x10，如果读到0x11，或者读不到，都认为是busy状态
-- 获取SMBUS Busy Status的隔的时间至少1ms
function retimer:wait_5902_chip_idle()
    local ok, rsp
    for _ = 1, 100 do
        cmn.skynet.sleep(RW_DELAY_MS)
        ok, rsp = pcall(self.retimer_obj.RefChip.Read, self.retimer_obj.RefChip,
            context.get_context_or_default(), RETIMER_CMD_CODE_GETSTAT, 1)
        if ok then
            local state = string.byte(rsp)
            if state == RETIMER_STAT_IDLE then
                log:info('%s 5902 chip is idle.', self.name)
                return true
            else
                log:warn('%s 5902 chip current state: %s.', self.name, state)
            end
        end
    end
    if not ok then
        log:error('%s wait 5902 chip idle fail, error: %s.', self.name, rsp)
    end
    return false
end

-- 5902 读请求
function retimer:read_5902_i2c_data(data_len, retry_count)
    local ok, read_result
    for _ = 0, retry_count do
        cmn.skynet.sleep(RW_DELAY_MS)
        ok, read_result = pcall(self.retimer_obj.RefChip.Read, self.retimer_obj.RefChip,
            context.get_context_or_default(), RETIMER_CMD_CODE_RW_REQ, data_len)
        if ok and read_result then
            log:debug('%s read 5902 data success, length: %s.', self.name, data_len)
            return read_result
        end
    end
    if not ok then
        log:error('%s read 5902 data fail, length: %s, error: %s.', self.name, data_len, read_result)
    end
    return nil
end

function retimer:read_5902_upg_state()
    local ok = self:wait_5902_chip_idle()
    if not ok then
        log:error('%s wait idle and read upgrade state fail', self.name)
        return nil, nil
    end
    ok = self:req_5902_upg_state(RETRY_REQ_TIMES)
    if not ok then
        log:error('%s send upgrade request fail', self.name)
        return nil, nil
    end

    ok = self:wait_5902_chip_idle()
    if not ok then
        log:error('%s wait idle and read upg state failed.', self.name)
        return nil, nil
    end
    local data = self:read_5902_i2c_data(RETIMER_5902_INNER_UPG_STATUS_ALL_LEN, RETRY_REQ_TIMES)
    if not data then
        log:error('%s read upg state failed.', self.name)
        return nil, nil
    end

    local inner_err = string.byte(data, RETIMER_5902_INNER_ERR_INDEX)
    local in_upg_state = string.byte(data, RETIMER_5902_IN_UPG_STATE_INDEX)
    local recv_data_len = string.byte(data, RETIMER_5902_RECV_DATA_LEN_INDEX)
    log:info('%s Response param(inner err %s, upgrade state %s, data len %s).', 
        self.name, inner_err, in_upg_state, recv_data_len)
    return inner_err, in_upg_state
end

-- 5902 写请求
function retimer:write_5902_i2c_data(data, retry_count)
    local ok, write_result
    for _ = 0, retry_count do
        cmn.skynet.sleep(RW_DELAY_MS)
        ok, write_result = pcall(self.retimer_obj.RefChip.Write, self.retimer_obj.RefChip,
            context.new(), RETIMER_CMD_CODE_RW_REQ, data)
        if ok then
            log:debug('%s write 5902 data success', self.name)
            return true
        end
    end
    if not ok then
        log:error('%s write 5902 data fail, error: %s', self.name, write_result)
    end
    return false
end

function retimer:set_5902_upgrade_force(retry_count)
    return self:write_5902_i2c_data(bs_write_data:pack({
        len = BS_REQ_LEN + 1,
        head_and_body = REQ_DATA_HEAD.SET_5902_UPG_FORCE_HEAD .. bs.new('<<data:8>>'):pack({data = 1})
    }), retry_count)
end

function retimer:req_5902_upg_state(retry_count)
    return self:write_5902_i2c_data(bs_write_data:pack({
        len = BS_REQ_LEN,
        head_and_body = REQ_DATA_HEAD.GET_5902_UPG_STATE_HEAD
    }), retry_count)
end

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

    for frame_count = 1, max_frame_num do
        local data_head
        local valid_data_len
        if frame_count == max_frame_num then
            valid_data_len = data_len - (frame_count - 1) * CDR5902_SINGLE_DATA_MAX_LEN
            data_head = REQ_DATA_HEAD.SEND_LAST_FIRMWARE_FRAME_HEAD(valid_data_len)
        else
            valid_data_len = CDR5902_SINGLE_DATA_MAX_LEN
            data_head = REQ_DATA_HEAD.SEND_FIRMWARE_FRAME_HEAD
        end

        local idle_state = self:wait_5902_chip_idle()
        if not idle_state then
            log:error('%s wait chip idle fail', self.name)
            return false
        end

        local ok, ret = pcall(self.retimer_obj.RefChip.Write, self.retimer_obj.RefChip,
            context.new(), RETIMER_CMD_CODE_RW_REQ, bs_write_data:pack({
            len = BS_REQ_LEN + valid_data_len,
            head_and_body = data_head .. string.sub(data, (frame_count - 1) * CDR5902_SINGLE_DATA_MAX_LEN + 1,
                (frame_count - 1) * CDR5902_SINGLE_DATA_MAX_LEN + valid_data_len)
        }))
        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)
    end
    log:info('%s transfer frame done', self.name)
    return true
end

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

        local param = {
            Id = self.name,
            Name = self.name,
            Version = '',
            BuildNum = '',
            ReleaseDate = '',
            LowestSupportedVersion = '',
            SoftwareId = '',
            Manufacturer = 'Huawei',
            Location = '',
            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 retimer:chip_lock(ctx, lock_time)
    return pcall(function (...)
        return chip_lock_singleton.get_instance():lock(self.retimer_obj.LockChip, ctx, lock_time)
    end)
end

function retimer:chip_unlock(ctx)
    return pcall(function (...)
        return chip_lock_singleton.get_instance():unlock(self.retimer_obj.LockChip, ctx)
    end)
end

return retimer
