-- 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 skynet = require 'skynet'
local class = require 'mc.class'
local log = require 'mc.logging'
local c_thermal_mgmt_service = require 'thermal_mgmt.service'
local fan_service = require 'fan_service'
local pump_service = require 'pump_service'
local valve_service = require 'valve_service'
local object_manage = require 'mc.mdb.object_manage'
local reboot_manage = require 'mc.mdb.micro_component.reboot'
local model = require 'class.model'
local mc_admin = require 'mc.mc_admin'
local utils_core = require 'utils.core'
local metric_collect = require 'metric_collect'
local c_object_manage = require 'mc.orm.object_manage'
local c_tasks = require 'mc.orm.tasks'
local intf_debug = require'mc.mdb.micro_component.debug'
local vos = require 'utils.vos'
local file_sec = require 'utils.file'
local c_fans_object = require 'fans_object'
local c_pumps_object = require 'pumps_object'
local thermal_configuration = require 'thermal_configuration'
local c_valves_object = require 'valves_object'
local ipmi_req = require 'thermal_mgmt.ipmi.ipmi'
local fan_ipmi = require 'ipmi_service.fan_ipmi'
local valve_ipmi = require 'ipmi_service.valve_ipmi'
local client = require 'thermal_mgmt.client'
local mdb = require 'mc.mdb'
require 'fan_info'
local cooling_service = require 'basic_cooling.cooling_service'
local should_use_debug_service, debug_service = pcall(require, 'thermal_mgmt.debug.service')
local thermal_mgmt_ok, thermal_mgmt_mock = pcall(require, 'thermal_mgmt.lualib.thermal_mgmt_mock')

local FANS_INTF <const> = 'bmc.kepler.Systems.Fans'
local PUMPS_INTF <const> = 'bmc.kepler.Systems.Pumps'
local SYSTEM_ID <const> = 1
local VALVES_INTF <const> = 'bmc.kepler.Chassis.Valves'
local BOARD_INTF <const> = 'bmc.kepler.Systems.Board'
local PRIMARY_KEY_CLASS_NAME<const> = {'Fan', 'Pumps', 'Fans', 'FanType', 'PWMChannel'}

local RET_ERR = 1
local RET_OK = 0

local thermal_mgmt_app = class(c_thermal_mgmt_service)

function thermal_mgmt_app:ctor()
    self.fan_service = fan_service.new(self.db)
    self.pump_service = pump_service.new(self.db)
    self.valve_service = valve_service.new(self.db, self.bus)
    self.metric_collect = metric_collect.new(self.bus)
    self.reboot_prepare = false
end

function thermal_mgmt_app:check_dependencies()
    local admin = mc_admin.new()
    admin:parse_dependency(utils_core.getcwd() .. '/mds/service.json')
    admin:check_dependency(self.bus)
end

local function dump_log(path, callback)
    local fp_w, err = file_sec.open_s(path, 'w+')
    if not fp_w then
        log:error('open file failed, err: %s', err)
        return
    end
    local ok, ret = pcall(function()
        return callback(fp_w)
    end)
    if not ok then
        log:error('dump_obj_infos failed, err: %s', ret)
    end
    fp_w:close()
end

function thermal_mgmt_app:register_ipmi()
    self:register_ipmi_cmd(ipmi_req.GetFanSpeed, function (req, ctx, ...)
        return self.fan_ipmi:get_fan_speed(req, ctx, ...)
    end)
    self:register_ipmi_cmd(ipmi_req.GetValveOpeningDegree, function (req, ctx, ...)
        return self.valve_ipmi:get_valve_opening_degree(req, ctx, ...)
    end)
    self:register_ipmi_cmd(ipmi_req.GetValveStandardOpeningDegree, function (req, ctx, ...)
        return self.valve_ipmi:get_valve_standard_opening_degree(req, ctx, ...)
    end)
    self:register_ipmi_cmd(ipmi_req.SetValveOpeningDegree, function (req, ctx, ...)
        return self.valve_ipmi:set_valve_opening_degree(req, ctx, ...)
    end)
    self:register_ipmi_cmd(ipmi_req.GetTotalFanPower, function (req, ctx, ...)
        return self.fan_ipmi:get_total_fan_power(req, ctx, ...)
    end)
