-- Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
--
-- this file licensed under the 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 log = require 'mc.logging'
local class = require 'mc.class'
local Singleton = require 'mc.singleton'
local props = require 'basic_cooling.define.cooling_properties'
local utils = require 'basic_cooling.cooling_utils'
local enums = require 'basic_cooling.define.cooling_enums'
local cooling_pid_intf = require 'basic_cooling.cooling_pid_intf'
local cooling_requirements = require 'basic_cooling.cooling_requirememts'
local cooling_fans = require 'basic_cooling.cooling_device.cooling_fans'
local abn_pumps = require "basic_cooling.cooling_abnormal_device.abnormal_pump"
local fan_obj = require "fan_object"
local abn_fans = require "basic_cooling.cooling_abnormal_device.abnormal_fan"

local exp_abnormal_fan_pattern = "<(%w+):(%d+):(%w+)>"
local exp_abnormal_requirement_pattern = "<(%w+):0x(%d+)>"

local exp_mgmt = class()

function exp_mgmt:ctor(service, data, fructl_data_keeping)
    self.service = service
    self.data = data
    self.fructl_data_keeping = fructl_data_keeping
    -- 保存异常场景下的风扇转速，key为异常原因(通过gen_exp_speed_key生成)，value为对应风扇ID与转速的table
    self.exp_speed_t = {}
    self.monitorint_status_t = {}
    self.fan_status_t = {}
    self.monitor_temp_t = {}
    self.requirements_instance = cooling_requirements.get_instance()
    self.fans_instance = cooling_fans.get_instance()
    self.abn_pumps_instance = abn_pumps.get_instance()
    self.abn_fans_instance = abn_fans.get_instance()
end

function exp_mgmt:init()
    self.requirements_instance.requirement_info_changed:on(function(action, obj, name, value)
        if action == 'DeleteRequirementExp' then
            -- 温度点删除，需要初始化状态，并尝试删除异常转速
            local req_id = obj[props.REQUIREMENT_ID]
            self.monitorint_status_t[req_id] = nil
            self:delete_requirement_exp_speed(obj)
        end
    end)
    self:register()
end

function exp_mgmt:get_exp_policy_table()
    return self.exp_speed_t
end

-- 根据异常类型、对象id等信息生成唯一的键
function exp_mgmt.gen_exp_speed_key(exp_type, obj_id, fan_abnormal_level)
    if exp_type == enums.exp_type.ABNORMAL_FAN then
        return string.format('<%s:%s:%s>', exp_type, obj_id, fan_abnormal_level)
    elseif exp_type == enums.exp_type.ABNORMAL_REQ or exp_type == enums.exp_type.ALARM_REQ then
        return string.format('<%s:0x%x>', exp_type, obj_id)
    else
        log:error('Unintended exception type')
        return nil
    end
end

function exp_mgmt:add_exp_speed_t(exp_speed_key, abn_policy_table)
    if self.exp_speed_t[exp_speed_key] then
        log:error('Duplicative exp speed, key:%s', exp_speed_key)
        return
    end
    self.exp_speed_t[exp_speed_key] = abn_policy_table
    local abn_policy_info = utils.parse_table_info(abn_policy_table)
    log:notice('Add exception speed, key:(%s), policy(%s)', exp_speed_key, abn_policy_info)
end

function exp_mgmt:delete_exp_speed_t(exp_speed_key)
    if not self.exp_speed_t[exp_speed_key] then
        return
    end

    self.exp_speed_t[exp_speed_key] = nil
    log:notice('Delete exception speed, key:(%s)', exp_speed_key)
end

function exp_mgmt:generate_abn_policy_table(device_table, device_pwms)
    local abn_policy_table = {}
    for key, device_id in pairs(device_table) do
        if type(device_pwms) == "table" then
            abn_policy_table[device_id] = device_pwms[key]
        end

        if type(device_pwms) == "number" then
            abn_policy_table[device_id] = device_pwms
        end
    end
    return abn_policy_table
end

