-- 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.

local log = require 'mc.logging'
local c_object = require 'mc.orm.object'
local c_object_manage = require 'mc.orm.object_manage'
local client = require 'thermal_mgmt.client'
local signal = require 'mc.signal'
local abnormal_pump = require 'basic_cooling.cooling_abnormal_device.abnormal_pump'

local PWM_MAX<const> = 255
local SPEED_TOLERANCE<const> = 0.25 -- 转速偏差阈值，超过则认为转速异常
local STATUS_ERROR_COUNT<const> = 5
local PWM_CHECK_THRESHOLD<const> = 30
local STATUS_CHECK_THRESHOLD<const> = 5

local pump_obj_manager = c_object('Pump')

local pump_status<const> = {
    STATUS_NORMAL = 0,
    STATUS_ABNORMAL_ROTATION = 1,
    STATUS_NOT_IN_POSITION = 2,
    STATUS_STOP_RUNNING = 3
}

function pump_obj_manager:ctor()
    self.change_times = 0
    self.last_pwm = 0
    self.status_table = {}
    self.start_flag = false
    self.mcu_upgrading = false
    self.pump_status_changed = signal.new()
    self.listener = {}
    self.abnormal_pump = abnormal_pump.get_instance()
end

function pump_obj_manager:get_id()
    return self.Id
end

function pump_obj_manager:get_model()
    return self.Model
end

function pump_obj_manager:get_status()
    return self.Status
end

function pump_obj_manager:set_expected_pwm(pwm)
    self.ExpectedPWM = pwm
end

function pump_obj_manager:get_expected_pwm()
    return self.ExpectedPWM
end

function pump_obj_manager:get_actual_pwm()
    return self.ActualPWM
end

function pump_obj_manager:is_present()
    return self.Presence == 1
end

function pump_obj_manager:set_actual_pwm(pwm_percent)
    local pwm_value = pwm_percent / 100 * (self:get_max_supported_pwm())
    -- 设置pwm，四舍五入
    self.ActualPWM = math.floor(pwm_value + 0.5)
    self.ExpectedPWM = math.floor(pwm_value + 0.5)
    log:debug("pwm_percent(%s) set pwm_value: %s, HardwarePWM: %s", pwm_percent, pwm_value, self.ActualPWM)
end

function pump_obj_manager:clear_status_change_count()
    self.status_table = {[1] = 0,[2] = 0}
end

function pump_obj_manager:set_status(status)
    if self.Status == status then
        -- 防止频繁重复设置
        return
    end
    self.Status = status
end

function pump_obj_manager:get_max_supported_pwm()
    if self.MaxSupportedPWM == nil or self.MaxSupportedPWM == 0 then
        return PWM_MAX
    end
    return self.MaxSupportedPWM
end

-- 停转检查
function pump_obj_manager:check_stop_running(status)
    if self.SpeedRPM == 0 then
        status = pump_status.STATUS_STOP_RUNNING
    end
    return status

end

-- 转速异常检查
function pump_obj_manager:check_abnoraml_rotation(status)
    local expected_pwm = self.ExpectedPWM
    local target = expected_pwm / self:get_max_supported_pwm()
    local max_speed = self.MaxSpeedRPM
    local low_speed, high_speed = math.floor((target - SPEED_TOLERANCE) * max_speed),
        math.floor((target + SPEED_TOLERANCE) * max_speed)
    local real_speed = self.SpeedRPM
    if real_speed < low_speed or real_speed > high_speed then -- 在非预期浮动区间
        status = pump_status.STATUS_ABNORMAL_ROTATION
    end
    return status
end

function pump_obj_manager:refresh_status()
    local status = pump_status.STATUS_NORMAL
    -- 优先检查是否停转
    status = self:check_stop_running(status)
    if status ~= pump_status.STATUS_NORMAL then
        return status
    end

    status = self:check_abnoraml_rotation(status)
    return status
end

