-- 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 singleton = require 'mc.singleton'
local c_super_pod_label_object = require 'super_pod_label_object'
local class = require 'mc.class'
local c_object = require 'mc.orm.object'
local custom_messages = require 'messages.custom'
local log = require 'mc.logging'
local ipmi_msg = require 'chassis.ipmi.ipmi_message'
local ipmi = require 'mc.ipmi'
local cc = ipmi.types.Cc
local bs = require 'mc.bitstring'
local client = require 'chassis.client'
local MIN_SUPERPOD_SIZE<const> = 1
local MAX_SUPERPOD_SIZE<const> = 384
local SUPERPOD_LABEL_ENABLED<const> = 0x01
local SUPERPOD_LABEL_DISABLED<const> = 0x00

local c_super_pod_label_collection = class()

function c_super_pod_label_collection:ctor()
end

function c_super_pod_label_collection:SetSuperPodId(ctx, SuperPodId)
    local super_pod_label_object = c_object('SuperPodLabel').collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodId to (%d) failed', SuperPodId)
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end
    local res = super_pod_label_object:update_super_pod_id(SuperPodId)
    if not res then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodId to (%d) failed', SuperPodId)
        error(custom_messages.ResourceNotFound('SuperPodId'))
    end
    log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodId to (%d) successfully', SuperPodId)
    return true
end

function c_super_pod_label_collection:SetServerIndex(ctx, ServerIndex)
    local super_pod_label_object = c_object('SuperPodLabel').collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set ServerIndex to (%d) failed', ServerIndex)
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end
    local res = super_pod_label_object:update_server_index(ServerIndex)
    if not res then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set ServerIndex to (%d) failed', ServerIndex)
        error(custom_messages.ResourceNotFound('ServerIndex'))
    end
    log:operation(ctx:get_initiator(), 'Chassis', 'Set ServerIndex to (%d) successfully', ServerIndex)
    return true
end

function c_super_pod_label_collection:SetSuperPodSize(ctx, SuperPodSize)
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodSize to (%d) failed', SuperPodSize)
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end

    local res = super_pod_label_object:update_super_pod_size(SuperPodSize)
    if not res then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodSize to (%d) failed', SuperPodSize)
        error(custom_messages.ResourceNotFound('SuperPodSize'))
    end
    log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodSize to (%d) successfully', SuperPodSize)
    return true
end

function c_super_pod_label_collection:SetSuperPodEnabled(ctx, SuperPodEnabled)
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodEnabled to (%s) failed', SuperPodEnabled)
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end

    local res = pcall(super_pod_label_object.update_super_pod_enabled, super_pod_label_object, SuperPodEnabled)
    if not res then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodEnabled to (%s) failed', SuperPodEnabled)
        error(custom_messages.PropertyModificationNotSupported('SuperPodEnabled'))
    end
    log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodEnabled to (%s) successfully', SuperPodEnabled)
    return
end

function c_super_pod_label_collection:RestoreDefaults(ctx)
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Restore SuperPodLabel to the default value failed')
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end
    local res = super_pod_label_object:restore_defaults()
    if not res then
        log:operation(ctx:get_initiator(), 'Chassis', 'Restore SuperPodLabel to the default value failed')
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end
    log:operation(ctx:get_initiator(), 'Chassis', 'Restore SuperPodLabel to the default value successfully')
    return true
end

function c_super_pod_label_collection:SetSuperPodLabel(ctx, policy, superpod_labels)
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        log:operation(ctx:get_initiator(), 'Chassis', 'Set SuperPodLabel failed')
        error(custom_messages.ResourceNotFound('SuperPodLabel'))
    end
    --下发配置
    super_pod_label_object:set_superPod_label(policy, superpod_labels)
end

