-- 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.

-- Description: mcu固件对象管理
local singleton = require 'mc.singleton'
local log = require 'mc.logging'
local utils = require 'mc.utils'
local signal = require 'mc.signal'
local file_sec = require 'utils.file'
local utils_core = require 'utils.core'
local vos = require 'utils.vos'
local fructrl = require 'mcu.upgrade.fructl_handler'
local MCU_ENUMS = require 'mcu.enum.mcu_enums'
local smc_mcu_object = require 'mcu.smc_mcu_object'
local smbus_mcu_object = require 'mcu.smbus_mcu_object'
local cmn = require 'common'
local upgrade_service_handle = require 'mcu.upgrade.upgrade_service.upgrade_service_handle'

local mcu_service = {}
mcu_service.__index = mcu_service

function mcu_service:start_update_mcu_version()
    if fructrl.is_multihost_type(self.bus) then
        return
    end

    cmn.skynet.sleep(12000) -- 延迟2分钟启动
    log:notice('start serial task for update mcu version')

    while true do
        -- 串行更新MCU版本，避免并发访问冲突
        for position, obj in pairs(self.mcu_collection) do
            local ok, err = pcall(function()
                obj:update_version_by_mcu(self.bus)
            end)
            if not ok then
                log:info('[McuService] Update MCU version failed for position %s: %s', position, err)
            end
            -- 串行执行，每个MCU之间间隔200ms
            cmn.skynet.sleep(20)
        end
        cmn.skynet.sleep(6000) -- 1分钟例测一次
    end
end

function mcu_service:on_add_object(class_name, object, position)
    local switch = {
        ['MCUFirmware'] = function ()
            local obj
            if object.Protocol ==  MCU_ENUMS.SMC_CHANNEL then
                obj = smc_mcu_object.new(object, position, self.vrd_info_changed)
            else
                obj = smbus_mcu_object.new(object, position, self.vrd_info_changed)
            end
            obj:register_firmware_info(self.bus)

            table.insert(self.mcu_collection, obj)
            -- BCU的MCU与Vrd有功能耦合，需要创建单独的table
            if object.BoardType == 'BCU' then
                table.insert(self.bcu_mcu_collection, obj)
                cmn.skynet.fork(function ()
                    cmn.skynet.sleep(12000) -- 启动阶段等待2分钟后监听VRD异常
                    self:listen_vrd_abnormal(obj)
                end)
            end
        end
    }

    if switch[class_name] then
        log:notice('[McuUpgrade] Add object, class: %s, position: %s', class_name, position)
        switch[class_name]()
    end
end

function mcu_service:on_delete_object(class_name, object, position)
    local switch = {
        ['MCUFirmware'] = function ()
            cmn.remove_ele_by_position(self.mcu_collection, position)
            cmn.remove_ele_by_position(self.bcu_mcu_collection, position)
        end
    }

    if switch[class_name] then
        log:notice('[McuUpgrade] Delete object, class: %s, position: %s', class_name, position)
        switch[class_name]()
    end
end

function mcu_service:listen_vrd_abnormal(obj)
    local abnormal_flag = false
    local retries = 0
    local value
    local mcu_id = obj:get_id()
    while true do
        value = obj.interface:get_system_event()
        if not value or type(value) ~= 'number' then
            retries = retries + 1
        else
            retries = 0
            -- 第四位为基础板异常掉电事件
            if value & 8 ~= 0 and not abnormal_flag then
                log:notice('[Vrd]vrd abnormal appear, start get dump')
                cmn.skynet.sleep(200) --硬件建议等待2s收集完成
                obj:get_vrd_dump_info(mcu_id)
                abnormal_flag = true
            -- 硬件确认故障后收集一次，直到故障消失
            elseif value & 8 == 0 and abnormal_flag then
                log:notice('[Vrd]vrd abnormal disappear, restart to listen')
                abnormal_flag = false
            end
        end
        if retries == 10 then
            -- 连续10次获取不到认为命令不通，停止轮询
            break
        end
        cmn.skynet.sleep(500) -- 每5s检测一次
    end
end

