-- 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 skynet = require 'skynet'
local log = require 'mc.logging'
local class = require 'mc.class'
local Singleton = require 'mc.singleton'
local enums = require 'basic_cooling.define.cooling_enums'
local utils = require 'basic_cooling.cooling_utils'
local props = require 'basic_cooling.define.cooling_properties'

local abnormal_fan = class()

function abnormal_fan:ctor()
    self.objs = {}
    self.exp_speed_t = {}
    self.abnormal_status_mapping = {
        'Normal',
        'AbnormalRotation',
        'NotInPosition',
        'StopRotation'
    }
    self.abn_presence_mapping = {
        [0] = 'NotInPosition',
        [1] = 'Normal'
    }
    self.abn_status_mapping = {
        ['Normal'] = 0,
        ['AbnormalRotation'] = 1,
        ['StopRotation'] = 32
    }
    self.abn_presence_tasks_handler = {}
    self.abn_status_tasks_handler = {}
end

function abnormal_fan:init()
    self.signal_fan_tb = {}
end

function abnormal_fan:get_exp_policy_table()
    return self.exp_speed_t
end

function abnormal_fan:get_abnormal_fan_obj(id, status)
    if not self.objs[id] then
        return
    end
    return self.objs[id][status]
end

function abnormal_fan:obj_add_callback(class_name, object, position)
    if not self.objs[object.FanIdx] then
        self.objs[object.FanIdx] = {}
    end
    local abnormal_fan_obj = self.objs[object.FanIdx][object.Status]
    if abnormal_fan_obj then
        if object[props.ABNORMAL_FAN_PRIORITY] > abnormal_fan_obj[props.ABNORMAL_FAN_PRIORITY] then
            self.objs[object.FanIdx][object.Status] = object
            log:notice('Update object, class_name:%s, position:%s, Id:%s, Status:%s',
            class_name, position, object.FanIdx, object.Status)
        end
        return
    end
    self.objs[object.FanIdx][object.Status] = object
    log:notice('Add object callback, class_name:%s, position:%s, Id:%s, Status:%s',
        class_name, position, object.FanIdx, object.Status)
end

function abnormal_fan:get_fan_presence_status(id, f_presence, r_presence)
    if self.abn_presence_mapping[f_presence] == 'NotInPosition' or
        self.abn_presence_mapping[r_presence] == 'NotInPosition' then
        log:notice("Fan(id:%s) presence is NotInPosition, front presence:%s, rear presence:%s", id, f_presence,
            r_presence)
        return "NotInPosition"
    end
    log:notice("Fan(id:%s) presence is Normal, front presence:%s, rear presence:%s", id, f_presence, r_presence)
    return "Normal"
end

function abnormal_fan:obj_delete_callback(class_name, object, position)
    if self.objs[object.FanIdx] and self.objs[object.FanIdx][object.Status] then
        self.objs[object.FanIdx][object.Status] = nil
    end
    if self.objs[object.FanIdx] and not next(self.objs[object.FanIdx]) then
        self.objs[object.FanIdx] = nil
    end
end

function abnormal_fan:get_fan_abn_status(id, f_presence, r_presence, f_status, r_status)
    if self.abn_presence_mapping[f_presence] == 'NotInPosition' or
        self.abn_presence_mapping[r_presence] == 'NotInPosition' then
        log:notice("Fan(id:%s) presence is NotInPosition, front presence:%s, rear presence:%s", id,
            f_presence, r_presence)
        return "NotInPosition"
    end
    -- 风扇前转子或后转子停转（bit5为1），认为风扇停转
    if (f_status & self.abn_status_mapping.StopRotation) ~= 0 or
        (r_status & self.abn_status_mapping.StopRotation) ~= 0 then
        log:notice("Fan(id:%s) status is StopRotation, front status:%s, rear status:%s", id,
            f_status, r_status)
        return "StopRotation"
    end
    -- 风扇前转子或后转子转速异常（bit0为1），认为风扇转速异常
    if (f_status & self.abn_status_mapping.AbnormalRotation) ~= 0 or
        (r_status & self.abn_status_mapping.AbnormalRotation) ~= 0 then
        log:notice("Fan(id:%s) status is AbnormalRotation, front status:%s, rear status:%s", id,
            f_status, r_status)
        return "AbnormalRotation"
    end
    log:notice("Fan(id:%s) status is Normal", id)
    return "Normal"
end

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

