-- 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 fructl_handler = require 'infrastructure.fructl'
local file_util = require 'infrastructure.file_util'
local spi_flash = require 'libmgmt_protocol.bios.infrastructure.spi_flash'
local log = require 'mc.logging'
local file_sec = require 'utils.file'
local utils = require 'mc.utils'
local power_lock = require 'infrastructure.power_lock'
local bs_util = require 'util.base_util'
local bios_enum = require 'domain.bios_firmware.defs'
local package_validator = require 'domain.bios_firmware.package.package_validator'
local bin_builder = require 'libmgmt_protocol.bios.bin_parser.bin_parser_builder'
local channel_selector = require 'domain.bios_firmware.package.channel.channel_selector'
local imu_communicate = require 'domain.transport.imu_communicate'
local client = require 'bios.client'
local context = require 'mc.context'
local bios_service = require 'service.bios_service'
local spi_def = require 'macros.spi_def'
local UpgradeExecutor = {}

local ARM_ENABLE<const> = 1
local OS_ENABLE<const> = 0
local FORCE_OFF_TIMES<const> = 30
local WAIT_OFF_TIMES<const> = 60
local POWER_ON_TIMES<const> = 20

local DEVNAME_BIOS_V5<const> = '/dev/mtd6'
local DEVNAME_BIOS_V6<const> = '/dev/mtd0'

local BASE_DIR<const> = '/dev/shm/upgrade'
local UPGRADE_PATH<const> = '/data/upgrade'
local CACHED_BIOS_PKG_PATH<const> = UPGRADE_PATH .. '/bios.tar.gz'
local SPIKODRV<const> = '/lib/modules/ko/sfc0_drv.ko'

local BIOS_FILE_LEN<const> = 16 * 1024 * 1024
local BIOS_FILE_LEN_KUNPENGB<const> = 32 * 1024 * 1024

local function _throws_error(err)
    if err then
        error(err)
    end
end

local BaseExecutor = class()

function BaseExecutor:execute()
end

local BackExecutor = class(BaseExecutor)

function BackExecutor:ctor()
    -- 缓存包的路径:用于判断是否为缓存包 
    self.cache_dir = UPGRADE_PATH
    self.cache_path = CACHED_BIOS_PKG_PATH
    self.upgrade_dir = BASE_DIR
end

function BackExecutor:_save_db(ctx)
    local db_info = ctx.Global.UpgradeDBTable
    db_info.PackageType = ctx.Global.PackageType
    db_info.Period = ctx.Global.Period
    db_info:save()
end

function BackExecutor:remove()
    pcall(function()
        log:notice('[bios] remove upgrade file start')
        os.remove(self.cache_path)
        log:notice('[bios] remove upgrade file finish')
    end)
end

-- ctx[UpgradePath:升级包路径]
function BackExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: back (executor) start')
    self.cache_path = bs_util.get_bios_cached_path(ctx.Global.SystemId)
    self.cache_dir = bs_util.get_bios_cached_dir(ctx.Global.SystemId)
    if ctx.Interchain.UpgradePath == self.cache_path then
        local err = chain:execute(ctx)
        if err then
            self:remove()
        end
        _throws_error(err)
        return
    end

    -- 检查upgrade升级目录是否存在
    local file = file_sec.open_s(self.upgrade_dir, 'r')
    if not file then
        log:error('[bios]upgrade package: open dir fail')
        error('open dir fail')
    end
    file:close()

    utils.mkdir_with_parents(self.cache_dir, utils.S_IRWXU | utils.S_IRGRP | utils.S_IXGRP)
    local res = file_util.copy_file(ctx.Interchain.UpgradePath, self.cache_path)
    if not res then
        log:error('[bios]upgrade package: cache bios upgrade package failed')
        error('cache bios upgrade package failed')
    end
    -- /data/upgrade/bios.tar.gz
    ctx.Interchain.UpgradePath = self.cache_path
    local err = chain:execute(ctx)
    if err then
        self:remove()
    end
    _throws_error(err)
end

UpgradeExecutor[bios_enum.UpgradeSteps.Backup] = BackExecutor

local CacheExecutor = class(BaseExecutor)

function CacheExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: cache (executor) start')
    -- os上电状态直接返回升级成功,缓存升级包
    local power_status = fructl_handler.get_power_status(ctx.Global.SystemId)
    if power_status ~= 'OFF' then
        log:notice('[bios]upgrade package: the power status is not off')
        return
    end
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.Cache] = CacheExecutor

local PackageCheckExecutor = class(BaseExecutor)

function PackageCheckExecutor:ctor()
    self.validator = package_validator.new()
end

-- ctx.Global.upgrade_mode
function PackageCheckExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: package_check (executor) start')
    self.validator:validate(ctx.Global)
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.PackageCheck] = PackageCheckExecutor

local PowerLockExecutor = class(BaseExecutor)

function PowerLockExecutor:ctor()
    self.lock_time = SEC_OF_PER_HOUR
end

function PowerLockExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: power_lock (executor) lock')
    local lock = power_lock.new(ctx.Global.SystemId)
    local res = lock:lock(self.lock_time)
    if not res then
        log:error('[bios]upgrade package: set power on lock failed')
        error('set power on lock failed')
    end
    local err = chain:execute(ctx)
    lock:unlock()
    log:notice('[bios]upgrade chain: power_lock (executor) unlock')
    _throws_error(err)
end

UpgradeExecutor[bios_enum.UpgradeSteps.LockPower] = PowerLockExecutor

local SpiDriverkExecutor = class(BaseExecutor)

function SpiDriverkExecutor:ctor(cfg)
    if spi_def.SPIKODRV == SPIKODRV then
        log:notice('load chipv1 spikodrv')
    else
        log:notice('load chipv2 spikodrv')
    end
    self.driver_name = spi_def.SPIKODRV
    self.device_name = DEVNAME_BIOS_V6
    if cfg.mhz then
        self.mhz = string.format('g_clk_mhz=%s', cfg.mhz)
    end
end

function SpiDriverkExecutor:_recover()
    local driver_ok, driver_err = pcall(function()
        spi_flash.rmmod_driver(self.driver_name, self.device_name, client, client.PProxyDriverRmmod)
    end)
    local spi_ok, spi_err = pcall(function()
        spi_flash.set_spi_owner(OS_ENABLE)
    end)
    if not driver_ok then
        return driver_err
    end
    if not spi_ok then
        return spi_err
    end
end

function SpiDriverkExecutor:_before()
    local ok, err = pcall(function()
        spi_flash.set_spi_owner(ARM_ENABLE) -- 切换总线到BMC
        spi_flash.insmod_driver(self.driver_name, self.mhz, self.device_name, client, client.PProxyDriverInsmod)
        spi_flash.check_device_ready(self.device_name)
        client.PFileFileChown(client, context.new(), nil, self.device_name, 104, 104)
    end)
    if not ok then
        log:error('[bios]spi_driver (executor): before fail')
        self:_recover()
        error(err)
    end
end

-- 切换spi:./bmcdfx coreMsg secfw set biosMuxStatus 1
-- 加载驱动:insmod /lib/modules/ko/sfc0_drv.ko
-- 检测设备:/dev/mtd0
function SpiDriverkExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: spi_driver (executor) start')
    self:_before()
    ctx.Interchain.DeviceName = self.device_name
    local err = chain:execute(ctx)
    local spi_err = self:_recover()
    if spi_err then
        log:error('[bios]upgrade chain: spi_driver (executor), %s', spi_err)
    end
    err = err or spi_err
    log:notice('[bios]upgrade chain: spi_driver (executor) end')
    _throws_error(err)
end

UpgradeExecutor[bios_enum.UpgradeSteps.SpiDriver] = SpiDriverkExecutor

local DecompressFileExecutor = class(BaseExecutor)

function DecompressFileExecutor:ctor()
    self.decompress_dir = UPGRADE_PATH
    self.decompress_file = CACHED_BIOS_PKG_PATH
end

local skynet = require 'skynet'
-- /data/upgrade/bios.tar.gz -> /data/upgrade/bios.bin
function DecompressFileExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: decompress_file (executor) start')
    self.decompress_file = bs_util.get_bios_cached_path(ctx.Global.SystemId)
    file_util.decompress_file(ctx.Interchain.UpgradePath, self.decompress_dir)
    skynet.sleep(1000)
    ctx.Interchain.BinPath = self.decompress_dir .. '/' .. ctx.Interchain.BinName
    local err = chain:execute(ctx)
    os.remove(ctx.Interchain.UpgradePath)
    os.remove(ctx.Interchain.BinPath)
    log:notice('[bios]upgrade chain: decompress_file (executor) end')
    _throws_error(err)
