-- 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: sensors lifecycle management for sensor application.
local skynet = require 'skynet'
local instance = require 'sensor.sensor_instance'
local ipmi_req = require 'sensor.ipmi.ipmi'
local common = require 'sensor.sensor_common'
local cc = require 'sensor.sensor_const'
local log = require 'mc.logging'
local utils = require 'sensor_utils'
local oper = require 'sensor_operation'
local err = require 'sensor_error'

local unregistered_discrete_events = {}  -- 暂存对应position中未注册的离散事件

local sensor_management = {}
sensor_management.__index = sensor_management

function sensor_management.new(db, sensor_sigs, sel_sigs, entity_sigs, sdr_sigs, global_sigs)
    return setmetatable({
        db = db,
        sensor_objs = {},
        sensor_numbers = {},
        sensor_number_base = 1,
        sensor_sigs = sensor_sigs,
        sel_sigs = sel_sigs,
        entity_sigs = entity_sigs,
        sdr_sigs = sdr_sigs,
        global_sigs = global_sigs
    }, sensor_management)
end

function sensor_management:initialize()
    -- 初始化传感器的编号动态分配起始位置
    local sdr_obj = self.db:query_persist_property(cc.SDR_DEVICE, cc.SENSOR_NUMBER_BASE)
    if sdr_obj then
        self.sensor_number_base = tonumber(sdr_obj.Value)
    else
        -- 若没查到则插入一条默认数据
        self.db:insert_persist_property(cc.SDR_DEVICE, cc.SENSOR_NUMBER_BASE, cc.DEFAULT_BASE_NUMBER)
        self.sensor_number_base = cc.DEFAULT_BASE_NUMBER
    end
    self.current_sensor_number = self.sensor_number_base

    -- 注册传感器更新信号槽函数，用来响应传感器的扫描状态更新
    self.sensor_sigs.update:on(function(...)
        self:update_sensors_scan_status(...)
    end)
    self.sensor_sigs.addsel:on(function (...)
        self:add_sensor_sel(...)
    end)
end

function sensor_management:add_sensor_sel(id, sel, dir_value)
    for _, v in pairs(self.sensor_objs) do
        if v.sensor_id == id then
            v:add_sensor_sel('outer added', id, sel, dir_value)
        end
    end
end

function sensor_management:update_sensors_scan_status(id, ins, host_id, status)
    for _, v in pairs(self.sensor_objs) do
        if v.mdb_obj.EntityId == id and v.mdb_obj.EntityInstance == ins and v.host_id == host_id then
            v:udpate_scan_status(status)
        end
    end
end

function sensor_management:register_ipmi(cb)
    cb(ipmi_req.SetSensorHysteresis, function(...) return self:ipmi_set_sensor_hysteresis(...) end)
    cb(ipmi_req.GetSensorHysteresis, function(...) return self:ipmi_get_sensor_hysteresis(...) end)
    cb(ipmi_req.SetSensorThreshold, function(...) return self:ipmi_set_sensor_threshold(...) end)
    cb(ipmi_req.GetSensorThreshold, function(...) return self:ipmi_get_sensor_threshold(...) end)
    cb(ipmi_req.SetSensorEventEnable, function(...) return self:ipmi_set_sensor_enable(...) end)
    cb(ipmi_req.GetSensorEventEnable, function(...) return self:ipmi_get_sensor_enable(...) end)
    cb(ipmi_req.GetSensorEventStatus, function(...) return self:ipmi_get_sensor_status(...) end)
    cb(ipmi_req.GetSensorReading, function(...) return self:ipmi_get_sensor_reading(...) end)
    cb(ipmi_req.GetSensorType, function(...) return self:ipmi_get_sensor_type(...) end)
    cb(ipmi_req.RearmSensorEvent, function(...) return self:ipmi_rearm_sensor_event(...) end)
    cb(ipmi_req.GetSensorFactors, function(...) return self:ipmi_get_sensor_factors(...) end)
end

local lun_index = {[0] = 0, [1] = 1, [3] = 2}

local function get_sensor_obj(self, lun, number)
    local lun_idx = lun_index[lun]
    return self.sensor_objs[lun_idx * 0xFF + number]
end