function abnormal_fan:add_exp_speed_t(exp_speed_key, abn_policy_fan_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_fan_table
    local abn_policy_info = utils.parse_table_info(abn_policy_fan_table)
    log:notice('Add exception speed, key:(%s), policy(%s)', exp_speed_key, abn_policy_info)
end

function abnormal_fan:delete_exp_speed_t(exp_speed_key)
    if not self.exp_speed_t[exp_speed_key] then
        return
    end
    local abn_policy_fan_table = self.exp_speed_t[exp_speed_key]
    local abn_policy_info = utils.parse_table_info(abn_policy_fan_table)
    log:notice('Delete exception speed, key:(%s), policy(%s)', exp_speed_key, abn_policy_info)
    self.exp_speed_t[exp_speed_key] = nil
end

function abnormal_fan:generate_abn_policy_table(fan_table, fan_pwms)
    local abn_policy_fan_table = {}
    for key, device_id in pairs(fan_table) do
        if type(fan_pwms) == "table" then
            abn_policy_fan_table[device_id] = fan_pwms[key]
        end

        if type(fan_pwms) == "number" then
            abn_policy_fan_table[device_id] = fan_pwms
        end
    end
    return abn_policy_fan_table
end

function abnormal_fan:refresh_exp_speed_table(exp_type, id)
    for _, level in pairs(self.abnormal_status_mapping) do
        local key = self.gen_exp_speed_key(exp_type, id, level)
        self:delete_exp_speed_t(key)
    end
end

function abnormal_fan:get_replacement_abnormal_fan_obj(id)
    if not self.objs[id] then
        return nil
    end
    local speed_percentage = 0
    local abnormal_fan_obj
    -- 获取同一风扇异常调速策略下转速最高的异常调速对象
    for _, obj in pairs(self.objs[id]) do
        if obj.SpeedPercentage > speed_percentage then
            speed_percentage = obj.SpeedPercentage
            abnormal_fan_obj = obj
        end
    end
    return abnormal_fan_obj
end

function abnormal_fan:update_exp_speed_table(exp_type, id, device_status)
    -- 恢复正常，将同obj_id下同类型异常转速信息删除
    if device_status == 'Normal' then
        self:refresh_exp_speed_table(exp_type, id)
        return true
    end

    -- 异常状态
    local abn_fan_obj = self:get_abnormal_fan_obj(id, device_status)
    if not abn_fan_obj then
        log:error("Get abnormal policy(status:%s) for fan(id:%s) failed", exp_type, id)
        abn_fan_obj = self:get_replacement_abnormal_fan_obj(id)
    end
    if not abn_fan_obj then
        log:error("Get replacement abnormal policy for fan(id:%s) failed", id)
        return false
    end
    local abn_policy_fan_table

    local exp_speed_key = self.gen_exp_speed_key(exp_type, id, device_status)
    -- 加入新的异常转速信息
    if not self.exp_speed_t[exp_speed_key] then
        self:refresh_exp_speed_table(exp_type, id)
        local fan_table = abn_fan_obj.FanGroup
        local fan_pwms = abn_fan_obj.SpeedPercentage
        abn_policy_fan_table = self:generate_abn_policy_table(fan_table, fan_pwms)
        self:add_exp_speed_t(exp_speed_key, abn_policy_fan_table)
    end
    return true
end

function abnormal_fan:process_fan_status_signal(fan_id, front_presence, rear_presence, front_status, rear_status)
    if self.abn_status_tasks_handler[fan_id] then
        skynet.killthread(self.abn_status_tasks_handler[fan_id])
        self.abn_status_tasks_handler[fan_id] = nil
    end
    self.abn_status_tasks_handler[fan_id] = skynet.fork_once(function()
        local abn_status, ret
        for _ = 1, 10, 1 do
            abn_status = self:get_fan_abn_status(fan_id, front_presence, rear_presence, front_status, rear_status)
            ret = self:update_exp_speed_table(enums.exp_type.ABNORMAL_FAN, fan_id, abn_status)
            if ret then
                break
            end
            -- 经验判断，BMC启动后对象分发需10分钟
            skynet.sleep(6000)
        end
        self.abn_status_tasks_handler[fan_id] = nil
    end)
end

function abnormal_fan:fan_status_signal_subscribed(fan_obj)
    if self.signal_fan_tb[fan_obj.FanId] then
        return
    end
    self.signal_fan_tb[fan_obj.FanId] = fan_obj.fan_status_changed:on(function(info, value)
        if info == props.FAN_FRONT_PRESENCE or info == props.FAN_REAR_PRESENCE or
            info == props.FRONT_STATUS or info == props.REAR_STATUS then
            self:process_fan_status_signal(value.FanId, value.FrontPresence, value.RearPresence,
                value.FrontStatus, value.RearStatus)
        end
    end)
end

return Singleton(abnormal_fan)