-- 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 led_tube_object = require 'led_tube_object'
local log = require 'mc.logging'
local skynet = require 'skynet'
local class = require 'mc.class'
local mdb = require 'mc.mdb'
local context = require 'mc.context'
local LED_TUBE_UPDATE_DELAY = 12000

local led_tube_service = class()

function led_tube_service:ctor(bus)
    -- 长度运算符遇到nil不会截断，重写元方法实现
    self.code_display_list = setmetatable({len = 0, datas = {}}, {
        __newindex = function (t, k, v)
            t.datas[k] = v
            if v == nil then
                if k <= t.len and k > 0 then
                    t.len = k - 1
                end
            elseif k == t.len + 1 then
                local next_i = k + 1
                while t.datas[next_i] ~= nil do
                    next_i = next_i + 1
                end
                t.len = next_i - 1
            end
        end,
        __index = function (t, k)
            return t.datas[k]
        end,
        __len = function (t)
            return t.len
        end
    })
    self.test_flag = false
    self.bus = bus
    self.event_obj = nil
    self.last_event_record = -1
    self.display_list_changed = true
end

-- 系统启动时，LED数码管自检测试
function led_tube_service:led_tube_dft_self_test()
    local TEST_RETRY_TIMES<const> = 3
    local LIGHT_ON_DATA<const> = '888'
    local LIGHT_OFF_DATA<const> = '   '
    for _ = 1, TEST_RETRY_TIMES do
        -- 全亮显示1s
        self.led_tube_object:set_all_tube_value(LIGHT_ON_DATA)
        skynet.sleep(100)
        -- 全灭显示1s
        self.led_tube_object:set_all_tube_value(LIGHT_OFF_DATA)
        skynet.sleep(100)
    end
    -- 检测完成后，开始刷新错误码
    skynet.fork_loop({count = 0}, function()
        skynet.sleep(LED_TUBE_UPDATE_DELAY) -- 启动阶段等待2分钟后刷新错误码
        self:refresh_display_list()
    end)
    skynet.fork_loop({count = 0}, function()
        skynet.sleep(LED_TUBE_UPDATE_DELAY) -- 启动阶段等待2分钟后刷新错误码
        self:led_tube_scan_task()
    end)
end

-- 二分查找上界
local function upper_bound(list, data)
    if data >= list[#list] then
        return nil
    end
    local l = 1
    local r = #list
    while l < r do
        local mid = (l + r) >> 1
        if list[mid] <= data then
            l = mid + 1
        else
            r = mid
        end
    end
    return l
end

local function format_event_list(list)
    if #list == 0 then
        return {}
    end
    -- 计算结构体参数个数num，接口返回的list为 sel * num 个数组,无法通过key直接获取sel对象，同时一个对象的参数个数不固定
    local num = 0
    local end_flag = list[1].MappingTable[1].Key -- 以第一个Key为标记，重复时说明遍历到第二个对象
    for key, value in pairs(list) do
        if value.MappingTable[1].Key == end_flag and key ~= 1 then
            break
        end
        num = num + 1
    end
    local count = 0
    local res = {}
    local event = {}
    for _, v in pairs(list) do
        event[v.MappingTable[1].Key] = v.MappingTable[1].Value
        count = count + 1
        if count % num == 0 then -- 每num个参数组成一个对象
            local temp = event
            event = {}
            res[#res + 1] = temp
        end
    end
    return res
end

local EVENTS_PATH<const> = '/bmc/kepler/Systems/1/Events'
local EVENTS_INTF<const> = 'bmc.kepler.Systems.Events'

-- 获取最新当前告警列表
function led_tube_service:get_latest_alarm_list()
    if not self.event_obj then
        local ok, event_obj = pcall(mdb.get_object, self.bus, EVENTS_PATH, EVENTS_INTF)
        if not ok then
            log:raise('[ledtube] get Events object fail')
        end
        self.event_obj = event_obj
    end

    local ok, ret = self.event_obj.pcall:GetAlarmList_PACKED(context.get_context_or_default(), 1, 0xffff, {})
    if not ok then
        log:raise('[ledtube] get event list fail, error: %s', ret)
    end
    if not ret.EventList then
        log:info('[ledtube] event list is empty')
        return
    end
    self.last_event_record = self.event_obj.EventRecordSeq
    return format_event_list(ret.EventList)
end

-- 常驻任务，轮询当前告警，并获取对外屏显错误码
function led_tube_service:refresh_display_list()
    local func = function()
        local ok, event_list = pcall(led_tube_service.get_latest_alarm_list, self)
        if not ok or not event_list then
            return
        end
        -- 仅在获取成功时更新code_display_list
        local temp_list = {}
        self.code_display_list[1] = nil
        local code = nil
        for _, event in pairs(event_list) do
            code = event.LedFaultCode
            if event.LedFaultCode and not temp_list[event.LedFaultCode] and #event.LedFaultCode > 0 then
                local len = #self.code_display_list
                self.code_display_list[len + 1] = code
                self.code_display_list[len + 2] = nil
                temp_list[code] = 1
            end
        end
        table.sort(self.code_display_list)
        self.display_list_changed = true
    end
    while self.led_tube_object do
        func()
        skynet.sleep(500)
    end
end

function led_tube_service:led_tube_scan_task()
    local DEFAULT_ERR_CODE<const> = '---'
    local disp_data = ''
    local last_index = 0
    local func = function()
        -- 当前处于测试状态，无后续操作
        if self.test_flag then
            return
        end
        -- 队列为空显示默认值
        if #self.code_display_list == 0 then
            log:debug('[ledtube] no code should be displayed')
            self.led_tube_object:set_all_tube_value(DEFAULT_ERR_CODE)
            disp_data = '' -- 初始化disp_data
            return
        end
        local disp_data_idx = last_index % #self.code_display_list + 1 -- 循环显示
        if self.display_list_changed then
            self.display_list_changed = false
            disp_data_idx = upper_bound(self.code_display_list, disp_data) or 1 -- 循环显示
        end
        last_index = disp_data_idx
        disp_data = self.code_display_list[disp_data_idx]
        self.led_tube_object:set_all_tube_value(disp_data)
    end
    while self.led_tube_object do
        func()
        skynet.sleep(500) -- 5秒钟更新数码管信息一次
    end
end

function led_tube_service:add_object_complete(position)
    if position == self.position then
        skynet.fork_once(function ()
            skynet.sleep(100)
            self:led_tube_dft_self_test()
        end)
    end
end

function led_tube_service:add_object(obj, position)
    self.led_tube_object = led_tube_object.new(obj)
    self.position = position
end

return led_tube_service