function sensor_management:ipmi_get_sensor_factors(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('sensor [no: 0x%02X] does not exist.', req.Number)
        error(err.ipmi_error_map(err.ERR_CANNOT_RESPONSE))
    end

    if obj.sensor_clz ~= 'ThresholdSensor' then
        log:error('sensor [no: 0x%02X] is not threshold sensor.', req.Number)
        error(err.ipmi_error_map(err.ERR_CANNOT_RESPONSE))
    end

    return {
        CompletionCode = 0x00,
        NextReading = 0x00,
        M = obj.mdb_obj.M,
        MT = obj.mdb_obj.MT,
        B = obj.mdb_obj.B,
        BA = obj.mdb_obj.BA,
        Accuracy = obj.mdb_obj.Accuracy,
        RBExp = obj.mdb_obj.RBExp
    }

end

function sensor_management:ipmi_rearm_sensor_event(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('sensor [no: 0x%02X] does not exist.', req.Number)
        error(err.ipmi_error_map(err.ERR_CANNOT_RESPONSE))
    end

    if req.RearmAll ~= 0 then
        obj:set_rearm_assert_state(req.RearmAssert, req.RearmDeassert)
    else
        self.sel_sigs.rearm:emit(obj.sensor_id)
    end

    return {CompletionCode = 0x00}
end

function sensor_management:ipmi_set_sensor_hysteresis(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Set sensor hysteresis sensor cannot be found by number %d', req.Number)
        oper.log(ctx, oper.SENSOR_HYSTER, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret = obj:set_sensor_hysteresis(req.PHysteresis, req.NHysteresis)
    if ret ~= err.SUCCESS then
        oper.log(ctx, oper.SENSOR_HYSTER, oper.FAILED)
        error(err.ipmi_error_map(ret))
    end

    oper.log(ctx, oper.SENSOR_HYSTER, oper.SUCCESS, obj.mdb_obj.SensorName,
        obj.disp_obj.PositiveHysteresisDisplay, obj.disp_obj.NegativeHysteresisDisplay)
    return {CompletionCode = 0x00}
end

function sensor_management:ipmi_get_sensor_hysteresis(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor hysteresis sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_hysteresis()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end
    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_set_sensor_threshold(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Set sensor threshold sensor cannot be found by number %d', req.Number)
        oper.log(ctx, oper.SENSOR_THRES, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret = obj:set_sensor_threshold(req, ctx)
    if ret ~= err.SUCCESS then
        oper.log(ctx, oper.SENSOR_THRES, oper.FAILED)
        error(err.ipmi_error_map(ret))
    end

    -- 成功的操作日志已经由上面的set_sensor_threshold记录
    return {CompletionCode = 0x00}
end

function sensor_management:ipmi_get_sensor_threshold(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor threshold sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_threshold()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end

    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_set_sensor_enable(req, ctx)
    local request = common.parse_enable_req(req.Datas)
    local obj = get_sensor_obj(self, ctx.dest_lun, request.number)
    if not obj then
        log:error('Set sensor enable sensor cannot be found by number %d', request.number)
        oper.log(ctx, oper.SENSOR_ENABLE, oper.FAILED)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local sensor_name = obj.mdb_obj.SensorName
    request.len = #req.Datas
    local ret = obj:set_sensor_enable(request)
    if ret ~= err.SUCCESS then
        oper.log(ctx, oper.SENSOR_ENABLE, oper.FAILED)
        error(err.ipmi_error_map(ret))
    end

    local datas = {}
    for i = 1, #req.Datas do
        datas[#datas+1] = string.format('%02X', string.byte(req.Datas, i))
    end

    oper.log(ctx, oper.SENSOR_ENABLE, oper.SUCCESS, sensor_name, table.concat(datas, '-'))
    local rsp = {}
    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_get_sensor_enable(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor enable sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_enable()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end
    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_get_sensor_status(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor status sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_status()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end
    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_get_sensor_reading(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor reading sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_reading()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end
    rsp.CompletionCode = 0x00
    return rsp
end

function sensor_management:ipmi_get_sensor_type(req, ctx)
    local obj = get_sensor_obj(self, ctx.dest_lun, req.Number)
    if not obj then
        log:error('Get sensor type sensor cannot be found by number %d', req.Number)
        error(err.ipmi_error_map(err.ERR_INVALID_ID))
    end

    local ret, rsp = obj:get_sensor_type()
    if ret ~= err.SUCCESS then
        error(err.ipmi_error_map(ret))
    end
    rsp.CompletionCode = 0x00
    return rsp
end

local function sort_sensor(list)
    table.sort(list, function (a, b)
        if a.SystemId ~= b.SystemId then
            return a.SystemId < b.SystemId
        end
        return a.SensorName < b.SensorName
    end)
    return list
end

function sensor_management:get_threshold_sensor_list()
    local list = {}
    for _, v in pairs(self.sensor_objs) do
        if v.sensor_clz == 'ThresholdSensor' then
            v:add_threshold_sensor(list, cc.GET_THRESHOLD_SENSOR_LIST)
        end
    end

    return sort_sensor(list)
end

function sensor_management:get_discrete_sensor_list()
    local list = {}
    for _, v in pairs(self.sensor_objs) do
        if v.sensor_clz == 'DiscreteSensor' then
            v:add_discrete_sensor(list, cc.GET_DISCRETE_SENSOR_LIST)
        end
    end

    return sort_sensor(list)
end

function sensor_management:get_all_sensor_list()
    local list = {}
    for _, v in pairs(self.sensor_objs) do
        v:add_all_sensor_to_list(list, cc.GET_ALL_SENSOR_LIST)
    end

    table.sort(list, function(a, b)
        if a.SystemId ~= b.SystemId then
            return a.SystemId < b.SystemId
        end

        -- 保证同一host下，所有离散传感器显示在后
        if a.SensorUnit ~= 'discrete' and b.SensorUnit == 'discrete' then
            return true
        elseif a.SensorUnit == 'discrete' and b.SensorUnit ~= 'discrete' then
            return false
        end

        return a.SensorName < b.SensorName
    end)

    return list
end

local owner_luns = {[0] = 0, [1] = 1, [2] = 3}
local function update_sensor_number(obj, number)
    if number % 0xFF == 0 then
        obj.OwnerLun = owner_luns[number // 0xFF - 1]
        obj.SensorNumber = 255
    else
        obj.OwnerLun = owner_luns[number // 0xFF]
        obj.SensorNumber = number % 0xFF
    end
end

-- SensorNumber 分配原则：
-- 1. 普通传感器默认配置为0xff, 注册时统一分配SensorNumber
-- 2. 定制化传感器的SensorNumber通过配置确定，若与已分配的普通传感器冲突，为普通传感器重新分配SensorNumber
-- 3. 若存在SensorNumber配置冲突的多个定制化传感器，后注册的视为普通传感器为其分配SensorNumber
local MAX_SENSOR_NUMBER = 765
function sensor_management:generate_sensor_number(obj, host_id)
    -- 给传感器统一进行编号，该编号资源由sensor统一进行管理
    local custom_sensor_duplicated = false

    if #self.sensor_numbers == 0 then
        for i = 1, MAX_SENSOR_NUMBER do
            self.sensor_numbers[i] = cc.UNAPPLIED
        end
    end

    local sensor_number = obj['SensorNumber']

    -- 为定制化传感器，SensorNumber通过配置指定
    if sensor_number and sensor_number ~= 0xFF then
        if self.sensor_numbers[sensor_number] == cc.UNAPPLIED then
            self.sensor_numbers[sensor_number] = cc.CUSTOM_APPLIED
            update_sensor_number(obj, sensor_number)
            return sensor_number
        elseif self.sensor_numbers[sensor_number] == cc.APPLIED then
            custom_sensor_duplicated = true
        else
            log:warn('sensor number[%d] of customize sensor[%s] is duplicated', sensor_number, obj['SensorName'])
        end
    end

    for i = self.current_sensor_number, MAX_SENSOR_NUMBER do
        if self.sensor_numbers[i] == cc.UNAPPLIED then
            self.sensor_numbers[i] = cc.APPLIED
            if custom_sensor_duplicated then
                local mobj = self.sensor_objs[sensor_number]
                mobj.sensor_number = i
                update_sensor_number(mobj.mdb_obj, i)
                self.sensor_objs[i] = mobj
                -- 更新SDR
                self.sdr_sigs.update:emit(mobj.sensor_clz, mobj.mdb_obj, host_id)

                update_sensor_number(obj, sensor_number)
                return sensor_number
            end
            update_sensor_number(obj, i)
            self.current_sensor_number = i + 1
            return i
        end
    end
end

-- 暂存离散事件，等待当前position所有对象分发完成后再完成注册
local function temp_store_discrete_event(position, obj)
    if not unregistered_discrete_events[position] then
        unregistered_discrete_events[position] = {}
    end

    local pos_discrete_events = unregistered_discrete_events[position]
    pos_discrete_events[#pos_discrete_events + 1] = obj
end

function sensor_management:register_discrete_events(position)
    local pos_discrete_events = unregistered_discrete_events[position]
    if not pos_discrete_events then
        return
    end

    local id, sensor_obj
    for k, obj in ipairs(pos_discrete_events) do
        id = obj:get_object_name()
        sensor_obj = nil
        for _, v in pairs(self.sensor_objs) do
            if v.sensor_id == obj.SensorObject then
                sensor_obj = v
                break
            end
        end
        if not sensor_obj then
            log:error('discrete event [%s] has no sensor [%s] instance.', id, obj.SensorObject)
            return
        end
        sensor_obj:add_discrete_event(id, obj)
        utils.push_regist_dump('discrete event [%s] is registered', id)
        pos_discrete_events[k] = nil
    end
    unregistered_discrete_events[position] = nil
end

function sensor_management:register_event(obj)
    local position = obj:get_position()
    temp_store_discrete_event(position, obj)
end

function sensor_management:register_sensor(clz, obj)
    local sensor_id = obj:get_object_name()
    local host_id = obj.BelongsToSystem and obj:get_system_id() or 0

    -- 由于 sensor number 是不连续的，因此这地方只能使用pairs
    for _, v in pairs(self.sensor_objs) do
        if v:match_sensor_obj(sensor_id, host_id) then
            log:warn('register duplicated sensor %s of host %s.', sensor_id, host_id)
            return false
        end
    end

    local number = self:generate_sensor_number(obj, host_id)
    -- 对象配置的是对应智能部件的OwnerId，对外统一为BMC的OwnerId
    obj.OwnerId = cc.BMC_OWNER_ID
    local ins = instance.new(number, clz, self.sel_sigs, self.entity_sigs, self.sdr_sigs,
                            self.sensor_sigs, self.global_sigs)
    ins:register(obj, host_id)
    self.sensor_objs[number] = ins
    utils.push_regist_dump('sensor %s of host %s is registered, number is %d.',
        sensor_id, host_id, number)

    return true
end

function sensor_management:unregister_event(obj)
    local event_id = obj:get_object_name()
    local sensor_id = obj.SensorObject
    local host_id = obj:get_system_id()
    local sensor_obj = nil

    for _, v in pairs(self.sensor_objs) do
        if v:match_sensor_obj(sensor_id, host_id) then
            sensor_obj = v
            break
        end
    end
    if not sensor_obj then
        log:error('discrete event [%s] has no sensor [%s] instance of host %s.', event_id, sensor_id, host_id)
        return
    end
    sensor_obj:remove_discrete_event(event_id)
    log:notice('discrete event [%s] of host %s has been unregistered.', event_id, host_id)
end

function sensor_management:unregister_sensor(obj)
    local sensor_id = obj:get_object_name()
    local host_id = obj.BelongsToSystem and obj:get_system_id() or 0

    local sensor_obj
    for _, v in pairs(self.sensor_objs) do
        if v:match_sensor_obj(sensor_id, host_id) then
            sensor_obj = v
            break
        end
    end
    if not sensor_obj then
        log:warn('sensor [%s] of host %s has no instance.', sensor_id, host_id)
        return false
    end

    -- 对象卸载需要恢复所有已经产生的事件
    sensor_obj:udpate_scan_status_disabled()

    -- 清除对应的传感器编号和对象的缓存
    self.sensor_numbers[sensor_obj.sensor_number] = cc.UNAPPLIED
    self.sensor_objs[sensor_obj.sensor_number] = nil
    log:notice('sensor [%s] of host %s has been unregistered.', sensor_id, host_id)

    return true
end

function sensor_management:mock_sensor(obj, initiator, enabled, name, value)
    local enable = enabled == 0 and 0 or 1

    -- 停止传感器模拟
    if enable == 0 then
        for _, v in pairs(self.sensor_objs) do
            if v.is_mock_mode then
                v:mock(initiator, 0, 0)
            end
        end
        return
    end

    -- 单个传感器模拟和停止模拟
    local val = 0
    if value == 'stop' then
        enable = 0
    else
        val = tonumber(value)
    end

    local sensor_existed = false
    for _, v in pairs(self.sensor_objs) do
        -- 根据调用的 obj 和传感器名称查找对应的传感器实例
        if v.mdb_obj.SensorName == name then
            sensor_existed = true
            v:mock(initiator, enable, val)
        end
    end

    if not sensor_existed then
        log:warn('sensor %s does not exist and cannot be mocked.', name)
        common.op(initiator, 'Mock sensor failed: sensor %s does not exist', name)
        error(err.sensor_error_map(err.ERR_INVALID_MOCK_NAME))
    end
end

function sensor_management:set_sensor_status(obj, initiator, sensor_name, status)
    local disabled = cc.SENSOR_ENABLED
    if status == 'disabled' then
        disabled = cc.SENSOR_DISALBED
    end

    local sensor_existed = false
    for _, v in pairs(self.sensor_objs) do
        if v.mdb_obj.SensorName == sensor_name then
            sensor_existed = true
            if v.sensor_clz == 'ThresholdSensor' then
                error(err.sensor_error_map(err.ERR_INVALID_NOT_SUPPORTED, 'threshold', status))
            end
            local ret = v:set_sensor_status(disabled, initiator)
            if ret ~= err.SUCCESS then
                error(err.sensor_error_map(ret, status == 'disabled' and 'Disable' or 'Enable'))
            end
        end
    end

    if not sensor_existed then
        log:warn('sensor %s does not exist and cannot set status.', sensor_name)
        error(err.sensor_error_map(err.ERR_INVALID_MOCK_NAME))
    end
end

function sensor_management:dump_sensor_list(path)
    local thresholds = {}
    local discretes = {}

    -- 1. 按照传感器类型收集传感器信息
    for _, v in pairs(self.sensor_objs) do
        if v.sensor_clz == 'ThresholdSensor' then
            thresholds[#thresholds+1] = v:dump_threshold_sensor()
        else
            discretes[#discretes+1] = v:dump_discrete_sensor()
        end
        if #thresholds + #discretes % utils.DUMP_SLEEP_PERIOD == 0 then skynet.sleep(utils.DUMP_SLEEP_TIME) end
    end

    -- 2. 将传感器信息写入到对应的文件中
    common.dump_sensors(thresholds, discretes, path)
end

function sensor_management:management_health(lun, number, state)
    if not self.sensor_objs[number] then
        log:error('current sensor [%d] does not exist.', number)
        return
    end

    self.sensor_objs[number]:update_management_health(state, lun, number, 0x00)
end

function sensor_management:on_import(ctx, data)
    local import = data.DynamicSensorNumBase
    if not import or not import.Import or type(import.Value) ~= 'number' then
        oper.log(ctx, oper.SENSOR_NUMBASE, oper.FAILED)
        log:error('sdr device property [DynamicSensorNumBase] import not supported.')
        return common.RET_ERROR
    end
    if import.Value <= 0 or import.Value >= 255 then
        oper.log(ctx, oper.SENSOR_NUMBASE, oper.FAILED)
        log:error('sdr device property [DynamicSensorNumBase] import out of range.')
        return common.RET_ERROR
    end

    local sdr_obj = self.db:query_persist_property(cc.SDR_DEVICE, cc.SENSOR_NUMBER_BASE)
    if not sdr_obj then
        oper.log(ctx, oper.SENSOR_NUMBASE, oper.FAILED)
        log:error('there is no sdr device instance to process import.')
        return common.RET_ERROR
    end

    if import.Value ~= tonumber(sdr_obj.Value) then
        self.sensor_number_base = import.Value
        self.db:insert_persist_property(cc.SDR_DEVICE, cc.SENSOR_NUMBER_BASE, import.Value)
        oper.log(ctx, oper.SENSOR_NUMBASE, oper.SUCCESS, import.Value)
    end
    log:info('sdr device property [DynamicSensorNumBase] import to %s successfully.', import.Value)
    return common.RET_OK
end

function sensor_management:on_export(ctx)
    local sdr_obj = self.db:query_persist_property(cc.SDR_DEVICE, cc.SENSOR_NUMBER_BASE)
    if not sdr_obj then
        log:error('there is no sdr device instance to process import.')
        return {}
    end

    log:info('sdr device property [SensorNumberBase] export successfully.')
    return {DynamicSensorNumBase = tonumber(sdr_obj.Value)}
end

return sensor_management