-- 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 class = require 'mc.class'
local log = require 'mc.logging'
local signal = require 'mc.signal'
local singleton = require 'mc.singleton'
local client = require 'thermal_mgmt.client'

local DRIVE_TEMP_INVALID<const> = 255 -- 硬盘温度的无效值

local storage_type = {
    HDD = 0,
    SSD = 1,
}

local disks_data_keeping = class()

function disks_data_keeping:ctor(bus)
    self.bus = bus
    self.intf = 'bmc.kepler.Systems.Storage.Drive'
    self.disks_info_changed = signal.new()
    -- storage_info 用来保存硬盘的信息：硬盘id, presence为1在位，media_type代表介质类型，temperature代表温度
    self.storage_info = {}
    self.SysHDDsMaxTemperature = 0
    self.SysSSDsMaxTemperature = 0
    self.SysAllSSDsMaxTemperature = 0
    self.DiskRowTemperatureAvailable = false -- 默认为false,存在硬盘温度获取不到

    self.FrontHddMaxTemperatureCelsius = 0
    self.FrontNVMeSSDMaxTemperatureCelsius = 0
    self.RearHddMaxTemperatureCelsius = 0
    self.RearNVMeSSDMaxTemperatureCelsius = 0
end

function disks_data_keeping:init()
    skynet.fork_loop({ count = 0 }, function()
        while true do
            skynet.sleep(1000) -- 继承V2 每10s更新一次
            self:get_all_infos()
            self:update_storage_info_in_config()
            self:update_drive_max_temp_for_sensor()
        end
    end)
end

function disks_data_keeping:set_sys_hdd_max_temp(temp)
    self.SysHDDsMaxTemperature = temp
    self.disks_info_changed:emit('hdd_temp', temp)
end

function disks_data_keeping:set_sys_ssd_max_temp(temp)
    self.SysSSDsMaxTemperature = temp
    self.disks_info_changed:emit('ssd_temp', temp)
end

function disks_data_keeping:set_sys_all_ssd_max_temp(temp)
    self.SysAllSSDsMaxTemperature = temp
    self.disks_info_changed:emit('all_ssd_temp', temp)
end

function disks_data_keeping:set_rear_nvme_max_temp(temp)
    self.RearNVMeSSDMaxTemperatureCelsius = temp
    self.disks_info_changed:emit('rear_nvme_temp', temp)
end

function disks_data_keeping:set_rear_hdd_max_temp(temp)
    self.RearHddMaxTemperatureCelsius = temp
    self.disks_info_changed:emit('rear_hdd_temp', temp)
end

function disks_data_keeping:set_front_nvme_max_temp(temp)
    self.FrontNVMeSSDMaxTemperatureCelsius = temp
    self.disks_info_changed:emit('front_nvme_temp', temp)
end

function disks_data_keeping:set_front_hdd_max_temp(temp)
    self.FrontHddMaxTemperatureCelsius = temp
    self.disks_info_changed:emit('front_hdd_temp', temp)
end

function disks_data_keeping:set_disk_temp_available(is_temp_available)
    if self.DiskRowTemperatureAvailable ~= is_temp_available then
        self.DiskRowTemperatureAvailable = is_temp_available
        self.disks_info_changed:emit('disks_temp_available', is_temp_available)
    end
end

