-- 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 vrd_comm_method = require 'release_command.vrd_comm_method'

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异常
                    vrd_comm_method: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 ()
            for _, obj in pairs(self.mcu_collection) do
                if obj:get_position() == position then
                    obj:unregister_mcu_firmware_inventory(obj:get_id())
                    break
                end
            end

            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: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_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)
    vrd_comm_method:on_dump_vrd_cb(ctx, path, self.bcu_mcu_collection, vrd_comm_method.DUMP_CHIP_TYPE.MCU)
    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)