-- 增加温度点异常转速
function exp_mgmt:add_requirement_exp_speed(obj)
    local base_id = obj[props.REQ_BASE_ID] -- 取前8位的bas_id
    local area_id = self.data.requirement_area_map[base_id]
    if not area_id then -- 未配置对应的Area
        log:error("Add requirement abnormal speed failed, Requirement(0x%x) not find CoolingArea(%u)",
            obj[props.REQUIREMENT_ID], base_id)
        return
    end
    local exp_speed_key = self.gen_exp_speed_key(enums.exp_type.ABNORMAL_REQ, obj[props.REQUIREMENT_ID], nil)

    -- 异常调速策略添加风扇调速信息
    local fan_t = self.data.cooling_areas[area_id].FanIdxGroup
    if next(fan_t) then
        local speed = obj.FailedValue
        local exp_table = self.abn_fans_instance:generate_abn_policy_table(fan_t, speed)
        self.abn_fans_instance:add_exp_speed_t(exp_speed_key, exp_table)
    end

    local pump_t = self.data.cooling_areas[area_id].LiquidCoolingDeviceGroup
    if next(pump_t) then
        -- 异常调速策略添加泵调速信息
        local pump_speed = obj.LiquidFailedValue
        local pump_exp_table = self.abn_pumps_instance:generate_abn_policy_table(pump_t, pump_speed)
        self.abn_pumps_instance:add_exp_speed_t(exp_speed_key, pump_exp_table)
    end
end

-- 删除温度点异常转速
function exp_mgmt:delete_requirement_exp_speed(obj)
    local exp_speed_key = self.gen_exp_speed_key(enums.exp_type.ABNORMAL_REQ, obj[props.REQUIREMENT_ID], nil)
    -- 删除风扇异常调速策略
    self.abn_fans_instance:delete_exp_speed_t(exp_speed_key)
    -- 删除泵异常调速策略
    self.abn_pumps_instance:delete_exp_speed_t(exp_speed_key)
end

-- 备用温度点返回其主用温度点,否则返回自身
local function get_actual_requirement(self, obj)
    if not obj.IsBackupRequirement then
        return obj
    end
    -- 温度点作为备用温度点,通过备用温度点的RequirementId与BackupRequirementIdx是否相同找主温度点
    return self.requirements_instance:get_requirement_by_backup_idx(obj)
end

-- 温度点状态异常调速
-- MonitoringStatus状态异常时，添加异常调速信息到exp_speed_t中
-- MonitoringStatus状态由异常转为正常时，删除温度点状态异常导致的转速
function exp_mgmt:check_requirement_status(req_o)
    local req_id = req_o[props.REQUIREMENT_ID]
    -- FailedValue没有配置不需要进行异常调速直接跳过
    if not req_o.FailedValue or req_o.FailedValue == 0 then
        return
    end
    -- 温度点调速策略不生效时，删除对应温度点的异常调速
    if req_o.IsValid ~= 1 then
        -- 删除调速策略前应当初始化温度点状态表
        self.monitorint_status_t[req_id] = nil
        self:delete_requirement_exp_speed(req_o)
        return
    end

    local m_status = 0
    local temp = req_o[props.MONITORING_VALUE]
    if self.requirements_instance:is_monitoring_abnormal(req_o) then -- 温度点状态不正常
        m_status = 1
    end
    log:debug('Requirement exp check: old status:%u, new status:%u',
        self.monitorint_status_t[req_id] and self.monitorint_status_t[req_id] or 0xff, m_status)
    -- 温度状态由正常变为异常
    if (not self.monitorint_status_t[req_id] or self.monitorint_status_t[req_id] == 0) and m_status == 1 then
        self.monitorint_status_t[req_id] = m_status
        self:add_requirement_exp_speed(req_o)
        utils.thermal_log('%s(id %s) status changes to abnormal, sensor name:%s, status:%s',
            req_o.ObjectName, req_id, req_o.SensorName, req_o.MonitoringStatus)
    -- 温度状态由异常变为正常
    elseif self.monitorint_status_t[req_id] == 1 and m_status == 0 then
        self.monitorint_status_t[req_id] = m_status
        self:delete_requirement_exp_speed(req_o)
        utils.thermal_log('%s(id %s) status changes to normal, sensor name:%s, status:%s',
            req_o.ObjectName, req_id, req_o.SensorName, req_o.MonitoringStatus)
    end
    -- 温度由0变为非0
    if (not self.monitor_temp_t[req_id]) and temp == 0 then
        self.monitor_temp_t[req_id] = true
        utils.thermal_log('%s(id %s) temperature changes to abnormal, sensor name:%s, temperature:%s',
            req_o.ObjectName, req_id, req_o.SensorName, req_o.MonitoringValue)
    -- 温度由非0变为0
    elseif self.monitor_temp_t[req_id] and temp ~= 0 then
        self.monitor_temp_t[req_id] = nil
        utils.thermal_log('%s(id %s) temperature changes to normal, sensor name:%s, temperature:%s',
            req_o.ObjectName, req_id, req_o.SensorName, req_o.MonitoringValue)
    end
