-- 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 interfaces for sel.
local bs = require 'mc.bitstring'
local utils_core = require 'utils.core'
local utils = require 'sensor_utils'
local mc_utils = require 'mc.utils'
local file_sec = require 'utils.file'
local log = require 'mc.logging'

local sel_common = {}
sel_common.__index = sel_common

sel_common.RET_OK = 0
sel_common.RET_ERROR = -1

function sel_common.makeup_sel_datas(record)
    local r = {}
    r[#r+1] = utils.toc(record.RecordId, utils.BITS_16, true)
    r[#r+1] = utils.toc(record.SelType, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.Timestamp, utils.BITS_32, true)
    r[#r+1] = utils.toc(record.GenerateId, utils.BITS_16, true)
    r[#r+1] = utils.toc(record.SelMsgVersion, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SensorType, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SensorNumber, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SelEventType, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SelData1, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SelData2, utils.BITS_8, true)
    r[#r+1] = utils.toc(record.SelData3, utils.BITS_8, true)
    return table.concat(r, '')
end

local pt = bs.new('<<version,sensor_type,sensor_number,event_type:7,direction:1,data1,data2,data3>>')

function sel_common.parse_sel_msg(data)
    return pt:unpack(data)
end

function sel_common.pack_sel_msg(msg)
    return pt:pack({
        version = msg.EventMsgVersion,
        sensor_type = msg.SensorType,
        sensor_number = msg.SensorNumber,
        event_type = msg.EventType,
        direction = msg.EventDir,
        data1 = msg.EventData1,
        data2 = msg.EventData2,
        data3 = msg.EventData3
    })
end

local bs_sel_event_type = bs.new([[<<et:7,ed:1>>]])

function sel_common.pack_sel_event_type(event_type, event_dir)
    return string.unpack('I1', bs_sel_event_type:pack({
        et = event_type,
        ed = event_dir
    }))
end

function sel_common.unpack_sel_event_type(sel_event_type)
    local t = bs_sel_event_type:unpack(string.pack('I1', sel_event_type))
    return t.et, t.ed
end

function sel_common.set_local_time(timestamp)
    utils_core.tzset() -- 时区信息读入并缓存

    local greenwich_date = os.date("!*t")
    local local_date = os.date("*t")

    local time_diff = os.time(local_date) - os.time(greenwich_date)
    return timestamp + time_diff
end

function sel_common.get_sel_status(sel_event_type)
    -- sel_staus为IpmiSelList.SelEventType的bit7
    return sel_event_type & 0x80 == 0 and "Asserted" or "Deasserted"
end

function sel_common.get_event_type(sel_event_type)
    -- sel_event_type为IpmiSelList.SelEventType的bit0~bit6
    return sel_event_type & 0x7F
end

local sel_level_table = {
    [0] = 'Informational',
    [1] = 'Minor',
    [2] = 'Major',
    [3] = 'Critical'
}

function sel_common.transform_sel_status(level)
    return sel_level_table[level]
end

local sensor_type_table = {
    [0] = "reserved",
    [1] = "Temperature",
    [2] = "Voltage",
    [3] = "Current",
    [4] = "Fan",
    [5] = "Physical Security",
    [6] = "Platform Security",
    [7] = "Processor",
    [8] = "Power Supply",
    [9] = "Power Unit",
    [10] = "Cooling Device",
    [11] = "Other",
    [12] = "Memory",
    [13] = "Drive Slot / Bay",
    [14] = "POST Memory Resize",
    [15] = "System Firmwares",
    [16] = "Event Logging Disabled",
    [17] = "Watchdog1",
    [18] = "System Event",
    [19] = "Critical Interrupt",
    [20] = "Button",
    [21] = "Module / Board",
    [22] = "Microcontroller",
    [23] = "Add-in Card",
    [24] = "Chassis",
    [25] = "Chip Set",
    [26] = "Other FRU",
    [27] = "Cable / Interconnect",
    [28] = "Terminator",
    [29] = "System Boot Initiated",
    [30] = "Boot Error",
    [31] = "OS Boot",
    [32] = "OS Critical Stop",
    [33] = "Slot / Connecto",
    [34] = "System ACPI Power State",
    [35] = "Watchdog2",
    [36] = "Platform Alert",
    [37] = "Entity Presence",
    [38] = "Monitor ASIC",
    [39] = "LAN",
    [40] = "Management Subsys Health",
    [41] = "Battery",
    [42] = "Session Audit",
    [43] = "Version Change",
    [44] = "FRU State"
}

local oem_sensor_type = {
    [240] = "FRU Hot Swap",
    [241] ="IPMB Physical Link",
    [242] = "Module Hot Swap",
    [243] = "Power Channel Notification",
    [244] = "Telco Alarm Input"
}

function sel_common.transform_sensor_type(type)
    return sensor_type_table[type] or oem_sensor_type[type] or sensor_type_table[0]
end

-- 这个接口用来分离path和对应的文件名
function sel_common.split_path_name(path)
    if type(path) ~= 'string' or path:find('/') ~= 1 then
        -- 路径类型非法，包含相对路径
        return nil
    end

    local items = {}
    path:gsub('[^/]+', function (s) table.insert(items, s) end)
    if #items < 2 then
        -- 传入参数没有路径名或者文件名
        return nil
    end

    return items[#items]
end

-- csv格式存储并打包
-- path: 例如:/tmp/sel.tar.gz
-- 打包路径：/dev/shm/sensor
local PACKED_PATH = '/dev/shm/sensor'
function sel_common.pack_sel(list, path)
    -- 由于打包API需要切换工作路径，这里需要将路径与文件名分开
    local n = sel_common.split_path_name(path)
    if not n then
        log:error('export path is invalid, please check')
        return false
    end

    -- 创建打包文件的路径和文件
    if file_sec.check_real_path_s(PACKED_PATH) ~= 0 and utils_core.mkdir(PACKED_PATH, mc_utils.S_IRWXU) ~= 0 then
        log:error('temporary is not existed and cannot be created, download sel failed')
        return false
    end
    local src_file = PACKED_PATH .. '/sensor_sel.csv'
    local res = sel_common.dump_sel_to_csv(list, src_file)
    if not res then
        return false
    end

    -- 打包对应的文件，打包之前先删除文件
    mc_utils.remove_file(path)
    local args = n:match('.tar.gz') and '-zcvf' or 'cvf'
    -- 只保留目标文件，排除父目录
    local ok, err = pcall(os.execute, string.format('tar %s %s -C %s sensor_sel.csv', args, path, PACKED_PATH))
    if not ok then
        log:error('compress export file failed, err: %s', err)
        return false
    end

    mc_utils.remove_file(src_file)
    return true
end

local function sel_to_str(v)
    local time = os.date('%Y-%m-%d %H:%M:%S', v.Timestamp)
    local fields = {v.RecordId, time, v.SensorName, v.SensorType, v.Level, v.Status, v.Description}
    -- 对于长字符串需要转移防止逗号被当做分隔符
    for k, val in ipairs(fields) do
        if type(val) == 'string' and val:find(',') then
            fields[k] = string.format([["%s"]], val)
        end
    end
    return fields
end

-- 打包sel记录为csv格式到指定目录
function sel_common.dump_sel_to_csv(list, path)
    local f = file_sec.open_s(path, 'w')
    if not f then
        log:error('open export file [sensor_sel.csv] failed')
        return false
    end
    mc_utils.safe_close_file(f, function()
        -- 格式化事件列表，并且写入到文件
        local fmt = '%s,%s,%s,%s,%s,%s,%s\n'
        f:write('ID,Timestamp,SensorName,SensorType,Level,Status,Description\n')
        for _, v in pairs(list) do
            f:write(string.format(fmt, table.unpack(sel_to_str(v))))
        end
    end)
    return true
end

-- 将查询的事件/告警列表以键值对的方式格式化
function sel_common.format_sel_list(base, source)
    local res = {}
    local struct, k_v
    for _, event in pairs(source) do
        for i = 1, #base do
            struct = {MappingTable = {}}
            k_v = {Key = base[i], Value = tostring(event[base[i]])}
            struct.MappingTable[#struct.MappingTable + 1] = k_v
            res[#res + 1] = struct
        end
    end
    return res
end

function sel_common.dump_sel_raw_data(path, events)
    local dumps = {}
    local item

    -- 格式化标题
    local title = {
        "RecordId", "SelType", "Timestamp", "GenerateId", "SelMsgVersion", "SensorType",
        "SensorNumber", "SelEventType", "SelData1", "SelData2", "SelData3", "SensorId",
        "SensorName", "SubjectName", "RecordSeq"
    }
    dumps[#dumps + 1] = table.concat(title, ' ') .. '\n'

    for i = 1, #events do
        item = {}
        for j = 1, #title do
            table.insert(item, tostring(events[i][title[j]]))
        end
        dumps[#dumps + 1] = table.concat(item, ' ') .. '\n'
    end

    local all_dump_info = table.concat(dumps, '')

    local f, err = require('utils.file').open_s(path, 'w')
    if not f then
        log:error('[sel_raw_data.dat] open or check file failed, err: %s', err)
        return
    end
    mc_utils.chmod(path, mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)
    f:write(all_dump_info)
    f:close()
end

function sel_common.dump_sel_log(path, events)
    local dumps = {}
    local item

    local title = {
        "RecordId", "Timestamp", "SensorName", "Level", "SensorType", "Status",
        "Description"
    }
    dumps[#dumps + 1] = table.concat(title, ',') .. '\n'

    -- 格式化对应的采集数据
    for i = 1, #events do
        item = {}
        for j = 1, #title do
            table.insert(item, tostring(events[i][title[j]]))
        end
        dumps[#dumps + 1] = table.concat(item, ',') .. '\n'
    end

    local all_dump_info = table.concat(dumps, '')

    local f, err = require('utils.file').open_s(path, 'w')
    if not f then
        log:error('[sel_log.csv] open or check file failed, err: %s', err)
        return
    end
    mc_utils.chmod(path, mc_utils.S_IRUSR | mc_utils.S_IWUSR | mc_utils.S_IRGRP)
    f:write(all_dump_info)
    f:close()
end

return sel_common