end

function thermal_mgmt_app:calculate_fans_power()
    local fan_power_total = 0
    while true do
        if not c_fans_object.collection or not next (c_fans_object.collection) then
            fan_power_total = 0
            log:debug('No fans object found! Get fan power failed!')
        else
            -- 获取的功率单位为0.01w，需要除100
            fan_power_total = math.floor(c_fans_object:get_fans_power() / 100 + 0.5)
        end
        self.fans.EstimatedTotalPowerWatts = fan_power_total
        c_tasks.sleep_ms(60000)
    end
end

function thermal_mgmt_app:init()
    thermal_mgmt_app.super.init(self)
    self:check_dependencies()
    self.cooling_service = cooling_service.new(self, self.db, self.bus, self.persist, function(...)
        self:register_ipmi_cmd(...)
    end)
    self.fan_service:start()
    self.fans = self:CreateSystemsFans(SYSTEM_ID)

    self.object_manage = c_object_manage.new(self.db, self.bus, true)
    self:register_callback()

    self.object_manage.app = thermal_mgmt_app
    self.fan_ipmi = fan_ipmi.new()
    self.valve_ipmi = valve_ipmi.new()
    self:register_ipmi()
    self:init_debug_service(self.bus)
    c_tasks.get_instance():next_tick(self.calculate_fans_power, self)
end

function thermal_mgmt_app:init_debug_service(bus)
    if should_use_debug_service == true and thermal_mgmt_ok == true then
        local thermal_debug_service_class = class(debug_service)
        function thermal_debug_service_class:implement_methods()
            self:ImplThermalMgmtDebugPidConfigurationGetVersion(function(obj, ctx)
                return thermal_mgmt_mock.get_instance():get_pid_version()
            end)
            self:ImplThermalMgmtDebugPidConfigurationSetPidPrintMode(function(obj, ctx, type, enabled)
                return thermal_mgmt_mock.get_instance():set_print_info_enabled(type, enabled)
            end)
            self:ImplThermalMgmtDebugPidConfigurationSetPidConfigItem(function(obj, ctx, support, enabled)
                return thermal_mgmt_mock.get_instance():set_pid_config_enabled(support, enabled)
            end)
        end
        local thermal_mgmt_debug_service = thermal_debug_service_class.new(bus)
        thermal_mgmt_debug_service:CreateThermalMgmtDebug()
        thermal_mgmt_debug_service:implement_methods()
    end
    self:CreateThermalMgmt()
end

local function get_position_id_by_path(path)
    local position = nil
    pcall(function()
        return string.gsub(path, '([^_]+)', function(a)
            -- position为最后一个_后的子串
            position = a
        end)
    end)
    return position
end

function thermal_mgmt_app:register_callback()
    self.object_manage:start()
    object_manage.on_add_object(self.bus, function(class_name, object, position)
        self.cooling_service:add_objects_callback(class_name, object, position)
    end, function(object)
        local object_class_name = object.class_name 
        for _, name in pairs(PRIMARY_KEY_CLASS_NAME) do
            if object_class_name == name then
                local primary_key_name = object_class_name .. 'ObjectName'
                object[primary_key_name] = object.object_name
                break
            end
        end
        return self.cooling_service:on_objects_preprocess_cb(object)
    end)
    object_manage.on_delete_object(self.bus, function(class_name, object, position)
        self.cooling_service:del_objects_callback(class_name, object, position)
    end)
    object_manage.on_add_object_complete(self.bus, function(position)
        c_tasks.get_instance():next_tick(function()
            self.fan_service:clear_persist_fan_info(position)
            thermal_configuration.collection:fold(function (_, obj)
                obj:collect_fan_objects(position)
            end)
        end)
        self.cooling_service:add_objects_complete_callback(position)
    end)
    object_manage.on_delete_object_complete(self.bus, function(position)
        self.cooling_service:delete_objects_complete_callback(position)
    end)
    self.object_manage.mc:add_object('SystemsFans', self.fans)
    self:register_rpc_methods()
    self:register_reboot_methods()
    self:register_log_dump()
    client:OnBoardInterfacesAdded(function (msg, path, board_obj)
        if board_obj.BoardType:value() == 'FanBoard' then
            local ok, ret = pcall(mdb.get_object, self.bus, path, BOARD_INTF)
            if not ok or not ret then
                log:error('get fanboard object failed, error: %s', ret)
                return
            end
            local position = get_position_id_by_path(path)
            thermal_configuration.collection:fold(function (_, obj)
                obj:collect_fan_board_objects(ret, position)
            end)
            self.fan_service:fan_board_listenning(ret, position)
        end
    end)
