-- 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: bmc global enables process.

local ipmi_req = require 'sensor.ipmi.ipmi'
local ct = require 'ipmi.enums'.ChannelType
local log = require 'mc.logging'
local bs = require 'mc.bitstring'
local utils = require 'sensor_utils'
local oper = require 'sensor_operation'
local err = require 'sensor_error'


local global_management = {}
global_management.__index = global_management

local enables_pattern = bs.new([[<<
    rev_msg_ipt:1,
    evt_msg_buf_full_ipt:1,
    evt_msg_buf:1,
    sys_evt_log:1,
    reserved:1,
    oem0:1,
    oem1:1,
    oem2:1
>>]])

local ENABLED = 1

local function convert_health(health)
    local map = {
        -- 与 sensor_common:convert_health() 不同，健康状态正常体现为 Normal 而非 OK
        [utils.HEALTH_NORMAL] = 'Normal',
        [utils.HEALTH_MINOR] = 'Minor',
        [utils.HEALTH_MAJOR] = 'Major',
        [utils.HEALTH_CRITICAL] = 'Critical'
    }

    return map[health] or 'Normal'
end

function global_management.new(sel_sigs, global_sigs)
    return setmetatable({
        sel_sigs = sel_sigs,
        global_sigs = global_sigs,
        evt_msgbuf_support = true,
        revmsg_ipt_support = true
    }, global_management)
end

function global_management:initialize()
    self.normal_sensor_num = 0
    self.minor_sensor_num = 0
    self.major_sensor_num = 0
    self.critical_sensor_num = 0

    self.global_sigs.update:on(function(...)
        return self:update_global(...)
    end)
    return
end

function global_management:set_sensors_obj(obj)
    self.mdb_obj = obj
end

-- 与 entity_instance:update_health_count 保持一致
function global_management:update_health_count(last_health, cur_health)
    -- 第一步：根据上一次健康状态调整对应的传感器数量值（核减1次）
    if last_health == utils.HEALTH_NORMAL and self.normal_sensor_num > 0 then
        self.normal_sensor_num = self.normal_sensor_num - 1
    elseif last_health == utils.HEALTH_MINOR and self.minor_sensor_num > 0 then
        self.minor_sensor_num = self.minor_sensor_num - 1
    elseif last_health == utils.HEALTH_MAJOR and self.major_sensor_num > 0 then
        self.major_sensor_num = self.major_sensor_num - 1
    elseif last_health == utils.HEALTH_CRITICAL and self.critical_sensor_num > 0 then
        self.critical_sensor_num = self.critical_sensor_num - 1
    end

    -- 第二步：根据本次的健康状态调整对应的传感器数量值（核增1次）
    if cur_health == utils.HEALTH_NORMAL then
        self.normal_sensor_num = self.normal_sensor_num + 1
    elseif cur_health == utils.HEALTH_MINOR then
        self.minor_sensor_num = self.minor_sensor_num + 1
    elseif cur_health == utils.HEALTH_MAJOR then
        self.major_sensor_num = self.major_sensor_num + 1
    elseif cur_health == utils.HEALTH_CRITICAL then
        self.critical_sensor_num = self.critical_sensor_num + 1
    end
end

function global_management:update_global(data)
    -- 以下更新步骤与 entity_instance:update_health 保持一致
    -- 第一步：更新当前的导致健康状态变化的个数
    self:update_health_count(data.last_health, data.cur_health)

    -- 第二步：更替上一次的健康状态，并且更新当前健康状态值
    local health = utils.HEALTH_NORMAL
    if self.critical_sensor_num > 0 then
        health = utils.HEALTH_CRITICAL
    elseif self.major_sensor_num > 0 then
        health = utils.HEALTH_MAJOR
    elseif self.minor_sensor_num > 0 then
        health = utils.HEALTH_MINOR
    end

    -- 转为健康状态字符串
    local health_str = convert_health(health)
    log:info('Global health has been changed from %s to %s', self.mdb_obj['Health'], health_str)
    self.mdb_obj['Health'] = health_str
end

function global_management:register_ipmi(cb)
    cb(ipmi_req.SetBMCEnables, function(...) return self:ipmi_set_bmc_enables(...) end)
    cb(ipmi_req.GetBMCEnables, function(...) return self:ipmi_get_bmc_enables(...) end)
end

function global_management:ipmi_set_bmc_enables(req, ctx)
    -- 仅支持带内发送
    if ctx.ChanType ~= ct.CT_HOST:value() then
        log:error("cmd (set bmc enables) is SystemOnly")
        oper.log(ctx, oper.GLOBAL_ENABLES, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_CMD))
    end

    local en = enables_pattern:unpack(string.pack('I1', req.Enable))
    if not self.evt_msgbuf_support and (en.evt_msg_buf == ENABLED) then
        log:error("event msg buffer is not supported but set to enabled")
        oper.log(ctx, oper.GLOBAL_ENABLES, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end
    if not self.revmsg_ipt_support and (en.rev_msg_ipt == ENABLED or en.en.evt_msg_buf_full_ipt == ENABLED) then
        log:error("receive msg interrupt is not supported but set to enabled")
        oper.log(ctx, oper.GLOBAL_ENABLES, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_FIELD))
    end

    -- 设置系统事件日志使能标志
    self.sel_sigs.enable:emit(en.sys_evt_log)

    self.obj.RecvMsgIntrptEnabled = en.rev_msg_ipt
    self.obj.EvtMsgBufFullIntrptEnabled = en.evt_msg_buf_full_ipt
    self.obj.EvtMsgBufEnabled = en.evt_msg_buf
    self.obj.SELEnabled = en.sys_evt_log
    self.obj.OEM0Enabled = en.oem0
    self.obj.OEM1Enabled = en.oem1
    self.obj.OEM2Enabled = en.oem2

    oper.log(ctx, oper.GLOBAL_ENABLES, oper.SUCCESS, req.Enable)
    return {CompletionCode = 0x00}
end

function global_management:ipmi_get_bmc_enables()
    local enables = string.unpack('I1', enables_pattern:pack({
        rev_msg_ipt = self.obj.RecvMsgIntrptEnabled,
        evt_msg_buf_full_ipt = self.obj.EvtMsgBufFullIntrptEnabled,
        evt_msg_buf = self.obj.EvtMsgBufEnabled,
        sys_evt_log = self.obj.SELEnabled,
        reserved = 0,
        oem0 = self.obj.OEM0Enabled,
        oem1 = self.obj.OEM1Enabled,
        oem2 = self.obj.OEM2Enabled
    }))

    return {
        CompletionCode = 0x00,
        Enable = enables
    }
end

function global_management:register(obj)
    -- 当前对象对于 BMC 来说全局唯一，因此直接根据类获取即可
    self.obj = obj

    local prd = utils.push_regist_dump
    prd('bmc global enables[RecvMsgIntrptEnabled] = %s', obj.RecvMsgIntrptEnabled)
    prd('bmc global enables[EvtMsgBufFullIntrptEnabled] = %s', obj.EvtMsgBufFullIntrptEnabled)
    prd('bmc global enables[EvtMsgBufEnabled] = %s', obj.EvtMsgBufEnabled)
    prd('bmc global enables[SELEnabled] = %s', obj.SELEnabled)
    prd('bmc global enables[OEM0Enabled] = %s', obj.OEM0Enabled)
    prd('bmc global enables[OEM1Enabled] = %s', obj.OEM1Enabled)
    prd('bmc global enables[OEM2Enabled] = %s', obj.OEM2Enabled)
end

return global_management