-- 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: 面板按钮相关

local log = require 'mc.logging'
local class = require 'mc.class'
local skynet = require 'skynet'
local ipmi = require 'ipmi'
local m_error_custom = require 'messages.custom'
local ipmi_msg = require 'fructrl.ipmi.ipmi'
local enums = require 'types.enums'
local utils = require 'types.utils'
local initiator = require 'mc.initiator'
local singleton = require 'mc.singleton'
local c_multihost = require 'multi_host'

local c_button_evt = class()

function c_button_evt:ctor(db)
    self.db = db
    self.obj = nil
    self.fructrl = nil
end

function c_button_evt:add_buttonevt_callback(object, fructrl)
    self.obj = object
    self.fructrl = fructrl
    log:notice_easy("set button_evt object successfully, uptime: %s.", utils.uptime())

    self.obj.property_changed:on(function(prop, value)
        if prop == 'GetPowerBtnEvt' then
            self:dealwith_pwr_button_evt(value)
        elseif prop =='PowerButtonShieldEnabled' then
            log:notice_easy('power button Shield Enabled change to [%s]', value)
            self:set_PwrButtonShield(value and 1 or 0)
        end
    end)

    -- 例测电源按钮事件
    skynet.fork_loop({count = 0}, function()
        self:clear_button_pressed_event()
    end)

    skynet.fork_once(function()
        local max_time = 60
        while true do
            if self:set_pwr_button_status() == enums.RetValue.OK then
                break
            end
            max_time = max_time - 1
            if max_time <= 0 then
                log:error("PwrButtonShield or PwrButtonLock set value faild, and retry times has reached 60 times.")
                break
            end
            skynet.sleep(1000) -- 模块初始化后，每10s重试设置pwr button status
        end
        if not c_multihost:get_instance():is_multihost_type() then
            self:listen_pwr_on_lock()
        end
    end)
end

function c_button_evt:update_pwr_button_lock(value)
    if self.fructrl:get_PanelPowerButtonEnabled() then
        self:set_PwrButtonLock(value and 1 or 0)
        log:notice_easy("The panel button is %s again due to the soft power on lock",
            value and 'sheild' or 'unsheild')
    end
end

function c_button_evt:listen_pwr_on_lock()
    self.fructrl.obj:get_mdb_object("bmc.kepler.Systems.PowerOnLock").property_changed:on(function(prop, value)
        if prop == 'PwrOnLocked' then
            self:update_pwr_button_lock(value)
        end
    end)
    if self.fructrl:get_PwrOnLocked() then
        self:update_pwr_button_lock(true)
    end
end

local function get_operation(obj, prop)
    local ret = nil
    local ok, msg = pcall(function ()
        ret = obj[prop]
    end)
    if not ok then
        log:error_easy('get prop %s failed, error_msg:%s', prop, msg)
    end
    return ret
end

local function set_operation(self, prop, value)
    local ok, msg = pcall(function ()
        self.obj[prop] = value
    end)
    if not ok then
        log:error_easy('set prop %s to %s failed, error_msg:%s', prop, value, msg)
    end
    return ok
end

function c_button_evt:set_pwr_button_status()
    -- 设置按钮防误触状态，true为短按无法使用，false为短按可以使用
    local data = self:get_PowerButtonShieldEnabled()
    if data == nil then
        return enums.RetValue.ERR
    end
    data = data and 1 or 0
    if self:set_PwrButtonShield(data) ~= enums.RetValue.OK then
        return enums.RetValue.ERR
    end
    -- 设置按钮屏蔽状态, true为电源按钮需要生效，关闭电源屏蔽功能
    local val = self.fructrl:get_PanelPowerButtonEnabled()
    if val == nil then
        return enums.RetValue.ERR
    end
    val = val and 0 or 1
    if self:set_PwrButtonLock(val) ~= enums.RetValue.OK then
        return enums.RetValue.ERR
    end
    return enums.RetValue.OK
end

function c_button_evt:clear_button_pressed_event()
    -- 检测到有按钮按下及时清除，避免长按按钮无法上报事件
    while true do
        if self:get_PwrButtonEvt() == 1 then
            skynet.sleep(20) -- 延时200ms，防止与监听冲突
            self:clear_PwrButtonEvt()
        end
        skynet.sleep(100) -- 1s钟例测一次按钮事件
    end
end