end

function thermal_mgmt_app:register_log_dump()
    intf_debug.on_dump(function(ctx, path)
        dump_log(path .. "/thermal_info.txt", function (fp_w)
            return fan_service.get_instance():dump_obj_infos(fp_w)
        end)
        dump_log(path .. "/fan_info.txt", function (fp_w)
            return fan_service.get_instance():dump_fan_info(fp_w)
        end)
        log:notice('collect log finished')
    end)
end

function thermal_mgmt_app:register_rpc_methods()
    self:ImplFanMetricGetItems(function(obj, ctx, ...)
        return self.metric_collect:get_fan_data_collection_items(obj, ...)
    end)

    self:ImplFanMetricGetData(function(obj, ctx, ...)
        return self.metric_collect:get_fan_data_collection_data(obj, ...)
    end)
    self:ImplFanFanSetFanPWM(function(obj, ctx, ...)
        return self:SetFanPWM(ctx, obj.SystemId, obj.FanId, ...)
    end)
    self:ImplSystemsFansFansSetPWM(function (obj, ctx, ...)
        return self.fan_service:set_fans_pwm(...)
    end)
    self:ImplPumpsSystemsPumpsSetPWM(function (obj, ctx, ...)
        return self.pump_service:set_pumps_pwm(...)
    end)
    self:ImplValvesValvesStartPatrol(function (obj, ctx, ...)
        local valves_obj = c_valves_object.collection:find(function (valves_obj)
            return valves_obj[VALVES_INTF] == obj
        end)
        return valves_obj:start_patrol(false, ...)
    end)
end

function thermal_mgmt_app:on_reboot_prepare()
    log:notice('[thermal_mgmt_app] reboot prepare')
    self.fan_service:on_reboot_prepare()
end

function thermal_mgmt_app:on_reboot_cancel()
    log:notice('[thermal_mgmt_app] reboot cancel')
end

function thermal_mgmt_app:on_reboot_action()
    log:notice('[thermal_mgmt_app] reboot action')
    self.fan_service:on_reboot_action()
end

function thermal_mgmt_app:register_reboot_methods()
    -- 注册平滑重启回调函数
    -- Prepare准备重启回调
    reboot_manage.on_prepare(function()
        self.reboot_prepare = true
        local ok, err = pcall(function()
            self:on_reboot_prepare()
            self.cooling_service:on_prepare()
        end)
        self.reboot_prepare = false
        if not ok then
            log:error('[thermal_mgmt_app] reboot prepare failed, err: %s', err)
            return -1
        end
        return 0
    end)
    -- Action执行重启回调
    reboot_manage.on_action(function()
        self:on_reboot_action()
        self.cooling_service:on_action()
        return 0
    end)
    -- Cancel取消重启回调
    reboot_manage.on_cancel(function()
        if self.reboot_prepare then
            -- 正在重启准备时，等待60s后仍未准备好，则认为重启失败
            for _ = 1, 60, 1 do
                skynet.sleep(100)
                if not self.reboot_prepare then
                    break
                end
            end
            if self.reboot_prepare then
                log:error("Reboot prepare timeout")
                self.reboot_prepare = false
            end
        end
        self:on_reboot_cancel()
        self.cooling_service:on_cancel()
    end)
end

function thermal_mgmt_app:SetFanPWM(ctx, system_id, fan_id, pwm)
    local result = self.fan_service:set_fan_pwm(system_id, fan_id, pwm)
    return result
end

return thermal_mgmt_app