end

UpgradeExecutor[bios_enum.UpgradeSteps.DecompressFile] = DecompressFileExecutor

local ParseBinExecutor = class(BaseExecutor)

function ParseBinExecutor:ctor(cfg)
    self.bin_parser = bin_builder.build(cfg.period)
end

function ParseBinExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: parse_bin (executor) start')
    local components = self.bin_parser:parse(ctx)
    ctx.Interchain.Components = components
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.ParserBin] = ParseBinExecutor

local SelectorRegionExecutor = class(BaseExecutor)

function SelectorRegionExecutor:execute(ctx, chain)
    _throws_error(chain:execute(ctx))
end

local SelectorChannelExecutor = class(BaseExecutor)

function SelectorChannelExecutor:ctor()
    self.selector = channel_selector.new()
end

function SelectorChannelExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: select_channel (executor) start')
    local channels = self.selector:select(ctx.Global)
    local components = ctx.Interchain.Components
    components:set_channels(channels)
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.SelectChannel]= SelectorChannelExecutor

local UpgradeComponentExecutor = class(BaseExecutor)

function UpgradeComponentExecutor:ctor(cfg)
    self.package_size = cfg.PackageSize or BIOS_FILE_LEN
end

local START<const> = 0x01
local END<const> = 0x02

function UpgradeComponentExecutor:send_confirm(ctx, state)
    if ctx.Global.Period == bios_enum.PackagePeriod.Period3 and
        ctx.Global.UpgradeMode == bios_enum.UpgradeMode.Hot then
        local imu_cmd = imu_communicate.get_instance()
        imu_cmd:confirm_send_state(state, ctx.Global.SystemId)
    end
end

function UpgradeComponentExecutor:execute(ctx, chain)
    if not ctx.Interchain.UpgradeFlag then
        log:notice('[bios]upgrade chain: upgrade_component (executor) no need upgrade')
        _throws_error(chain:execute(ctx))
        return
    end
    log:notice('[bios]upgrade chain: upgrade_component (executor) start')
    self:send_confirm(ctx, START)
    local snapshot = ctx.Global.Snapshot
    snapshot:upgrade_start()
    local _, err = pcall(function()
        local components = ctx.Interchain.Components
        components:upgrade(ctx.Interchain, self.package_size)
    end)
    snapshot:upgrade_finish()
    skynet.fork_once(function ()
        local bios_ser = bios_service.get_instance()
        bios_ser:fetch_info(ctx.Global.SystemId)
    end)
    _throws_error(err)
    self:send_confirm(ctx, END)
    log:notice('[bios]upgrade chain: upgrade_component (executor) finish')
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.UpgradeComponent] = UpgradeComponentExecutor

local ForcePowerExecutor = class(BaseExecutor)

function ForcePowerExecutor:ctor()
    self.force_off_times = FORCE_OFF_TIMES
    self.wait_off_times = WAIT_OFF_TIMES
    self.power_on_times = POWER_ON_TIMES
end

function ForcePowerExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: force_power (executor) start')
    local system_id = ctx.Global.SystemId
    fructl_handler.try_set_power_state(self.force_off_times, 'ForceOff', 'ChassisControlCommand', system_id)
    fructl_handler.wait_until_power_off(self.wait_off_times, system_id)
    local err = chain:execute(ctx)
    fructl_handler.try_set_power_state(self.power_on_times, 'On', 'ChassisControlCommand', system_id)
    log:notice('[bios]upgrade chain: force_power (executor) end')
    _throws_error(err)
end

UpgradeExecutor[bios_enum.UpgradeSteps.ForcePower] = ForcePowerExecutor

local ActivateFirmwareExecutor = class(BaseExecutor)

function ActivateFirmwareExecutor:ctor(cfg)
    self.ActivateBitmap = cfg.ActivateBitmap
end

function ActivateFirmwareExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: activate_firmware (executor) start')
    if not self.ActivateBitmap or not ctx.Interchain.UpgradeFlag then
        log:notice('[bios]activate_firmware (executor): upgrade success, no need activate')
        _throws_error(chain:execute(ctx))
        return
    end
    log:notice('[bios]activate_firmware (executor): start activate bitmap(%s)', self.ActivateBitmap)
    local ok, err = pcall(function()
        local snapshot = ctx.Global.Snapshot
        snapshot:activate(self.ActivateBitmap)
    end)
    if not ok then
        error(string.format(
            '[bios]activate_firmware (executor): activate component fail, err_code(%s)', err.Code))
    end
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.ActivateFirmware] = ActivateFirmwareExecutor

local ComponentFilterExecutor = class(BaseExecutor)

function ComponentFilterExecutor:ctor(cfg)
    self.UpgradeComponents = cfg.UpgradeComponents
end

function ComponentFilterExecutor:get_firmware_ids(ctx)
    local package_cfg = ctx.Global.PackageCfg:get_cfg()
    local sub_components = package_cfg.subcomponent_list
    local firmware_id_map = {}
    for _, component in pairs(self.UpgradeComponents) do
        local firmware_id = 1 << (component.BitMap)
        firmware_id_map[firmware_id] = 1
        local sub_component = sub_components[firmware_id]
        if sub_component then
            log:notice('[bios]component_filter (executor): component(%s) current verison(%s) package version(%s) ' ..
                'current image version(%s)', sub_component.Name, component.Version, sub_component.Version,
                component.ImageVersion)
                local reliable_version = component.ImageVersion or component.Version
            if reliable_version == sub_component.Version then
                log:notice('[bios]component_filter (executor): filter component(%s)', sub_component.Name)
                firmware_id_map[firmware_id] = nil
            end
        end
    end
    return firmware_id_map
end

-- 1、过滤无法热升级的组件
-- 2、过滤版本一致的组件
local HEADER_FIRMWARE_ID<const> = 24
function ComponentFilterExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: component_filter (executor) start')
    if not self.UpgradeComponents then
        log:notice('[bios]component_filter (executor): no need activate filter')
        _throws_error(chain:execute(ctx))
        return
    end
    local components = ctx.Interchain.Components
    local firmware_ids = self:get_firmware_ids(ctx)
    if not firmware_ids or not next(firmware_ids) then
        log:notice('[bios]component_filter (executor): filter all components, no need upgrade')
        ctx.Interchain.UpgradeFlag = false
    else
        firmware_ids[1 << HEADER_FIRMWARE_ID] = 1
    end
    components:filter(firmware_ids)
    log:notice('[bios]component_filter (executor): filter success')
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.ComponentFilter] = ComponentFilterExecutor

local WaitComponentFinishExecutor = class(BaseExecutor)

function WaitComponentFinishExecutor:ctor(cfg)
    self.defs = {
        Success = 0x00,
        InPorcess = 0x01,
        Fail = 0x02
    }
end

local function get_err_name(code)
    local name_map = {
        [1] = 'cause verify the bios visa fail',
        [2] = 'cause upgrade flash fail',
        [3] = 'cause version is same',
        [4] = 'cause component is incomplete'
    }
    return name_map[code] or 'unknown reason'
end

function WaitComponentFinishExecutor:execute(ctx, chain)
    log:notice('[bios]upgrade chain: wait_component_finish (executor) start')
    local imu_cmd = imu_communicate.get_instance()
    local status = self.defs.InPorcess
    while status ~= self.defs.Success do
        skynet.sleep(100)
        local res = imu_cmd:query_firmware_process_status(ctx.Global.SystemId)
        status = res.Status
        if status == self.defs.Fail then
            log:maintenance(log.MLOG_ERROR, log.FC__PUBLIC_OK,
                'wait patch package upgrade finish fail, %s', get_err_name(res.ErrorCode))
            error(string.format('[bios]wait_component_finish (executor): wait finish fail, err code(%s)',
                res.ErrorCode))
        end
    end
    log:notice('[bios]wait_component_finish (executor): upgrade finish')
    _throws_error(chain:execute(ctx))
end

UpgradeExecutor[bios_enum.UpgradeSteps.WaitUpgradeFinish] = WaitComponentFinishExecutor

return UpgradeExecutor