function c_button_evt:ipmi_cmd_set_pwrbutton_shield_state(req, ctx)
    if req.FruId > 0 or req.ShieldState > 1 then
        log:error_easy("Parameter is out of range.")
        error(m_error_custom.IPMIOutOfRange())
    end

    local state_db = false -- 数据库存的值
    local state_log = "Disable" -- 操作日志记录的值
    if req.ShieldState ~= 0 then
        state_db = true
        state_log = "Enable"
    end
    -- 该ipmi命令仅开启电源按钮防误触，不开启电源按钮屏蔽
    self:set_PowerButtonShieldEnabled(state_db)

    local multihost = c_multihost:get_instance()
    local sys_id = not ctx.HostId and 1 or ctx.HostId
    local print_sys = multihost:is_multihost_type() and '[System:' .. sys_id .. ']' or ''

    ipmi.ipmi_operation_log(ctx, 'fructrl',
        '%sSuccessfully %s the function of shielding the short-press power button', print_sys, state_log)

    local rsp = ipmi_msg.SetButtonShieldState.rsp.new()
    rsp.CompletionCode = 0x00
    rsp.ManufactureId = req.ManufactureId
    return rsp
end

function c_button_evt:ipmi_cmd_get_pwrbutton_shield_state(req, ctx)
    if req.FruId > 0 then
        log:error_easy("Parameter is out of range.")
        error(m_error_custom.IPMIOutOfRange())
    end

    local shield_state = 0x00
    if self:get_PowerButtonShieldEnabled() then
        shield_state = 0x01
    end

    local rsp = ipmi_msg.GetButtonShieldState.rsp.new()
    rsp.CompletionCode = 0x00
    rsp.State = shield_state
    rsp.ManufactureId = req.ManufactureId
    return rsp
end

function c_button_evt:set_PwrButtonLock(val)
    if set_operation(self, 'PowerBtnLock', val) then
        log:notice_easy('execute pwr button lock, val=(%d)', val)
        return enums.RetValue.OK
    end
    return enums.RetValue.ERR
end

function c_button_evt:set_PwrButtonShield(val)
    if set_operation(self, 'PowerBtnShield', val) then
        log:notice_easy('execute pwr button shield, val=(%d)', val)
        return enums.RetValue.OK
    end
    return enums.RetValue.ERR
end

-- 模块内修改电源按钮防误触属性
function c_button_evt:set_PowerButtonShieldEnabled(val)
    if set_operation(self, 'PowerButtonShieldEnabled', val) then
        log:notice_easy('execute pwr button shield enable, val=(%s)', val)
        return enums.RetValue.OK
    end
    return enums.RetValue.ERR
end

function c_button_evt:get_PowerButtonShieldEnabled()
    return get_operation(self.obj, 'PowerButtonShieldEnabled')
end

function c_button_evt:clear_PwrButtonEvt()
    if set_operation(self, 'SetPowerBtnEvt', 0) then
        log:notice_easy('clear power button event')
        return enums.RetValue.OK
    end
    return enums.RetValue.ERR
end

function c_button_evt:get_PwrButtonEvt()
    return get_operation(self.obj, 'SetPowerBtnEvt')
end

local button_evt_flag = 0 -- 记录按钮按下标记，用于装备等模块获取状态
function c_button_evt:get_button_evt_flag()
    return button_evt_flag
end

function c_button_evt:set_button_evt_flag(val)
    button_evt_flag = val
end

function c_button_evt:dealwith_pwr_button_evt(val)
    log:notice_easy('power button event change to [%s]', val)
    if val ~= 1 then
        return
    end

    self.fructrl.sys_reset_queue(function()
        self.fructrl:add_RestartCauseRecords(enums.RestartCause.PowerUpViaPushButton)
        self.fructrl:set_sys_reset_flag(true)
    end)
    self:set_button_evt_flag(1)
    -- 记录按钮事件
    if self.fructrl:get_PanelPowerButtonEnabled() then
        log:operation(initiator.new('PANEL', 'N/A', '127.0.0.1'), 'fructrl',
            'Press power button successfully')
    else
        log:operation(initiator.new('PANEL', 'N/A', '127.0.0.1'), 'fructrl',
            'Power button pressed but this action is invalid, power button on the panel is disabled')
    end
    -- 清除事件
    self:clear_PwrButtonEvt()
end

return singleton(c_button_evt)