-- 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: common utils function for sensor module.
local bs = require 'mc.bitstring'
local utils = require 'mc.utils'
local _, skynet = pcall(require,'skynet')
local mc_utils = require 'mc.utils'
local util_file = require 'utils.file'
local log = require 'mc.logging'

local sensor_utils = {}
sensor_utils.__index = sensor_utils

-- 传感器IPMI数据位宽
sensor_utils.BITS_8 = 8
sensor_utils.BITS_16 = 16
sensor_utils.BITS_24 = 24
sensor_utils.BITS_32 = 32

-- 传感器事件的版本
sensor_utils.EVENT_MSG_VERSION = 4

-- 传感器事件产生方向
sensor_utils.EVENT_DIR_DEASSERT = 1
sensor_utils.EVENT_DIR_ASSERT = 0

-- 传感器的健康状态
sensor_utils.HEALTH_NORMAL = 0
sensor_utils.HEALTH_MINOR = 1
sensor_utils.HEALTH_MAJOR = 2
sensor_utils.HEALTH_CRITICAL = 3

-- 传感器事件查询方式
sensor_utils.SEL_NO_CONDITION = 0
sensor_utils.SEL_MSG_MODE = 1
sensor_utils.SENSOR_ID_MODE = 2
sensor_utils.SENSOR_TYPE_MODE = 3
sensor_utils.SEL_INDEX_MODE = 4
sensor_utils.SENSOR_EVENT_MODE = 5

-- 传感器事件对外查询接口规则
sensor_utils.GET_SEL_RULE = 0
sensor_utils.REPORT_SEL_RULE = 1

-- 传感器是否支持扫描
sensor_utils.SCAN_DISABLED = 0
sensor_utils.SCAN_ENABLED = 1

-- FRU SDR 注册方式
sensor_utils.FRU_SDR_BY_OBJ = 0
sensor_utils.FRU_SDR_BY_ID = 1

-- SEL 日志使能获取标识
sensor_utils.SEL_LOG_GETTER = 1
sensor_utils.SEL_LOG_SETTER = 2

sensor_utils.DUMP_SLEEP_PERIOD = 20
sensor_utils.DUMP_SLEEP_TIME = 5

if not utils.is_lua_51 then
    function sensor_utils.tos(value, bits)
        local v = (1 << (bits - 1))
        if value & v ~= 0 then
            return ((-(value & v)) | value)
        else
            return value
        end
    end
else
    local ffi = require 'ffi'
    local ffi_cast = ffi.cast
    local bit = require 'bit'
    local band = bit.band
    local bor = bit.bor
    local lshift = bit.lshift
    function sensor_utils.tos(value, bits)
        local v = lshift(1, (bits - 1))
        local sign = band(value, v)
        if sign ~= 0 then
            return tonumber(ffi_cast("int", bor(tonumber(ffi_cast("unsigned int", -sign)), value)))
        else
            return value
        end
    end
end

-- 根据不同的方向对数值进行字符转换
-- dir 为 true 时表征对外返回的参数，只需要转换即可
-- dir 为 false 时表征内部数据处理，默认行为，转换之后需要逆序处理保证数据正确
function sensor_utils.toc(value, bits, dir)
    if type(value) ~= 'number' then
        return value
    end
    local result = {}
    local v = value
    local b = bits
    while b ~= 0 do
        result[#result + 1] = string.char(v & 0xFF)
        v = v >> sensor_utils.BITS_8
        b = b - sensor_utils.BITS_8
        if b == 0 then
            break
        end
    end

    local r = table.concat(result, '')
    return dir and r or string.reverse(r)
end

function sensor_utils.toww(c1, c2, c3, c4)
    return (c1 << sensor_utils.BITS_24) | (c2 << sensor_utils.BITS_16) | (c3 << sensor_utils.BITS_8) | c4
end

function sensor_utils.utos_byte(u)
    return string.unpack('i1', string.pack('I1', u & 0xFF))
end

function sensor_utils.format_system_id(path)
    local index = string.find(path, 'Systems')
    local left = string.find(path, '/', index)
    local right = string.find(path, '/', left + 1)
    return tonumber(string.sub(path, left + 1, right - 1))
end

function sensor_utils.copy(src)
    if type(src) ~= 'table' then
        return src
    end

    local dest = {}
    for k, v in pairs(src) do
        dest[k] = v
    end
    return dest
end

function sensor_utils.is_in(name, names)
    if type(names) ~= 'table' then
        return false
    end

    if #names == 0 then
        -- 当作表来看待，直接判断key
        for k, _ in pairs(names) do
            if k == name then
                return true
            end
        end
    else
        -- 当作数组来看待，需要获取value值
        for _, v in ipairs(names) do
            if v == name then
                return true
            end
        end
    end
    return false
end

local sensor_log = nil

function sensor_utils.add_dump_task()
    if sensor_log then
        return
    end
    sensor_log = {}
    skynet.fork_once(function()
        -- 每5秒写入一次
        skynet.sleep(500)
        local f = util_file.open_s('/var/log/sensor.log', 'a')
        if not f then
            log:error('open file[sensor.log] failed while dumping register info')
            return
        end
        mc_utils.safe_close_file(f, function()
            f:write('\n')
            f:write(table.concat(sensor_log, '\n'))
        end)
        mc_utils.chmod('/var/log/sensor.log', mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)
        for index, _ in ipairs(sensor_log) do
            sensor_log[index] = nil
        end
        sensor_log = nil
    end)
end

function sensor_utils.push_regist_dump(fmt, ...)
    sensor_utils.add_dump_task()
    sensor_log[#sensor_log + 1] = string.format('[%s] %s', os.date('%Y-%m-%d %H:%M:%S'), string.format(fmt, ...))
end

-- 开启一个任务
function sensor_utils:start_task(bus, name, path, timeout)
    local task_mgmt = require 'mc.mdb.task_mgmt'
    local _, _, id = task_mgmt.create_task(bus, name, path, timeout)
    return id
end

-- 更新任务(当前不需要退出任务)
function sensor_utils:update_task(id, param)
    local task_mgmt = require 'mc.mdb.task_mgmt'
    task_mgmt.update_task(id, param)
end

-- IPMI 传感器 Status 位域
sensor_utils.reading_state = bs.new([[<<
    disable_scanning:1,
    disable_all:1,
    disable_scanning_local:1,
    disable_override:1,
    disable_access_error:1,
    initial_update_progress:1,
    auto_re_arm:1,
    ignore_if_disable:1
>>]])

-- IPMI 传感器 Capabilities 位域
sensor_utils.capability = bs.new([[<<
    event_msg_support:2,
    threshold_access_support:2,
    hysteresis_support:2,
    auto_rearm_support:1,
    ignore_support:1
>>]])

return sensor_utils