-- 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 c_object = require 'mc.orm.object'
local log = require 'mc.logging'
local crc16 = require 'mc.crc16'
local skynet = require 'skynet'
local ctx = require 'mc.context'
local client = require 'chassis.client'
local chassis_def = require 'def.chassis_def'
local custom_messages = require 'messages.custom'

local c_super_pod_label = c_object('SuperPodLabel')
local RETRY_TIMES <const> = 3

-- 获取电源状态
local function get_power_status()
    local obj_list = client:GetFruCtrlObjects()
    if obj_list and next(obj_list) then
        for _, obj in pairs(obj_list) do
            return obj.PowerState
        end
    end
end

function c_super_pod_label:ctor()
end

local PROP_TYPE_MAP<const> = {
    SuperPodId = {prop_type = 'I4', prop_reg = 'SuperPodIdAcc'},
    ServerIndex = {prop_type = 'I2', prop_reg = 'ServerIndexAcc'},
    SuperPodSize = {prop_type = 'I2', prop_reg = 'SuperPodSizeAcc'},
    TopologyType = {prop_type = 'I1', prop_reg = 'TopologyTypeReg'},
    NPUBootOption = {prop_type = 'I1', prop_reg = 'NPUBootOptionReg'},
    UBMDeployMode = {prop_type = 'I1', prop_reg = 'UBMDeployModeReg'},
    ChassisId = {prop_type = 'I2', prop_reg = 'ChassisIdReg'},
    PresentDPUCount = {prop_type = 'I1', prop_reg = 'PresentDPUCountReg'},
    InterconType = {prop_type = 'I1', prop_reg = 'InterconTypeReg'},
    ConfigurationCompletedFlag = {prop_type = 'I1', prop_reg = 'ConfigurationCompletedReg'}
}

local POLICY_BIT_MAP<const> = {
    BIT_0 = 1,  -- 超节点信息写入BMC，持久化
    BIT_1 = 2   -- 超节点信息写入硬件
}

function c_super_pod_label:init_super_pod_label()
    local super_pod_label_info = 'super pod label'
    for key, value in pairs(PROP_TYPE_MAP) do
        local prop_reg = value.prop_reg
        super_pod_label_info = super_pod_label_info .. ', ' .. key .. ': ' .. self[key]
        if key ~= 'ConfigurationCompletedFlag' then
           self[prop_reg] = self[key]
        end
    end
    local superpod_labels = {
    }
    self.Crc16 = self:cal_crc16(superpod_labels)
    self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
    log:notice('init %s successfully', super_pod_label_info)
end

function c_super_pod_label:init()
    local times = 1
    local res = false
    local err
    while not res and times <= RETRY_TIMES do
        res, err = pcall(function ()
            self:init_super_pod_label()
        end)
    end

    if not res then
        log:error('init SuperPodLabel failed, error : %s', err)
    end

    c_super_pod_label.super.init(self)
end

function c_super_pod_label:cal_crc16(superpod_labels)
    local data = ''
    local super_pod_label_crc16 = 'super pod label'
    for key, value in ipairs(self.CrcCalcParams) do
        if not PROP_TYPE_MAP[value] then
            log:error('%s is invalid', value)
            error('CrcCalcParams config invalid.')
        end
        local prop_type = PROP_TYPE_MAP[value].prop_type
        if superpod_labels[value] then
            super_pod_label_crc16 = super_pod_label_crc16 .. ', ' .. value .. ': ' .. superpod_labels[value]
            data = data .. string.pack(prop_type, superpod_labels[value])
        else
            super_pod_label_crc16 = super_pod_label_crc16 .. ', ' .. value .. ': ' .. self[value]
            data = data .. string.pack(prop_type, self[value])
        end
    end
    local crc_value = crc16(data)
    log:notice('Calculate %s crc16 value: %s', super_pod_label_crc16, crc_value)
    return crc_value
end

function c_super_pod_label:update_super_pod_id(super_pod_id)
    log:notice('update super pod id, value : %d', super_pod_id)
    local times = 1
    local res = false
    local superpod_labels = {
        SuperPodId = super_pod_id
    }
    while not res and times <= RETRY_TIMES do
        res = pcall(function ()
            self.SuperPodIdAcc = super_pod_id
            self.Crc16 = self:cal_crc16(superpod_labels)
            self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
        end)
        skynet.sleep(1)
        times = times + 1
    end
    if not res then
        log:error("update super pod id failed")
        return false
    end
    if self.SuperPodId ~= super_pod_id then
        self.SuperPodId = super_pod_id
    end
    return res
end

function c_super_pod_label:update_server_index(server_index)
    log:notice('update server index, value : %d', server_index)
    local times = 1
    local res = false
    local superpod_labels = {
        ServerIndex = server_index
    }
    while not res and times <= RETRY_TIMES do
        res = pcall(function ()
            self.ServerIndexAcc = server_index
            self.Crc16 = self:cal_crc16(superpod_labels)
            self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
        end)
        skynet.sleep(1)
        times = times + 1
    end
    if not res then
        log:error("update server index failed")
        return false
    end
    if self.ServerIndex ~= server_index then
        self.ServerIndex = server_index
    end
    return res
end

function c_super_pod_label:update_super_pod_size(super_pod_size)
    log:notice('update super pod size, value : %d', super_pod_size)
    local times = 1
    local res = false
    local superpod_labels = {
        SuperPodSize = super_pod_size
    }
    while not res and times <= RETRY_TIMES do
        res = pcall(function ()
            self.SuperPodSizeAcc = super_pod_size
            self.Crc16 = self:cal_crc16(superpod_labels)
            self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
        end)
        skynet.sleep(1)
        times = times + 1
    end
    if not res then
        log:error("update super pod size failed")
        return false
    end
    if self.SuperPodSize ~= super_pod_size then
        self.SuperPodSize = super_pod_size
    end
    return res
