// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "util.hpp"

#include <nlohmann/json.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <stdplus/print.hpp>
#include <xyz/openbmc_project/Common/error.hpp>

#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <regex>
#include <string>
#include <tuple>
#include <vector>

namespace google
{
namespace ipmi
{
namespace fs = std::filesystem;
using namespace phosphor::logging;
using InternalFailure =
    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;

nlohmann::json parseConfig(const std::string& file)
{
    std::ifstream jsonFile(file);
    if (!jsonFile.is_open())
    {
        log<level::ERR>("Entity association JSON file not found");
        elog<InternalFailure>();
    }

    auto data = nlohmann::json::parse(jsonFile, nullptr, false);
    if (data.is_discarded())
    {
        log<level::ERR>("Entity association JSON parser failure");
        elog<InternalFailure>();
    }

    return data;
}

std::string readPropertyFile(const std::string& fileName)
{
    std::ifstream ifs(fileName);
    std::string contents;

    if (!ifs.is_open())
    {
        auto msg = std::string("Unable to open file ") + fileName.c_str();
        log<level::DEBUG>(msg.c_str());
    }
    else
    {
        if (ifs >> contents)
        {
            // If the last character is a null terminator; remove it.
            if (!contents.empty())
            {
                const char& back = contents.back();
                if (back == '\0')
                    contents.pop_back();
            }

            return contents;
        }
        else
        {
            stdplus::print(stderr, "Unable to read file {}.\n", fileName);
        }
    }

    return "";
}

std::vector<std::tuple<std::uint32_t, std::string>> buildPcieMap()
{
    std::vector<std::tuple<std::uint32_t, std::string>> pcie_i2c_map;

    // Build a vector with i2c bus to pcie slot mapping.
    // Iterate through all the devices under "/sys/bus/i2c/devices".
    for (const auto& i2c_dev : fs::directory_iterator("/sys/bus/i2c/devices"))
    {
        std::string i2c_dev_path = i2c_dev.path();
        std::smatch i2c_dev_string_number;
        std::regex e("(i2c-)(\\d+)");

        // Check if the device has "i2c-" in its path.
        if (std::regex_search(i2c_dev_path, i2c_dev_string_number, e))
        {
            // Check if the i2c device has "pcie-slot" file under "of-node" dir.
            std::string pcie_slot_path = i2c_dev_path + "/of_node/pcie-slot";
            std::string pcie_slot;

            // Read the "pcie-slot" name from the "pcie-slot" file.
            pcie_slot = readPropertyFile(pcie_slot_path);
            if (pcie_slot.empty())
            {
                continue;
            }
            std::string pcie_slot_name;
            std::string pcie_slot_full_path;

            // Append the "pcie-slot" name to dts base.
            pcie_slot_full_path.append("/proc/device-tree");
            pcie_slot_full_path.append(pcie_slot);

            // Read the "label" which contains the pcie slot name.
            pcie_slot_full_path.append("/label");
            pcie_slot_name = readPropertyFile(pcie_slot_full_path);

            if (pcie_slot_name.empty())
            {
                continue;
            }

            // Get the i2c bus number from the i2c device path.
            std::uint32_t i2c_bus_number =
                i2c_dev_string_number[2].matched
                    ? std::stoi(i2c_dev_string_number[2])
                    : 0;
            // Store the i2c bus number and the pcie slot name in the vector.
            pcie_i2c_map.push_back(
                std::make_tuple(i2c_bus_number, pcie_slot_name));
        }
    }

    return pcie_i2c_map;
}

} // namespace ipmi
} // namespace google