-- 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 class = require 'mc.class'
local log = require 'mc.logging'
local skynet = require 'skynet'
local singleton = require 'mc.singleton'
local mdb = require 'mc.mdb'
local mod_service = require 'chassis.service'
local bs = require 'mc.bitstring'
local ipmi = require 'mc.ipmi'
local enums = require 'mc.ipmi.enums'
local ipmi_rsp = require 'chassis.ipmi.ipmi'
local cc = ipmi.types.Cc

local enclosure_service = class()

function enclosure_service:ctor(bus)
    self.bus = bus
    self.enclosure = mod_service:CreateEnclosure(1)
end

function enclosure_service:update_data_from_smm()
    skynet.fork(function()
        if not self:check_chassis() then
            return
        end

        log:notice('update_data_from_smm')
        for _ = 1, 10 do
            local ok = self:update_smm_data_to_dbus()
            if ok then
                log:notice('update smm data success')
                return
            end
            log:error('read data from smm failed, sleep 10s.')
            skynet.sleep(10 * 1000)
        end
    end)
end

local read_elabe_req = bs.new([[<<
    0x05:1/unit:8,
    id:1/unit:8,
    area:1/unit:8,
    field:1/unit:8,
    offset:1/unit:8,
    length:1/unit:8
    >>]])

function enclosure_service:update_chassis_sn_from_mm()
    local req_data = read_elabe_req:pack({
        id = 0x01,
        area = 0x02,
        field = 0x03,
        offset = 0x00,
        length = 0x40
    })

    local ok, status, payload = pcall(function()
        return ipmi.request(self.bus, {enums.ChannelType.CT_SMM:value(), enums.ChannelId.CT_IPMB:value()},
        {DestNetFn = 0x30, Cmd = 0x90, Payload = req_data})
        end
    )
    if not ok or status ~= cc.Success then
        log:error('error: %s', payload)
        return false
    end

    local data = string.sub(payload, 2, -1)
    if data ~= nil then
        log:notice('get CabinetSerialNumber = %s', data)
        self.enclosure.CabinetSerialNumber = data
    end
    return true
end

function enclosure_service:update_chassis_ext_sn_from_mm()
    local data = 'N/A'
    for i = 1, 10 do
        local req_data = read_elabe_req:pack({
            id = 0x01,
            area = 0x05,
            field = i,
            offset = 0x00,
            length = 0x40
        })

        local ok, status, payload = pcall(function()
            return ipmi.request(self.bus, {enums.ChannelType.CT_SMM:value(), enums.ChannelId.CT_IPMB:value()},
            {DestNetFn = 0x30, Cmd = 0x90, Payload = req_data})
            end
        )
        if not ok or status ~= cc.Success then
            log:error('error: %s', payload)
            goto continue
        end

        data = string.sub(payload, 2, -1)
        --判断是否是扩展SN
        local match_data = string.match(data, 'ChassisExtSerialNumber=(.*)')
        if match_data then
            log:notice('get EnclosureSerialNumber = %s', match_data)
            self.enclosure.EnclosureSerialNumber = match_data
            return true
        end
        ::continue::
    end
    -- 当扩展域全部读取结束时都没有半框SN则返回false
    return false
end


function enclosure_service:update_smm_data_to_dbus()
    local sn_result = self:update_chassis_sn_from_mm()
    local esn_result = self:update_chassis_ext_sn_from_mm()
    return sn_result and esn_result
end


function enclosure_service:find_shelf_info_by_type(shelf_info_type)
    if not self.enclosure then
        log:error('get Enclosure failed.')
        return nil
    end
    if shelf_info_type == 1 then
        return self.enclosure['CabinetSerialNumber']
    elseif shelf_info_type == 3 then
        return self.enclosure['EnclosureSerialNumber']
    end
end

local function response_error(err_code)
    return err_code, 0x0007DB, 0, 0, ''
end

function enclosure_service:ipmi_get_shelf_info(req, ctx)
    local shelf_info_type = req.ShelfInfo
    local offset = req.ReadOffset
    local len = req.Length
    if len == 0 then
        return response_error(cc.ReqDataLenInvalid)
    end

    local rsp = ipmi_rsp.GetShelfInfo.rsp.new()
    rsp.CompletionCode = cc.Success
    rsp.ManufactureId = 0x0007db
    rsp.EndFlag = 1

    local data = self:find_shelf_info_by_type(shelf_info_type)
    if not data then
        return response_error(cc.InvalidFieldRequest)
    end

    rsp.Data = string.sub(data, offset + 1, offset + len)
    if #data - offset <= len then
        rsp.EndFlag = 0
    end

    return rsp
end

function enclosure_service:check_chassis()
    -- 判断类型只有刀片计算节点才需要从管理板获取信息
    local ok, obj
    for _ = 1, 10 do
        skynet.sleep(1000)
        ok, obj = pcall(mdb.get_object, self.bus, '/bmc/kepler/Chassis/1', 'bmc.kepler.Chassis')
        if ok then
            break
        end
        log:error('get Chassis fail')
    end
    -- ChassisType为2表示为刀片计算节点
    if not obj or obj.ChassisType ~= 2 then
        log:notice('not need update from smm')
        return false
    end
    return true

end

return singleton(enclosure_service)