end

-- 温度点状态变化触发回调
function exp_mgmt:policy_add_or_delete_error_speed(obj)
    -- 如果是备用温度点，增加备用温度点的异常调速
    if obj.IsBackupRequirement then
        self:check_requirement_status(obj)
    end
    -- 增加主用或独立温度点的异常调速
    local actual_req = get_actual_requirement(self, obj)
    -- 如果是备用温度点 存在找不到主用温度点的情况（共风扇板机型）
    if not actual_req then
        return
    end
    self:check_requirement_status(actual_req)
end

local function cooling_update_area_alarm_speed(c_area, c_requirement, fan_alarm, power_state)
    -- 3个告警温度值
    if #c_requirement.ThresholdValue ~= 3 then
        return
    end

    if power_state ~= enums.power_status.ON and not c_requirement[props.ACTIVE_IN_STANDBY] then
        return
    end

    local alarm_speed
    if c_requirement.MonitoringValue < c_requirement.ThresholdValue[enums.alarm.NORMAL_SPEED_INDEX] then
        alarm_speed = c_requirement.AlarmSpeed[enums.alarm.NORMAL_SPEED_INDEX]
    elseif c_requirement.MonitoringValue < c_requirement.ThresholdValue[enums.alarm.MINOR_SPEED_INDEX] then
        alarm_speed = c_requirement.AlarmSpeed[enums.alarm.MINOR_SPEED_INDEX]
    elseif c_requirement.MonitoringValue < c_requirement.ThresholdValue[enums.alarm.MAJOR_SPEED_INDEX] then
        alarm_speed = c_requirement.AlarmSpeed[enums.alarm.MAJOR_SPEED_INDEX]
    else
        alarm_speed = c_requirement.AlarmSpeed[enums.alarm.CRITICAL_SPEED_INDEX]
    end

    -- 当前告警转速设置给所有风扇
    for _, fan_idx in pairs(c_area.FanIdxGroup) do
        local fan_pos = (fan_idx << 8) | fan_idx
        if fan_alarm[fan_pos] == nil or alarm_speed > fan_alarm[fan_pos] then
            fan_alarm[fan_pos] = alarm_speed
        end
    end
end

function exp_mgmt:get_fan_front_and_rear_speed()
    local fans_prop = {}
    if not fan_obj.collection or not next(fan_obj) then
        log:info('No fan object found!')
        return nil
    end

    fan_obj.collection:fold(function (_, fan)
        local fan_prop = {
            Id = fan.FanId,
            FrontSpeed = fan.FrontSpeed,
            RearSpeed = fan.RearSpeed
        }
        table.insert(fans_prop, fan_prop)
    end)
    return fans_prop
end

function exp_mgmt:exception_scan(objs)
    local fan_alarm = {} -- 遍历温度点前需格式化fan_alarm
    for _, c_requirement in pairs(self.requirements_instance:get_objs()) do
        local req_base_id = c_requirement[props.REQ_BASE_ID] -- 取前8位的base_id
        local area_id = objs.requirement_area_map[req_base_id]
        if not area_id then -- 没找到对应的Area
            log:debug("Requirement(0x%x) not find CoolingArea(%u)", c_requirement[props.REQUIREMENT_ID], req_base_id)
            goto continue
        end
        -- 完整的目标调速策略进入时先维护异常调速表
        self:policy_add_or_delete_error_speed(c_requirement)
        if c_requirement.IsValid ~= 1 then -- 温度点不生效
            goto continue
        end
        -- 温度点状态，0:正常,1:错误，状态异常时不下发温度
        if self.requirements_instance:is_monitoring_abnormal(c_requirement) then
            goto continue
        end
        -- 进风口温度异常则使用转接板温度下发
        cooling_pid_intf.cooling_send_temp_to_pid(self.service,
            c_requirement, self.requirements_instance:get_temperature_value(c_requirement))
        local c_area = objs.cooling_areas[area_id]
        if not c_area then
            goto continue
        end
        -- 告警调速
        cooling_update_area_alarm_speed(c_area, c_requirement, fan_alarm, self.fructl_data_keeping:get_power_state())
        ::continue::
    end
    -- 遍历完所有温度点后下发对应的告警转速给PID
    cooling_pid_intf.cooling_tell_almspd_to_pid(self.service, fan_alarm)
    cooling_pid_intf.cooling_fan_speed_to_pid(self.service, self.get_fan_front_and_rear_speed())