function c_super_pod_label_collection:ipmi_get_superpod_label_info(req, ctx)
    local rsp = ipmi_msg.GetSuperPodLabelInfoRsp.new()
    rsp.ManufactureId = 0x0007db
    rsp.EndFlag = 0
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Get SuperPodLabel failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    rsp.Data = bs.new('<<SuperPodId:4/unit:8,ServerIndex:2/unit:8,SuperPodSize:2/unit:8>>'):pack({
        SuperPodId = super_pod_label_object.SuperPodId,
        ServerIndex = super_pod_label_object.ServerIndex,
        SuperPodSize = super_pod_label_object.SuperPodSize
    })
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_set_superpod_label_info(req, ctx)
    local rsp = ipmi_msg.SetSuperPodLabelInfoRsp.new()
    rsp.ManufactureId = 0x0007db
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local super_pod_id = (req.Data:byte(4) << 24) | req.Data:byte(3) << 16 | (req.Data:byte(2) << 8) | req.Data:byte(1)
    local server_index = (req.Data:byte(6) << 8) | req.Data:byte(5)
    local super_pod_size =  (req.Data:byte(8) << 8) | req.Data:byte(7)
    if super_pod_size < MIN_SUPERPOD_SIZE or super_pod_size > MAX_SUPERPOD_SIZE then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel failed')
        rsp.CompletionCode = cc.ParmOutOfRange
        return rsp
    end
    local res = super_pod_label_object:update_super_pod_id(super_pod_id)
    res = super_pod_label_object:update_server_index(server_index) and res
    res = super_pod_label_object:update_super_pod_size(super_pod_size) and res
    if not res then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel failed')
        rsp.CompletionCode = cc.ResponseError
        return rsp
    end
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodId to (%s), \
                    ServerIndex to (%s), SuperPodSize to (%s) successfully',
        super_pod_id, server_index, super_pod_size)
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_set_super_pod_id(req, ctx)
    local rsp = ipmi_msg.SetSuperPodIdRsp.new()
    rsp.ManufactureId = 0x0007db
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodId failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local super_pod_id = (req.Data:byte(4) << 24) | req.Data:byte(3) << 16 | (req.Data:byte(2) << 8) | req.Data:byte(1)
    local res = super_pod_label_object:update_super_pod_id(super_pod_id)
    if not res then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodId failed')
        rsp.CompletionCode = cc.ResponseError
        return rsp
    end
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodId to (%s) successfully', super_pod_id)
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_set_server_index(req, ctx)
    local rsp = ipmi_msg.SetServerIndexRsp.new()
    rsp.ManufactureId = 0x0007db
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set ServerIndex failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local server_index = (req.Data:byte(2) << 8) | req.Data:byte(1)
    local res = super_pod_label_object:update_server_index(server_index)
    if not res then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set ServerIndex failed')
        rsp.CompletionCode = cc.ResponseError
        return rsp
    end
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set ServerIndex to (%s) successfully', server_index)
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_set_super_pod_size(req, ctx)
    local rsp = ipmi_msg.SetSuperPodSizeRsp.new()
    rsp.ManufactureId = 0x0007db
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodSize failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local super_pod_size = (req.Data:byte(2) << 8) | req.Data:byte(1)
    local res = super_pod_label_object:update_super_pod_size(super_pod_size)
    if not res then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodSize failed')
        rsp.CompletionCode = cc.ResponseError
        return rsp
    end
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodSize to (%s) successfully', super_pod_size)
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_set_super_pod_enabled(req, ctx)
    local rsp = ipmi_msg.SetSuperPodEnabledRsp.new()
    rsp.ManufactureId = 0x0007db
    local obj_list = client:GetFruCtrlObjects()
    if not next(obj_list) then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodEnabled failed')
        log:error("get fru objects failed")
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    for _, obj in pairs(obj_list) do 
        if obj.PowerState ~= "OFF" then
            ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodEnabled failed')
            log:error("power state : %s, expected off", obj.PowerState)
            rsp.CompletionCode = cc.DataNotAvailable
            return rsp
        end
    end
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodEnabled failed')
        log:error("get super_pod_label object failed")
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local super_pod_enabled = req.Data:byte(1)
    if super_pod_enabled == SUPERPOD_LABEL_DISABLED then
        super_pod_enabled = false
    elseif super_pod_enabled == SUPERPOD_LABEL_ENABLED then
        super_pod_enabled = true
    else
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodEnabled failed')
        rsp.CompletionCode = cc.ParmOutOfRange
        return rsp
    end
    super_pod_label_object:update_super_pod_enabled(super_pod_enabled)
    rsp.CompletionCode = cc.Success
    --增加ipmi命令的操作日志
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodEnabled(%s) successfully', super_pod_enabled)
    return rsp
