-- 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 log = require 'mc.logging'
local prop_def = require 'macros.property_def'
local bs_util = require 'util.base_util'
local file_sec = require 'utils.file'
local utils = require 'mc.utils'
local xml_util = {}

function xml_util.construct_xml_head(bios_version, registry_version)
    local xml_head = string.format('<Component Name="BIOS" SoftwareVersion="%s" ConfigVersion="%s">\n',
        bios_version, registry_version)
    return xml_head
end

function xml_util.construct_xml_data(export_config, traverse_list)
    local len = #traverse_list
    local text = {}
    local index = 1
    for i = 1, len, 1 do
        local key = traverse_list[i]
        local value = export_config[key]
        if value then
            local xml_data = '    <Attribute Name="' .. key .. '">' .. value .. '</Attribute>' .. '\n'
            text[index] = xml_data
            index = index + 1
        end
    end

    return table.concat(text)
end

function xml_util.construct_xml_tail()
    local xml_tail = '</Component>\n'
    return xml_tail
end


-- 对外接口:将json数据写出xml格式
function xml_util.write_xml(config_path, write_data)
    if config_path == nil or write_data == nil then
        return bs_util.err_msg(prop_def.RESPONSE_OPEN_EXPORT_FILE_NULL, prop_def.RESPONSE_INNER_ERROR)
    end

    local bios_version = write_data.bios_version
    local registry_version = write_data.registry_version
    local export_config = write_data.export_config
    local traverse_list = write_data.traverse_list_data
    if not bios_version or not registry_version or not export_config or not traverse_list then
        return bs_util.err_msg(prop_def.RESPONSE_XML_INFO_INVALID, prop_def.RESPONSE_INNER_ERROR)
    end

    local file = file_sec.open_s(config_path, "w+b")
    if not file then
        log:debug("write_xml: open %s file fail!", config_path)
        return bs_util.err_msg(prop_def.RESPONSE_CONFIG_PATH_NULL, prop_def.RESPONSE_INNER_ERROR)
    end

    local xml_head = xml_util.construct_xml_head(bios_version, registry_version)
    local xml_data = xml_util.construct_xml_data(export_config, traverse_list)
    local xml_tail = xml_util.construct_xml_tail()
    local content = xml_head .. xml_data .. xml_tail
    file:write(content)
    file:flush()
    file:close()

    return bs_util.err_msg(prop_def.E_OK, prop_def.E_OK)
end

local function parse_xml_head(xml_data)
    local format = '<Component Name="BIOS" SoftwareVersion="(%d+%.%d+)" ConfigVersion="V(%d+%.%d+)">'
    local bios_version, cfg_version = string.match(xml_data, format)
    if bios_version == nil or cfg_version == nil then
        return nil, nil
    end

    return bios_version, cfg_version
end

local function parse_xml_tail(xml_data)
    if xml_data == '</Component>' then
        return xml_data
    end

    return nil
end

local function parse_xml_content(xml_data, xml_json, registry_ser, traverse_list)
    local format = '<Attribute Name="(.+)">(.+)</Attribute>'
    local name, val = string.match(xml_data, format)
    if name == nil or val == nil or not registry_ser then
        return prop_def.E_FAILURE
    end

    local attribute = registry_ser:get_attribute_by_name(name)
    if attribute == nil then
        log:error('parse_xml_content: %s has not attribute.', name)
        return prop_def.E_OK
    end

    local prop_type = attribute:get_val_by_name(prop_def.REGRIST_PROP_TYPE)
    if prop_type == nil then
        log:error("parse_xml_content: get %s type fail.", name)
        return prop_def.E_FAILURE
    end

    local real_val = val
    if prop_type == prop_def.REGRIST_PROP_TYPE_INTEGER then
        real_val = tonumber(val)
        if real_val == nil then
            log:error('parse_xml_content: prop(%s) value(%s) not integer', name, val)
            return prop_def.E_FAILURE
        end
    end

    local len = #traverse_list
    traverse_list[len + 1] = name
    xml_json[name] = real_val
    return prop_def.E_OK
end

local function parse_xml_data(xml_data, xml_json, registry_ser, traverse_list)
    local bios_version, cfg_version = parse_xml_head(xml_data)
    if bios_version and cfg_version then
        xml_json[prop_def.BIOS_VERSION] = bios_version
        xml_json[prop_def.REGRIST_PROP_REGISTRYVERSIOHN] = cfg_version
        return true
    end

    local err_code = parse_xml_content(xml_data, xml_json, registry_ser, traverse_list)
    if err_code == prop_def.E_OK then
        return true
    end

    local tail = parse_xml_tail(xml_data)
    if tail then
        return true
    end

    return false
end

-- 对外接口:xml转化为json格式
function xml_util.xml_2_json(xml_path, registry_ser)
    if not xml_path or not registry_ser then
        return bs_util.err_msg(prop_def.RESPONSE_XML_CONTENT_NULL, prop_def.RESPONSE_INNER_ERROR)
    end

    local file = file_sec.open_s(xml_path, "rb")
    if not file then
        return bs_util.err_msg(prop_def.RESPONSE_XML_OPEN_XML_FAIL, prop_def.RESPONSE_INNER_ERROR)
    end

    return utils.safe_close_file(file, function()
        local xml_json = {}
        local traverse_list = {}
        local xml_line = file:read()
        while xml_line ~= nil do
            local vaild = parse_xml_data(xml_line, xml_json, registry_ser, traverse_list)
            if not vaild then
                return bs_util.err_msg(prop_def.RESPONSE_XML_FORMAT_INVALID, prop_def.RESPONSE_INNER_ERROR)
            end
            xml_line = file:read()
        end
        local res_data = {}
        res_data.xml_json_data = xml_json
        res_data.traverse_list_data = traverse_list

        return bs_util.err_msg(prop_def.E_OK, res_data)
    end)
end

return xml_util