function mcu_service:get_vrd_info()
    for _, mcu in pairs(self.bcu_mcu_collection) do
        local ok, vrd_info = pcall(function ()
           return mcu:get_vrd_info()
        end)
        if ok and vrd_info then
            return vrd_info
        end
    end
end

function mcu_service:get_obj_by_position(position)
    for _, obj in pairs(self.mcu_collection) do
        if obj:get_position() == position then
            return obj
        end
    end
    return nil
end

function mcu_service.new()
    return setmetatable({mcu_collection = {}}, mcu_service)
end

function mcu_service:on_dump_vrd_cb(ctx, path)
    -- 单独收集每个mcu的日志，并进行格式化处理，将处理好的结果存放在对应收集次数的数组中
    local mcu_id
    for _, mcu in pairs(self.bcu_mcu_collection) do
        mcu_id = mcu:get_id()
        mcu:get_vrd_dump_info(mcu_id)
    end

    local print_data = ''
    for _, mcu in pairs(self.bcu_mcu_collection) do
        local file_data = mcu:get_vrd_log()
        if file_data then
            for _, v in pairs(file_data) do
                print_data = print_data .. v
            end
        end
    end
    local file_name = path .. '/vrd_reg_dump'
    local file, err = file_sec.open_s(file_name, 'w+')
    if not file then
        log:error('open %s failed, err: %s', file_name, err)
        return
    end
    utils.close(file, pcall(file.write, file, print_data))
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    log:notice('vrd ifx info collect finish')
end

function mcu_service:on_dump_mcu_cb(ctx, path)
    if not path then
        log:error('path is nil')
        return
    end
    local print_data = ''
    local head = string.format(
        '%-32s | %-32s | %-15s | %-15s | %-32s | %-15s | %-32s\n',
        'Id', 'Name', 'version', 'Manufacturer', 'software_id', 'Location', 'Position')
    print_data = print_data .. head
    local software_id, info
    local location = 'NA'
    for _,obj in pairs(self.mcu_collection) do
        software_id =obj:get_software_id() or ''
        info = string.format(
            '%-32s | %-32s | %-15s | %-15s | %-32s | %-15s | %-32s\n',
            obj:get_id(), string.format('MCU_%s', obj:get_uid()), obj:get_mcu_version(), 'Huawei',
            software_id, location, obj:get_position()
        )
        print_data = print_data .. info
    end
    local file_name = path .. '/mcu_info'
    local file, err = file_sec.open_s(file_name, 'w+')
    if not file then
        log:error('open %s failed, err: %s', file_name, err)
        return
    end
    utils.close(file, pcall(file.write, file, print_data))
    utils_core.chmod_s(file_name, utils.S_IRUSR | utils.S_IWUSR | utils.S_IRGRP)
    log:notice('mcu info collect finish')
end

--收集MCU日志
function mcu_service:on_dump_mcu_log_cb(ctx, path)
    --从持久化目录中复制数据到一键收集路径下
    if vos.get_file_accessible(MCU_ENUMS.MCU_LOG_PATH) then
        local res = vos.system_s('/bin/cp', '-r', MCU_ENUMS.MCU_LOG_PATH .. '/.', path)
        if res ~= MCU_ENUMS.RET_OK then
            log:error('cp mcu log failed')
        end
    end
end

function mcu_service:on_dump_cb(ctx, path)
    if not path then
        log:error('path is nil')
        return
    end
    self:on_dump_mcu_cb(ctx, path)
    self:on_dump_vrd_cb(ctx, path)
    self:on_dump_mcu_log_cb(ctx, path)
end

function mcu_service:init(bus, db)
    self.bus = bus
    self.vrd_dumps = {}
    self.bcu_mcu_collection = {}
    self.upgrade_service_handle = upgrade_service_handle.new()
    self.upgrade_service_handle:init(bus, db, self.mcu_collection)
    self.vrd_info_changed = signal.new()
    -- 启动串行MCU版本更新任务
    cmn.skynet.fork(function()
        self:start_update_mcu_version()
    end)
end

function mcu_service:set_dft_mode()
    self.upgrade_service_handle:set_dft_mode()
end

return singleton(mcu_service)