end

function c_super_pod_label_collection:ipmi_get_super_pod_enabled(req, ctx)
    local rsp = ipmi_msg.GetSuperPodLabelInfoRsp.new()
    rsp.ManufactureId = 0x0007db
    rsp.EndFlag = 0
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Get SuperPodEnabled failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    local super_pod_enabled = super_pod_label_object.SuperPodEnabled
                                    and SUPERPOD_LABEL_ENABLED
                                    or SUPERPOD_LABEL_DISABLED
    rsp.Data = bs.new('<<SuperPodEnabled:1/unit:8>>'):pack({
        SuperPodEnabled = super_pod_enabled
    })
    rsp.CompletionCode = cc.Success
    return rsp
end

local ipmi_data = nil
local type_name = ""
local DATA_ENTRIES <const> = {
    { type = 0x00, key = "TopologyType", length = 0x01 },
    { type = 0x04, key = "UBMDeployMode", length = 0x01 },
    { type = 0x05, key = "ChassisId", length = 0x02 },
    { type = 0x06, key = "IsUBFabricMode", length = 0x01 },
    { type = 0x07, key = "IsUBControlNode", length = 0x01 },
    { type = 0x08, key = "LogicalSlotNumber", length = 0x01 }
}
--用于校验ipmi设置属性是否是非法值
local TOPOLOGYTYPE_VALUE <const> = {0, 1, 2}
local NUM_TO_BOOL <const> = {0, 1}

function c_super_pod_label_collection:set_log(ctx, rsp, errorcode)
    ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel failed')
    rsp.CompletionCode = errorcode
    return rsp
end

local function find_type(prop_type)
    for k, v in ipairs(DATA_ENTRIES) do
        if v.type == prop_type then
            return k
        end
    end
    return nil
end

function c_super_pod_label_collection:set_superpod_label_info_expansion_by_obj(req, ctx, rsp, TotalCount, obj)
    local index = 2
    local length = 0
    local table_key = nil
    for i = 1, TotalCount do
        table_key = find_type(req.Data:byte(index))
        length = DATA_ENTRIES[table_key].length
        ipmi_data = 0
        for j = 1, length do
            ipmi_data = req.Data:byte(index + 1 + j) + (ipmi_data << 8)
        end
        index = index + length + 2
        type_name = DATA_ENTRIES[table_key].key
        if type_name == "IsUBFabricMode" or type_name == "IsUBControlNode" then
            obj[type_name] = (ipmi_data == 1)
            ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel %s to %s successfully',type_name, ipmi_data == 1)
        elseif type_name == "TopologyType" then
            obj[type_name] = ipmi_data
            ipmi.ipmi_operation_log(ctx, 'Chassis', 'Set SuperPodLabel %s to %s successfully',type_name, ipmi_data)
        end
    end
    return rsp
end

function c_super_pod_label_collection:check_superpod_label_info_expansion_data(req, ctx, rsp, TotalCount)
    local index = 2
    local length = 0
    local ipmi_length = 0
    local table_key = nil
    for i = 1, TotalCount do
        if index + length + 2 > #req.Data + 1 then
            log:error('input data totallength is error')
            rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.DataNotAvailable)
            return rsp
        end
        table_key = find_type(req.Data:byte(index))
        if not table_key then
            log:error('the type(%s) is not available', table_key)
            rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.ParmOutOfRange)
            return rsp
        end
        length = DATA_ENTRIES[table_key].length
        ipmi_length = req.Data:byte(index + 1)
        if length ~= ipmi_length then
            log:error('The current setting attribute length %s does not match the IPMI interface definition length %s', ipmi_length, length)
            rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.InvalidCommand)
            return rsp
        end
        ipmi_data = 0
        for j = 1, length do
            ipmi_data = req.Data:byte(index + 1 + j) + (ipmi_data << 8)
        end
        index = index + length + 2
        type_name = DATA_ENTRIES[table_key].key
        if type_name == "IsUBFabricMode" or type_name == "IsUBControlNode" then
            if not NUM_TO_BOOL[ipmi_data + 1]  then
                log:error('the value(%s) is not available', ipmi_data)
                rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.InvalidCommand)
                return rsp
            end
        elseif type_name == "TopologyType" then
            if not TOPOLOGYTYPE_VALUE[ipmi_data + 1] then
                log:error('the value(%s) is not available', ipmi_data)
                rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.InvalidCommand)
                return rsp
            end
        else
            log:error('%s is not allowed to set.', type_name)
            rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.InvalidCommand)
            return rsp
        end
    end
    return rsp