end

local function set_abnormal_fan_info(abnormal_fan_info, temp_requirementId, temp_level, value)
    -- 若不存在最大值信息或者当前获取到的PWM大于已存储的PWM，则直接更新信息
    if not abnormal_fan_info['max_fan_pwm'] or value > abnormal_fan_info['max_fan_pwm'] then
        abnormal_fan_info['max_fan_pwm'] = value
        abnormal_fan_info['requirementId'] = temp_requirementId
        abnormal_fan_info['level'] = temp_level
        return
    end
    -- 若当前获取到PWM等于已存储的PWM，则对requirementId进行判断（此处的requirementId为FanId）
    -- 保存较小的requirementId的信息，如果requirementId相同，则保存优先级最大的异常因子
    if value == abnormal_fan_info['max_fan_pwm'] then
        if abnormal_fan_info['requirementId'] > temp_requirementId then
            abnormal_fan_info['requirementId'] = temp_requirementId
            abnormal_fan_info['level'] = temp_level
            return
        end
        if abnormal_fan_info['requirementId'] == temp_requirementId then
            abnormal_fan_info['level'] = abnormal_fan_info['level'] < temp_level and
                abnormal_fan_info['level'] or temp_level
            return
        end
    end
end

local function set_abnormal_req_info(abnormal_req_info, temp_requirementId, value)
    -- requirementId为16进制数，在此需要先将其转为10进制数
    temp_requirementId = tonumber(tostring(temp_requirementId), 16)
    if not abnormal_req_info['max_fan_pwm'] or value > abnormal_req_info['max_fan_pwm'] then
        abnormal_req_info['max_fan_pwm'] = value
        abnormal_req_info['requirementId'] = temp_requirementId
        return
    end
    if value == abnormal_req_info['max_fan_pwm'] then
        abnormal_req_info['requirementId'] = abnormal_req_info['requirementId'] < temp_requirementId and
            abnormal_req_info['requirementId'] or temp_requirementId
        return
    end
end

local function get_compare_fan_and_req_exception_info(exception_info, abnormal_fan_info, abnormal_req_info,
        is_fan_info_valid, is_req_info_valid)
    -- is_fan_info_valid   is_req_info_valid
    -- false               false              获取到的fan和req均为异常数据
    -- true                false              获取到的fan为有效数据，req为异常数据，直接返回fan对应信息
    -- false               true               获取到的fan为异常数据，req为有效数据，直接返回req对应信息
    -- true                true               获取到的fan和req均为有效数据，则根据转速最大值和优先级(fan > req)返回对应信息
    if not is_fan_info_valid and not is_req_info_valid then
        log:error('Both fan and requirement info is not valid')
        return exception_info
    elseif is_fan_info_valid and not is_req_info_valid then
        if not enums.fan_abnormal_status_mapping_for_ipmi[tonumber(abnormal_fan_info['level'])] then
            log:error('Abnormal fan info level[%s] does not exist', abnormal_fan_info['level'])
            return exception_info
        end
        exception_info.exp_type = enums.max_fan_pwm_type.FAN
        exception_info.max_fan_pwm = abnormal_fan_info['max_fan_pwm']
        exception_info.requirementId = 'Fan'.. abnormal_fan_info['requirementId'] .. ' ' ..
            enums.fan_abnormal_status_mapping_for_ipmi[tonumber(abnormal_fan_info['level'])]
        return exception_info
    elseif not is_fan_info_valid and is_req_info_valid then
        exception_info.exp_type = enums.max_fan_pwm_type.REQUIREMENT
        exception_info.max_fan_pwm = abnormal_req_info['max_fan_pwm']
        exception_info.requirementId = abnormal_req_info['requirementId']
        return exception_info
    else
        if abnormal_fan_info['max_fan_pwm'] >= abnormal_req_info['max_fan_pwm'] then
            if not enums.fan_abnormal_status_mapping_for_ipmi[tonumber(abnormal_fan_info['level'])] then
                log:error('Abnormal fan info level[%s] does not exist', abnormal_fan_info['level'])
                return exception_info
            end
            exception_info.exp_type = enums.max_fan_pwm_type.FAN
            exception_info.max_fan_pwm = abnormal_fan_info['max_fan_pwm']
            exception_info.requirementId = 'Fan'.. abnormal_fan_info['requirementId'] .. ' ' ..
                enums.fan_abnormal_status_mapping_for_ipmi[tonumber(abnormal_fan_info['level'])]
            return exception_info
        else
            exception_info.exp_type = enums.max_fan_pwm_type.REQUIREMENT
            exception_info.max_fan_pwm = abnormal_req_info['max_fan_pwm']
            exception_info.requirementId = abnormal_req_info['requirementId']
            return exception_info
        end
    end
