-- 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 skynet = require 'skynet'
local log = require 'mc.logging'
local m_error_base = require 'messages.base'
local class = require 'mc.class'
local utils = require 'mc.utils'
local queue = require 'skynet.queue'

local LOCK_FOREVER = 0xFFFF -- 用特殊的超时时间代表永久锁

local pwr_on_lock = class()

function pwr_on_lock:ctor(fructrl)
    self.fructrl = fructrl
    self.system_id = fructrl.system_id
    self.poweron_lock_queue = queue()
end

-- 维护PwrOnLockedRecord属性，记录上电锁信息。每种锁记录一行信息，超时或解锁后删除。
-- 增
local function add_poweron_lock(fructrl, pwronlocked, timeout, appname, reason)
    if not appname or not reason then
        log:error("[System:%s]appname or reason is nil %s %s", fructrl.system_id ,appname, reason)
        return
    end
    local val = fructrl.PwrOnLockedRecord
    local data = {
        appname = appname,
        reason = reason,
        pwronlocked = pwronlocked,
        timeout = timeout
    }
    val[appname .. '--' .. reason] = data
    fructrl.PwrOnLockedRecord = val

    if timeout == LOCK_FOREVER then
        log:notice_easy('[System:%s]Attention, [%s] will forbid power on forever!!!', fructrl.system_id, appname)
    else
        log:notice_easy('[System:%s][%s] add item to lock_table successfully.', fructrl.system_id, appname)
    end
end

-- 删
local function del_poweron_lock(fructrl, key)
    local val = fructrl.PwrOnLockedRecord
    -- 不能用remove的方式删除，任务会退出
    val[key] = nil
    fructrl.PwrOnLockedRecord = val
end

-- 改
local function update_poweron_lock(fructrl, pwronlocked, timeout, appname, reason)
    if not appname or not reason then
        log:error("[System:%s]appname or reason is nil %s %s", fructrl.system_id, appname, reason)
        return
    end
    local val = fructrl.PwrOnLockedRecord
    for _, v in pairs(val) do
        if v.appname == appname and v.reason == reason then
            v.pwronlocked = pwronlocked
            v.timeout = timeout
            log:notice_easy('[System:%s]Update item successfully, appname=(%s), reason=(%s), pwronlocked=(%s),' ..
                ' timeout=(%d)', fructrl.system_id, appname, reason, pwronlocked, timeout)
            if timeout == LOCK_FOREVER then
                log:notice_easy('[System:%s]Attention, [%s] will forbid power on forever!!!',
                     fructrl.system_id, appname)
            end
            fructrl.PwrOnLockedRecord = val
            return true
        end
    end
    return false
end

-- 查
local function find_poweron_lock(fructrl, appname, reason)
    local val = fructrl.PwrOnLockedRecord
    for k, v in pairs(val) do
        if v.appname == appname and v.reason == reason then
            return k
        end
    end
end

local function get_reasons_in_table(fructrl)
    local val = fructrl.PwrOnLockedRecord
    local reasons = {}
    for _, v in pairs(val) do
        table.insert(reasons, v.reason)
    end
    return reasons
end

-- 例测任务
local function poweron_overtime_task(self, appname, reason)
    local key = find_poweron_lock(self.fructrl, appname, reason)
    local val, is_lock
    if not key then
        log:error_easy('[System:%s]Get wrong key in poweron_lock_table!!!', self.system_id)
        return
    end

    while true do
        skynet.sleep(100)
        is_lock = self.poweron_lock_queue(function()
            val = self.fructrl.PwrOnLockedRecord
            val = utils.table_copy(val)
            -- 永久锁不能超时退出，只能解锁退出
            if not (val[key].timeout == LOCK_FOREVER) then
                val[key].timeout = val[key].timeout - 1
                self.fructrl.PwrOnLockedRecord = val
            end
            -- 解锁
            if not val[key].pwronlocked then
                -- 删除锁
                del_poweron_lock(self.fructrl, key)
                return false
            end
            -- 超时
            if val[key].timeout <= 0 then
                log:error_easy('[System:%s]Poweron lock is overtime!!!', self.system_id)
                -- 删除锁
                del_poweron_lock(self.fructrl, key)
                return false
            end
            return true
        end)
        if not is_lock then
            break
        end
    end

    -- 超时或解锁都删除锁
    log:notice_easy('[System:%s]Delete pwr_on_lock item, appname=(%s), reason=(%s)', self.system_id, appname, reason)
    val = self.fructrl.PwrOnLockedRecord
    -- 如果表格空
    if next(val) == nil then
        -- 解锁
        self.fructrl.PwrOnLocked = false
        log:notice('[System:%s]PwrOnLocked has been unlocked', self.system_id)
    end
    self.fructrl.Reasons = get_reasons_in_table(self.fructrl)
end

-- 入口函数
function pwr_on_lock:poweron_lock_entrance(pwronlocked, timeout, appname, reason)
    -- 入参判断，已对齐结论，合理值范围在10-3600s
    if (timeout ~= LOCK_FOREVER) and (timeout < 10 or timeout > 3600) then
        log:error_easy('[System:%s]Wrong input parameter when call SetPowerOnLock!', self.system_id)
        error(m_error_base.PropertyValueNotInList('%PowerLockTimeout:' .. timeout, '%PowerLockTimeout'))
    end

    local val = utils.table_copy(self.fructrl.PwrOnLockedRecord)
    if not val then
        val = {}
        self.fructrl.PwrOnLockedRecord = val
    end

    self.poweron_lock_queue(function()
        -- 先尝试找到对应项，并刷新数据（包括解锁）
        if update_poweron_lock(self.fructrl, pwronlocked, timeout, appname, reason) then
            return
        end
        -- 表格里没有该项，并且为解锁
        if not pwronlocked then
            log:debug('[System:%s]Item is not exist in poweron_lock_table when unlock.', self.system_id)
            return
        end

        -- 加锁
        add_poweron_lock(self.fructrl, pwronlocked, timeout, appname, reason)
        -- 刷新资源树,pwronlocked和reasons
        self.fructrl.PwrOnLocked = true
        self.fructrl.Reasons = get_reasons_in_table(self.fructrl)
        -- 创建例测超时任务
        skynet.fork_once(function()
            poweron_overtime_task(self, appname, reason)
        end)
    end)
end

function pwr_on_lock:restart_task()
    local val = self.fructrl.PwrOnLockedRecord
    for _, v in pairs(val) do
        skynet.fork_once(function()
            poweron_overtime_task(self, v.appname, v.reason)
        end)
    end

end

function pwr_on_lock:is_power_locked_by_upgrade()
    local lock_record = self.fructrl.PwrOnLockedRecord
    if lock_record == nil then
        return false
    end

    local upgrade_mode_flag_table = {
        Upgrade = {
            bios = true
        },
        general_hardware = {
            cpld = true,
            Vrd = true,
            NPU_VRD = true,
            fpga = true
        }
    }

    for _, record in pairs(lock_record) do
        if upgrade_mode_flag_table[record.appname] and upgrade_mode_flag_table[record.appname][record.reason] then
            return true
        end
    end
    return false
end

return pwr_on_lock