end

function c_super_pod_label:update_super_pod_enabled(super_pod_enabled)
    log:notice('update super pod enabled, value : %s', super_pod_enabled)
    --下电状态不更新 
    local ret = get_power_status()
    if ret and ret ~= "OFF" then
        log:error("current power state is %s, expect OFF for update superpod-enbaled property", ret)
        error(custom_messages.PropertyModificationNotSupported('SuperPodEnabled'))
    end
    if self.SuperPodEnabled ~= super_pod_enabled then
        self.SuperPodEnabled = super_pod_enabled
    end
end

function c_super_pod_label:restore_defaults()
    local superpod_labels = {
        ServerIndex = self.DefaultServerIndex,
        SuperPodId = self.DefaultSuperPodId,
        SuperPodSize = self.DefaultSuperPodSize,
        TopologyType = self.DefaultTopologyType,
        UBMDeployMode = self.DefaultUBMDeployMode,
        ChassisId = self.DefaultChassisId,
        PresentDPUCount = self.DefaultPresentDPUCount,
        NPUBootOption = self.DefaultNPUBootOption,
        InterconType = self.DefaultInterconType
    }
    -- 先更新硬件值 
    local ok, err = pcall(function ()
        self.ServerIndexAcc = self.DefaultServerIndex
        self.SuperPodIdAcc = self.DefaultSuperPodId
        self.SuperPodSizeAcc = self.DefaultSuperPodSize
        self.TopologyTypeReg = self.DefaultTopologyType
        self.UBMDeployModeReg = self.DefaultUBMDeployMode
        self.ChassisIdReg = self.DefaultChassisId
        self.PresentDPUCountReg = self.DefaultPresentDPUCount
        self.NPUBootOptionReg = self.DefaultNPUBootOption
        self.InterconTypeReg = self.DefaultInterconType
        self.Crc16 = self:cal_crc16(superpod_labels)
        self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
    end)

    if not ok then
        log:error("restore super pod label defaults failed, err: %s", err)
        return ok
    end
    -- 硬件值更新成功了，在更新持久化值 
    self.ServerIndex = self.DefaultServerIndex
    self.SuperPodId = self.DefaultSuperPodId
    self.SuperPodSize = self.DefaultSuperPodSize
    self.TopologyType = self.DefaultTopologyType
    self.UBMDeployMode = self.DefaultUBMDeployMode
    self.ChassisId = self.DefaultChassisId
    self.PresentDPUCount = self.DefaultPresentDPUCount
    self.NPUBootOption = self.DefaultNPUBootOption
    self.InterconType = self.DefaultInterconType

    return ok
end

local function get_op_initiator()
    local initiator = {}
    local context = ctx.get_context()
    if context and not context:is_empty() then
        initiator = context:get_initiator()
    else
        local mc_initiator = require 'mc.initiator'
        initiator = mc_initiator.new('N/A', 'N/A', 'localhost')
    end

    return initiator
end

function c_super_pod_label:set_superPod_label(policy, superpod_labels)
    local crc16_table = {}
    for _, superpod_property in pairs(superpod_labels) do
        local name = superpod_property.Key
        local value = superpod_property.Value
        if not name or not value then
            log:notice('name (%s) or value(%s) invalid', name, value)
            log:operation(get_op_initiator(), 'Chassis', 'Set SuperPodLabel failed')
            error(custom_messages.OperationFailed())
        end
        crc16_table[name] = value
        if name == 'IsUBFabricMode' or name == 'IsUBControlNode' then
            value = (value == 1) and true or false
            self[name] = value
            log:operation(get_op_initiator(), 'Chassis', 'Set %s to (%s) successfully', name, value)
            goto continue
        end
        -- 更新硬件值
        if policy & POLICY_BIT_MAP.BIT_1 == POLICY_BIT_MAP.BIT_1  then
            local ok, res = pcall(function ()
                self[PROP_TYPE_MAP[name].prop_reg] = value
            end)
            if not ok then
                log:notice('update %s failed, err : %s', name, res)
                log:operation(get_op_initiator(), 'Chassis', 'Set %s to (%s) failed', name, value)
                error(custom_messages.OperationFailed())
            end
        end
        -- 更新持久化值
        if policy & POLICY_BIT_MAP.BIT_0 ==  POLICY_BIT_MAP.BIT_0 then
            self[name] = value
            if name == 'PresentDPUCount' then
                log:operation(get_op_initiator(), 'Chassis', 'Set %s to (%s) successfully', name, chassis_def.PRESENT_DPU_COUNT_TREE_TO_REAL[value])
                goto continue
            end
            log:operation(get_op_initiator(), 'Chassis', 'Set %s to (%s) successfully', name, value)
        end
        ::continue::
    end
    -- 计算crc
    local ok, res = pcall(function ()
        self.Crc16 = self:cal_crc16(crc16_table)
        self.ConfigurationCompletedReg = self.ConfigurationCompletedFlag
    end)
    if not ok then
        log:notice('update crc16 failed, err : %s', res)
        log:operation(get_op_initiator(), 'Chassis', 'Set SuperPodLabel failed')
        error(custom_messages.OperationFailed())
    end
    return true
end

return c_super_pod_label