-- 更新HDD，SSD硬盘的最大温度, 是否硬盘温度都能获取到
function disks_data_keeping:update_storage_info_in_config()
    local hdd_max_temp = 0
    local ssd_max_temp = 0
    local temp_invalid_num = 0
    local all_ssd_max_temp = 0


    for _, storage_info in pairs(self.storage_info) do
        if storage_info.presence == 0 then -- 硬盘不在位
            goto continue
        end
        -- 遍历所有SSD介质的硬盘，找到最大温度
        if storage_info.media_type == storage_type.SSD and
            storage_info.temperature ~= DRIVE_TEMP_INVALID and storage_info.temperature > all_ssd_max_temp then
            all_ssd_max_temp = storage_info.temperature
        end
        -- 此处只需要获取SAS和SATA接口类型的硬盘,NVME盘能够通过i2c单独获取到温度,不用在此处获取最大温度
        -- 0-undefined 1-parallel 2-SAS 3-SATA 4-FC 5-SAS/SATA(SATA/SAS硬盘的默认值) 6-Pcie 255-unknown
        if storage_info.protocol ~= 2 and storage_info.protocol ~= 3 then
            goto continue
        end
        -- 判断是否为M.2的硬盘，如果是就自动踢除,前缀为M.2或者SATA DOM
        -- 温度无效
        if storage_info.temperature == DRIVE_TEMP_INVALID then
            temp_invalid_num = temp_invalid_num + 1
            goto continue
        end
        if storage_info.media_type == storage_type.HDD and storage_info.temperature > hdd_max_temp then
            hdd_max_temp = storage_info.temperature
        elseif storage_info.media_type == storage_type.SSD and storage_info.temperature > ssd_max_temp then
            ssd_max_temp = storage_info.temperature
        end
        -- 其他情况不做处理
        ::continue::
    end
    if hdd_max_temp ~= self.SysHDDsMaxTemperature then
        self:set_sys_hdd_max_temp(hdd_max_temp)
    end
    if ssd_max_temp ~= self.SysSSDsMaxTemperature then
        self:set_sys_ssd_max_temp(ssd_max_temp)
    end
    if all_ssd_max_temp ~= self.SysAllSSDsMaxTemperature then
        self:set_sys_all_ssd_max_temp(all_ssd_max_temp)
    end


    if temp_invalid_num == 0 then
        self:set_disk_temp_available(true) -- 硬盘温度都能获取到
    else
        self:set_disk_temp_available(false) -- 存在硬盘温度无法获取
    end
    log:debug(
        "hdd maxTemp(%s), ssd maxTemp(%s), is_temp_avail(%s), invalid_temp_num(%s), all ssd maxTemp(%s)",
        self.SysHDDsMaxTemperature, self.SysSSDsMaxTemperature, self.DiskRowTemperatureAvailable, temp_invalid_num,
        self.SysAllSSDsMaxTemperature)
end

function disks_data_keeping:update_drive_max_temp_for_sensor()
    local rear_hdd_max = 0
    local rear_nvme_max = 0
    local front_hdd_max = 0
    local front_nvme_max = 0

    for _, storage_info in pairs(self.storage_info) do
        -- 硬盘不在位时，最大温度255，不符合预期，影响赋值，因此跳过判断
        if storage_info.presence == 0 or storage_info.temperature == DRIVE_TEMP_INVALID then
            goto continue
        end
        -- 后部nvme盘温度最大值
        if storage_info.protocol == 6 and storage_info.position == 'Rear' then
            rear_nvme_max = math.max(storage_info.temperature, rear_nvme_max)
        end

        -- 前部nvme盘温度最大值
        if storage_info.protocol == 6 and storage_info.position == 'Front' then
            front_nvme_max = math.max(storage_info.temperature, front_nvme_max)
        end

        -- 后部disk最大值
        if storage_info.media_type == 0 and storage_info.position == 'Rear' then
            rear_hdd_max = math.max(rear_hdd_max, storage_info.temperature)
        end

        -- 前部disk最大值
        if storage_info.media_type == 0 and storage_info.position == 'Front' then
            front_hdd_max = math.max(front_hdd_max, storage_info.temperature)
        end

        ::continue::
    end

    if self.RearNVMeSSDMaxTemperatureCelsius ~= rear_nvme_max then
        self:set_rear_nvme_max_temp(rear_nvme_max)
    end

    if self.RearHddMaxTemperatureCelsius ~= rear_hdd_max then
        self:set_rear_hdd_max_temp(rear_hdd_max)
    end

    if self.FrontNVMeSSDMaxTemperatureCelsius ~= front_nvme_max then
        self:set_front_nvme_max_temp(front_nvme_max)
    end

    if self.FrontHddMaxTemperatureCelsius ~= front_hdd_max then
        self:set_front_hdd_max_temp(front_hdd_max)
    end
end

-- 获取所有硬盘信息
function disks_data_keeping:get_all_infos()
    local tmp_storage_info = {} -- 建立临时的table存储,防止异常导致数据丢失
    client:ForeachDriveObjects(function (obj)
        tmp_storage_info[obj.path] = {
            id = obj.Id,
            presence = obj.Presence,
            protocol = obj.Protocol,
            media_type = obj.MediaType,
            temperature = obj.TemperatureCelsius,
            position = obj.ContainerPhysicalContext
        }
        log:debug("Get disk(path:%s): (id: %u, presence: %u, protocol: %s, media type: %u, temperature: %u)",
            obj.path, obj.Id, obj.Presence, obj.Protocol, obj.MediaType, obj.TemperatureCelsius)
    end)
    self.storage_info = tmp_storage_info
end

return singleton(disks_data_keeping)