function pump_obj_manager:scan_status()
    local id = self.Id
    if self.status_table == nil then
        self.status_table = {[1] = 0, [2] = 0}
    end
    -- 第一位表示状态，第二位表示状态持续次数
    local status_record_t = self.status_table
    -- 获取当前状态
    local cur_status = self:refresh_status()
    -- 状态信息记录
    if cur_status == status_record_t[1] and status_record_t[2] < STATUS_ERROR_COUNT then
        status_record_t[2] = status_record_t[2] + 1
    elseif cur_status ~= status_record_t[1] then
        status_record_t[1] = cur_status
        status_record_t[2] = 1
    end
    log:debug("self.Id:%s, status:%s, count:%s", id, status_record_t[1], status_record_t[2])
    -- 当前状态持续次数超过阈值，刷新状态
    if status_record_t[2] >= STATUS_ERROR_COUNT  then
        self:set_status(status_record_t[1])
    end
end

function pump_obj_manager:clear_status()
    if self.Status ~= pump_status.STATUS_NORMAL then
        self.Status = pump_status.STATUS_NORMAL
    end
end

function pump_obj_manager:check_pwm_change_for_status()
    -- 连续n次PWM变化小于5%的情况下，才进行故障判断
    local current_pwm = self.ExpectedPWM
    if math.abs(current_pwm - self.last_pwm) < PWM_CHECK_THRESHOLD then
        if self.change_times < STATUS_CHECK_THRESHOLD then
            self.change_times = self.change_times + 1
            return
        end
        self:scan_status()
    else
        self.change_times = 0
    end
    self.last_pwm = current_pwm
end

function pump_obj_manager:monitor_status()
    if self.mcu_upgrading or self.PowerGood == 0  then -- MCU升级或下电状态
        log:debug("Clear monitor status info")
        self:clear_status_change_count()
        return
    end

    if self.ExpectedPWM == 0 then
        log:debug("Clear monitor status info")
        self:clear_status()
        return
    end
    -- 根据PWM变化开启故障状态扫描
    self:check_pwm_change_for_status()
end

function pump_obj_manager:fan_board_changed_handle(interface, dics)
    local unit_interface = 'bmc.kepler.Systems.Board.Unit'
    if interface ~= unit_interface or not dics['CurrentUpgradeStatus'] then
        return
    end

    if dics['CurrentUpgradeStatus']:value() ~= 0 then
        self.mcu_upgrading = true
    else
        self.mcu_upgrading = false
        -- MCU升级过程中setPWM无法生效，升级完成后需要重新设置一遍
        self:next_tick(self.set_actual_pwm, self, self.ExpectedPWM / self.MaxSupportedPWM * 100)
    end

    log:notice('update pump(%d) mcu_upgrading = %s', self.Id, tostring(self.mcu_upgrading))
end

function pump_obj_manager:fan_board_listenning()
    self.listener[#self.listener + 1] = client:OnUnitPropertiesChanged(function(prop_value, path, interface)
        if not string.find(path, "FanBoard") then
            return
        end
        self:fan_board_changed_handle(interface, prop_value)
    end)
end

function pump_obj_manager:create_listenning()
    local pump_start = function ()
        if self.start_flag then
            return
        end
        self.start_flag = true
        self:start()
    end
    self:listen('Presence', function()
        local is_presence = self:is_present()
        log:notice('Detect pump(%s) presence:%s.', self.Id, is_presence)
        if is_presence then
            self:next_tick(pump_start)
        else
            self:set_status(pump_status.STATUS_NOT_IN_POSITION)
            self:next_tick(function()
                self:stop_tasks()
                self.start_flag = false
            end)
        end
    end)
    self:listen('Status', function()
        log:notice('Detect pump(%s) status:%s', self.Id, self.Status)
        self.pump_status_changed:emit("Status", {Id = self.Id, Status = self.Status})
    end)
    self:fan_board_listenning()
end

function pump_obj_manager:start()
    self:next_tick(function(task)
        while self:is_present() do
            self:sleep_ms(2000) -- 2秒扫描一次
            if task.is_exit then
                break
            end
            self:monitor_status()
        end
    end)
end

function pump_obj_manager:init()
    local pump_start = function ()
        if self.start_flag then
            return
        end
        self.start_flag = true
        self:start()
    end
    self:connect_signal(self.on_add_object, function()
    end)
    self:connect_signal(self.on_add_object_complete, function()
        log:notice('on_add_object_complete pump id: %d', self.Id)
        if self:is_present() then
            self:next_tick(pump_start)
        else
            self:set_status(pump_status.STATUS_NOT_IN_POSITION)
        end
        self.abnormal_pump:pump_status_signal_subscribed(self)
        self:create_listenning()
    end)
    pump_obj_manager.super.init(self)
end

return pump_obj_manager