-- 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 context = require 'mc.context'
local utils = require 'mc.utils'
local class_mgnt = require 'mc.class_mgnt'
local skynet = require 'skynet'
local common = require 'common'
require 'frudata.json_types.Accessor'
require 'frudata.json_types.BlockIO'

local manufacture_app = class()

local RESULT_SUCCEED <const> = 'Succeed'
local RESULT_FAILED <const> = 'Failed'
local RESULT_NON <const> = 'Non'
local STATUS_COMPLETE <const> = "Complete"
local STATUS_TESTING <const> = 'Testing'
local STATUS_UNSTART <const> = 'Unstart'
local EEPROM_SELFTEST_OFFSET <const> = 0x4000 - 8

function manufacture_app.new(app)
    return setmetatable({ app = app }, manufacture_app)
end

function manufacture_app:init()
    self:register_rpc_methods()
end

local function get_frudata(dft_frudata, obj)
    if not dft_frudata or #dft_frudata == 0 then
        error('dft_frudata is nil or length of dft_frudata equal zero')
    end
    local frudata_name

    for _, frudata_obj in pairs(dft_frudata) do
        if frudata_obj.Id == obj.Id and frudata_obj.Type == obj.Type and frudata_obj.Slot == obj.Slot and
            frudata_obj.DeviceNum == obj.DeviceNum then
            frudata_name = frudata_obj.FruData
        end
    end

    if not frudata_name then
        log:error('get frudata failed')
        error('get frudata_name failed')
    end

    -- 通过对象名获取对象
    local frudata = class_mgnt('FruData')[frudata_name]
    return frudata
end

local test_data = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }
local test_hex_data = '55 55 55 55 55 55 55 55 '

local function set_eepromwp_with_retry(frudata, eepromwp)
    local retry_times = 0
    while frudata.EepromWp ~= eepromwp and retry_times < 5 do
        frudata.EepromWp = eepromwp
        retry_times = retry_times + 1
        skynet.sleep(10)
    end
    if frudata.EepromWp ~= eepromwp then
        log:error("set fru(%d) eepromwp(%d) failed", frudata.FruId, eepromwp)
    end
end

local function test_write_eeprom(frudata, origin_eepromwp)
    local raw_data = frudata.FruDev:Read(context.get_context_or_default(), EEPROM_SELFTEST_OFFSET, #test_data)
    local ok, ret = pcall(frudata.FruDev.Write, frudata.FruDev, context.get_context_or_default(),
        EEPROM_SELFTEST_OFFSET, test_data)
    if not ok then
        log:notice('write fruid(%d) eeprom failed, ret(%s)', frudata.FruId, ret)
        error('Failed to write Eeprom')
    end
    local buffer = frudata.FruDev:Read(context.get_context_or_default(), EEPROM_SELFTEST_OFFSET, #test_data)
    frudata.FruDev:Write(context.get_context_or_default(), EEPROM_SELFTEST_OFFSET, raw_data) -- 恢复数据
    set_eepromwp_with_retry(frudata, origin_eepromwp) -- 恢复测试之前的写保护策略
    return buffer
end

local eeprom_result = RESULT_NON
local function DFTSelfEepromStart(dft_frudata, obj)
    if obj.Status == STATUS_TESTING then
        log:info("DFTSelfEeprom is Testing")
        return
    end
    obj.Status = STATUS_TESTING
    local ok, frudata = pcall(get_frudata, dft_frudata, obj)
    if not ok or not frudata then
        obj.Status = STATUS_COMPLETE
        eeprom_result = RESULT_FAILED
        log:error_easy("DFTSelfEeprom test failed, frudata is nil")
        return
    end

    -- 保留初始测试数据
    local origin_eepromwp = frudata.EepromWp

    -- 测试写成功
    log:info('DFTSelfEepromGetResult EepromWp_state = %d', frudata.EepromWp)
    set_eepromwp_with_retry(frudata, common.EEPROM_WRITE_PROTECT.CLOSE) -- 关闭写保护
    local _, ret = pcall(test_write_eeprom, frudata, origin_eepromwp)
    obj.Status = STATUS_COMPLETE
    -- 由于写保护关闭，期望写数据与读数据一致
    if utils.to_hex(ret) ~= test_hex_data then
        eeprom_result = RESULT_FAILED
        log:error_easy("DFTSelfEeprom test failed, ret(%s) is not equal to test hex data", utils.to_hex(ret))
        return
    end
    eeprom_result = RESULT_SUCCEED
end

local function DFTSelfEepromStop(obj)
    obj.Status = STATUS_UNSTART
    eeprom_result = RESULT_NON
end

local function DFTSelfEepromGetResult()
    return eeprom_result, ""
end

local eepromwp_result = RESULT_NON
local function DFTSelfEepromWpStart(dft_frudata, obj)
    if obj.Status == STATUS_TESTING then
        log:info("DFTSelfEepromWpStart is Testing")
        return
    end
    obj.Status = STATUS_TESTING
    local ok, frudata = pcall(get_frudata, dft_frudata, obj)
    if not ok or not frudata then
        obj.Status = STATUS_COMPLETE
        eepromwp_result = RESULT_FAILED
        return
    end

    -- 保留初始测试数据
    local origin_eepromwp = frudata.EepromWp

    -- 测试写成功
    log:info('DFTSelfEepromGetResult EepromWp_state = %d', frudata.EepromWp)
    set_eepromwp_with_retry(frudata, common.EEPROM_WRITE_PROTECT.OPEN) -- 打开写保护
    local _, ret = pcall(test_write_eeprom, frudata, origin_eepromwp)
    obj.Status = STATUS_COMPLETE
    -- 由于写保护打开，期望写数据与读数据不一致
    if utils.to_hex(ret) == test_hex_data then
        eepromwp_result = RESULT_FAILED
        return
    end
    eepromwp_result = RESULT_SUCCEED
end

local function DFTSelfEepromWpGetResult()
    return eepromwp_result, ""
end

function manufacture_app:register_rpc_methods()
    self.app:ImplDftEepromManufactureStart(function(obj, ...)
        return DFTSelfEepromStart(self.app.dft_frudata, obj)
    end)
    self.app:ImplDftEepromManufactureStop(function(obj, ...)
        return DFTSelfEepromStop(obj)
    end)
    self.app:ImplDftEepromManufactureGetResult(function(obj, ...)
        return DFTSelfEepromGetResult()
    end)

    self.app:ImplDftEepromWpManufactureStart(function(obj, ...)
        return DFTSelfEepromWpStart(self.app.dft_frudata, obj)
    end)
    self.app:ImplDftEepromWpManufactureStop(function(obj, ...)
        return DFTSelfEepromStop(obj)
    end)
    self.app:ImplDftEepromWpManufactureGetResult(function(obj, ...)
        return DFTSelfEepromWpGetResult()
    end)
end

return manufacture_app
