-- 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 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: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)
            if not fructrl.is_multihost_type(self.bus) then
                obj:update_version(self.bus)
            end
            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异常
                    obj:listen_vrd_abnormal()
                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: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

local VRD_DATA_LEN = 13
local function get_vrd_dump_data(mcu_id, vrd_dump)
    local vrd_data = {}
    local vrd_num = ''
    local value = {}
    local data = ''
    for i = 1, #vrd_dump // VRD_DATA_LEN do
        vrd_num = 'Vrd' .. i
        value = {}
        for j = 1, VRD_DATA_LEN do
            value[j] = ' ' .. vrd_dump[(i - 1) * VRD_DATA_LEN + j]
        end
        data = string.format('%-20s | %-4s | %-32s\n', mcu_id, vrd_num, table.concat(value))
        vrd_data[i] = data
    end
    vrd_data = table.concat(vrd_data)
    return vrd_data
end

function mcu_service:on_dump_vrd_cb(ctx, path)
    local print_body = ''
    local head = string.format('%-20s | %-4s | %-32s\n', 'Id', 'VrdNum', 'Value')
    for _, mcu in pairs(self.bcu_mcu_collection) do
        local mcu_id = mcu:get_id()
        local vrd_dump = mcu:get_vrd_dump_info()
        if vrd_dump and #vrd_dump ~= 0 then
            print_body = print_body .. get_vrd_dump_data(mcu_id, vrd_dump)
        end
    end
    if #print_body == 0 then
        log:notice('vrd_dump is empty')
        return
    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, head .. print_body))
    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

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)
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()
end

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

return singleton(mcu_service)