end

function c_super_pod_label_collection:ipmi_set_superpod_label_info_expansion(req, ctx)
    local rsp = ipmi_msg.SetSuperPodLabelInfoExpansionRsp.new()
    rsp.CompletionCode = cc.Success
    rsp.ManufactureId = 0x0007db
    if req.Length < #req.Data then
        log:error('data is invalid')
        rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.ParameterOutOfRange)
        return rsp
    end
    local TotalCount = req.Data:byte(1)
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        log:error('Without SuperpodLabel object')
        rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.DataNotAvailable)
        return rsp
    end
    if TotalCount == 0 then
        log:error('The TotalCount is 0')
        rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.InvalidCommand)
        return rsp
    end
    if TotalCount > #DATA_ENTRIES then
        log:error('The currenter number %s of attributes set exceeds the maximum supported number %s', TotalCount, #DATA_ENTRIES)
        rsp = c_super_pod_label_collection:set_log(ctx, rsp, cc.ParameterOutOfRange)
        return rsp
    end
    rsp = c_super_pod_label_collection:check_superpod_label_info_expansion_data(req, ctx, rsp, TotalCount)
    if rsp.CompletionCode ~= cc.Success then
        return rsp
    end
    return c_super_pod_label_collection:set_superpod_label_info_expansion_by_obj(req, ctx, rsp, TotalCount, super_pod_label_object)
end

function c_super_pod_label_collection:get_superpod_label_info_expansion_by_obj(object, rsp)
    local format_parts = {"TotalCount:1/unit:8"}
    local index = 0
    for i = 1, #DATA_ENTRIES do
        index = i - 1
        table.insert(format_parts, string.format("data%dtype:1/unit:8", index)) -- 添加type字段
        table.insert(format_parts, string.format("data%dlength:1/unit:8", index)) -- 添加length字段
        table.insert(format_parts, string.format("data%d:%d/unit:8", index, DATA_ENTRIES[index + 1].length)) -- 添加data字段
    end
    local format_str = "<<" .. table.concat(format_parts, ",") .. ">>"
    local pack_data = {
        TotalCount = #DATA_ENTRIES
    }
    for k, v in ipairs(DATA_ENTRIES) do
        if object[v.key] == nil then
            rsp.CompletionCode = cc.CommandDisabled
            return rsp
        end
        index = k - 1
        pack_data["data" .. index .. "type"] = v.type
        pack_data["data" .. index .. "length"] = v.length
        if v.type == 6 or v.type == 7 then
            pack_data["data" .. index] = object[v.key] and 1 or 0
        else
            pack_data["data" .. index] = object[v.key]
        end
    end
    rsp.Data = bs.new(format_str):pack(pack_data)
    rsp.CompletionCode = cc.Success
    return rsp
end

function c_super_pod_label_collection:ipmi_get_superpod_label_info_expansion(req, ctx)
    local rsp = ipmi_msg.GetSuperPodLabelInfoExpansionRsp.new()
    rsp.ManufactureId = 0x0007db
    rsp.EndFlag = 0
    local super_pod_label_object = c_super_pod_label_object.collection:find({Id = 1})
    if not super_pod_label_object then
        ipmi.ipmi_operation_log(ctx, 'Chassis', 'Get SuperPodLabel failed')
        rsp.CompletionCode = cc.DataNotAvailable
        return rsp
    end
    return c_super_pod_label_collection:get_superpod_label_info_expansion_by_obj(super_pod_label_object, rsp)
end

return singleton(c_super_pod_label_collection)