end

function exp_mgmt:get_exception_info()
    local abnormal_fan_info = {
        ['max_fan_pwm'] = nil,
        ['requirementId'] = nil,
        ['level'] = nil
    }
    local abnormal_req_info = {
        ['max_fan_pwm'] = nil, ['requirementId'] = nil
    }
    local exception_info = {
        ['exp_type'] = nil,
        ['max_fan_pwm'] = nil,
        ['requirementId'] = nil
    }
    local is_exist_exception_info = false -- 用于校验是否存在exp_speed_t数据
    local fan_table = self.abn_fans_instance:get_exp_policy_table()
    for key, value in pairs(fan_table) do
        is_exist_exception_info = true
        value = utils.get_max_value(value)
        -- 匹配abnormal_fan异常信息
        local exp_type, temp_requirementId, model = string.match(key, exp_abnormal_fan_pattern)
        local temp_level = enums.fan_abnormal_status_mapping[model]
        if exp_type and temp_requirementId and temp_level then
            set_abnormal_fan_info(abnormal_fan_info, temp_requirementId, temp_level, value)
            goto continue
        end
        -- 匹配abnormal_requirement异常信息
        exp_type, temp_requirementId = string.match(key, exp_abnormal_requirement_pattern)
        if exp_type and temp_requirementId then
            set_abnormal_req_info(abnormal_req_info, temp_requirementId, value)
            goto continue
        end
        ::continue::
    end
    if not is_exist_exception_info then
        exception_info.exp_type, exception_info.max_fan_pwm, exception_info.requirementId = 0, 0, 0
        return exception_info -- 返回0,0,0表示不存在风扇异常和传感器异常信息
    end
    local is_fan_info_valid, is_req_info_valid = true, true
    -- 判断获取到的异常信息是fan还是req
    if is_exist_exception_info and
        (not abnormal_fan_info['max_fan_pwm'] or not abnormal_fan_info['requirementId'] or
            not abnormal_fan_info['level']) then
        is_fan_info_valid = false
        log:error('Abnormal fan info does not exist')
    end
    if is_exist_exception_info and (not abnormal_req_info['max_fan_pwm'] or not abnormal_req_info['requirementId']) then
        is_req_info_valid = false
        log:error('Abnormal requirement info does not exist')
    end
    get_compare_fan_and_req_exception_info(exception_info, abnormal_fan_info, abnormal_req_info,
        is_fan_info_valid, is_req_info_valid)
    return exception_info
end

function exp_mgmt:get_abnormal_policy_effective_status(ctx, policy_type)
    local pump_table = self.abn_pumps_instance:get_exp_policy_table()
    local pump_exp_flag, fan_exp_flag, requirement_exp_flag = false, false, false

    if next(pump_table) then
        pump_exp_flag = true
    end
    local abn_fan_exp = self.abn_fans_instance:get_exp_policy_table()
    for key, _ in pairs(abn_fan_exp) do
        -- 匹配abnormal_fan异常信息
        local exp_type, temp_requirementId, temp_level = string.match(key, exp_abnormal_fan_pattern)
        if exp_type and temp_requirementId and temp_level then
            fan_exp_flag = true
            goto continue
        end
        -- 匹配abnormal_requirement异常信息
        exp_type, temp_requirementId = string.match(key, exp_abnormal_requirement_pattern)
        if exp_type and temp_requirementId then
            requirement_exp_flag = true
            goto continue
        end
        ::continue::
    end

    if policy_type == 'Pump' then
        return pump_exp_flag
    end

    if policy_type == 'Fan' then
        return fan_exp_flag
    end

    if policy_type == 'Temperature' then
        return requirement_exp_flag
    end

    if policy_type == 'All' then
        if pump_exp_flag or fan_exp_flag or requirement_exp_flag then
            return true
        end
    end

    return false
end

function exp_mgmt:register()
    self.service:ImplCoolingConfigCoolingConfigGetAbnormalPolicyEffectiveStatus(function(obj, ctx, ...)
        return self:get_abnormal_policy_effective_status(ctx, ...)
    end)
end

return Singleton(exp_mgmt)
