#include "pdr.hpp"

#include <libpldm/fru.h>
#include <libpldm/platform.h>

#include <phosphor-logging/lg2.hpp>

#include <climits>

PHOSPHOR_LOG2_USING;

using namespace pldm::pdr;

namespace pldm
{
namespace responder
{
namespace pdr_utils
{
// Refer: DSP0257_1.0.0 Table 2
// 7: uint16_t(FRU Record Set Identifier), uint8_t(FRU Record Type),
// uint8_t(Number of FRU fields), uint8_t(Encoding Type for FRU fields),
// uint8_t(FRU Field Type), uint8_t(FRU Field Length)
static constexpr uint8_t fruRecordDataFormatLength = 7;

// // 2: 1byte FRU Field Type, 1byte FRU Field Length
static constexpr uint8_t fruFieldTypeLength = 2;

pldm_pdr* Repo::getPdr() const
{
    return repo;
}

RecordHandle Repo::addRecord(const PdrEntry& pdrEntry)
{
    uint32_t handle = pdrEntry.handle.recordHandle;
    int rc = pldm_pdr_add_check(repo, pdrEntry.data, pdrEntry.size, false,
                                TERMINUS_HANDLE, &handle);
    if (rc)
    {
        // pldm_pdr_add() assert()ed on failure to add PDR
        throw std::runtime_error("Failed to add PDR");
    }
    return handle;
}

const pldm_pdr_record* Repo::getFirstRecord(PdrEntry& pdrEntry)
{
    constexpr uint32_t firstNum = 0;
    uint8_t* pdrData = nullptr;
    auto record = pldm_pdr_find_record(getPdr(), firstNum, &pdrData,
                                       &pdrEntry.size,
                                       &pdrEntry.handle.nextRecordHandle);
    if (record)
    {
        pdrEntry.data = pdrData;
    }

    return record;
}

const pldm_pdr_record* Repo::getNextRecord(const pldm_pdr_record* currRecord,
                                           PdrEntry& pdrEntry)
{
    uint8_t* pdrData = nullptr;
    auto record = pldm_pdr_get_next_record(getPdr(), currRecord, &pdrData,
                                           &pdrEntry.size,
                                           &pdrEntry.handle.nextRecordHandle);
    if (record)
    {
        pdrEntry.data = pdrData;
    }

    return record;
}

uint32_t Repo::getRecordHandle(const pldm_pdr_record* record) const
{
    return pldm_pdr_get_record_handle(getPdr(), record);
}

uint32_t Repo::getRecordCount()
{
    return pldm_pdr_get_record_count(getPdr());
}

bool Repo::empty()
{
    return !getRecordCount();
}

StatestoDbusVal populateMapping(const std::string& type, const Json& dBusValues,
                                const PossibleValues& pv)
{
    size_t pos = 0;
    pldm::utils::PropertyValue value;
    StatestoDbusVal valueMap;
    if (dBusValues.size() != pv.size())
    {
        error(
            "dBusValues size is not equal to pv size, dBusValues Size: {DBUS_VAL_SIZE}, pv Size: {PV_SIZE}",
            "DBUS_VAL_SIZE", dBusValues.size(), "PV_SIZE", pv.size());
        return {};
    }

    for (auto it = dBusValues.begin(); it != dBusValues.end(); ++it, ++pos)
    {
        if (type == "uint8_t")
        {
            value = static_cast<uint8_t>(it.value());
        }
        else if (type == "uint16_t")
        {
            value = static_cast<uint16_t>(it.value());
        }
        else if (type == "uint32_t")
        {
            value = static_cast<uint32_t>(it.value());
        }
        else if (type == "uint64_t")
        {
            value = static_cast<uint64_t>(it.value());
        }
        else if (type == "int16_t")
        {
            value = static_cast<int16_t>(it.value());
        }
        else if (type == "int32_t")
        {
            value = static_cast<int32_t>(it.value());
        }
        else if (type == "int64_t")
        {
            value = static_cast<int64_t>(it.value());
        }
        else if (type == "bool")
        {
            value = static_cast<bool>(it.value());
        }
        else if (type == "double")
        {
            value = static_cast<double>(it.value());
        }
        else if (type == "string")
        {
            value = static_cast<std::string>(it.value());
        }
        else
        {
            error("Unknown D-Bus property type, TYPE={OTHER_TYPE}",
                  "OTHER_TYPE", type.c_str());
            return {};
        }

        valueMap.emplace(pv[pos], value);
    }

    return valueMap;
}

std::tuple<TerminusHandle, SensorID, SensorInfo>
    parseStateSensorPDR(const std::vector<uint8_t>& stateSensorPdr)
{
    auto pdr =
        reinterpret_cast<const pldm_state_sensor_pdr*>(stateSensorPdr.data());
    CompositeSensorStates sensors{};
    auto statesPtr = pdr->possible_states;
    auto compositeSensorCount = pdr->composite_sensor_count;
    std::vector<StateSetId> stateSetIds{};

    while (compositeSensorCount--)
    {
        auto state =
            reinterpret_cast<const state_sensor_possible_states*>(statesPtr);
        PossibleStates possibleStates{};
        uint8_t possibleStatesPos{};
        auto updateStates = [&possibleStates,
                             &possibleStatesPos](const bitfield8_t& val) {
            for (int i = 0; i < CHAR_BIT; i++)
            {
                if (val.byte & (1 << i))
                {
                    possibleStates.insert(possibleStatesPos * CHAR_BIT + i);
                }
            }
            possibleStatesPos++;
        };
        std::for_each(&state->states[0],
                      &state->states[state->possible_states_size],
                      updateStates);

        sensors.emplace_back(std::move(possibleStates));
        stateSetIds.emplace_back(state->state_set_id);

        if (compositeSensorCount)
        {
            statesPtr += sizeof(state_sensor_possible_states) +
                         state->possible_states_size - 1;
        }
    }

    auto entityInfo =
        std::make_tuple(static_cast<ContainerID>(pdr->container_id),
                        static_cast<EntityType>(pdr->entity_type),
                        static_cast<EntityInstance>(pdr->entity_instance));
    auto sensorInfo = std::make_tuple(std::move(entityInfo), std::move(sensors),
                                      std::move(stateSetIds));
    return std::make_tuple(pdr->terminus_handle, pdr->sensor_id,
                           std::move(sensorInfo));
}

std::vector<FruRecordDataFormat> parseFruRecordTable(const uint8_t* fruData,
                                                     size_t fruLen)
{
    // Refer: DSP0257_1.0.0 Table 2
    // 7: uint16_t(FRU Record Set Identifier), uint8_t(FRU Record Type),
    // uint8_t(Number of FRU fields), uint8_t(Encoding Type for FRU fields),
    // uint8_t(FRU Field Type), uint8_t(FRU Field Length)
    if (fruLen < fruRecordDataFormatLength)
    {
        lg2::error("Invalid fru len: {FRULEN}", "FRULEN", fruLen);
        return {};
    }

    std::vector<FruRecordDataFormat> frus;

    size_t index = 0;
    while (index < fruLen)
    {
        FruRecordDataFormat fru;

        auto record = reinterpret_cast<const pldm_fru_record_data_format*>(
            fruData + index);
        fru.fruRSI = (int)le16toh(record->record_set_id);
        fru.fruRecType = record->record_type;
        fru.fruNum = record->num_fru_fields;
        fru.fruEncodeType = record->encoding_type;

        index += 5;

        std::ranges::for_each(std::views::iota(0, (int)record->num_fru_fields),
                              [fruData, &fru, &index](int) {
            auto tlv =
                reinterpret_cast<const pldm_fru_record_tlv*>(fruData + index);
            FruTLV frutlv;
            frutlv.fruFieldType = tlv->type;
            frutlv.fruFieldLen = tlv->length;
            frutlv.fruFieldValue.resize(tlv->length);
            for (const auto& i : std::views::iota(0, (int)tlv->length))
            {
                memcpy(frutlv.fruFieldValue.data() + i, tlv->value + i, 1);
            }
            fru.fruTLV.push_back(frutlv);

            // 2: 1byte FRU Field Type, 1byte FRU Field Length
            index += fruFieldTypeLength + (unsigned)tlv->length;
        });

        frus.push_back(fru);
    }

    return frus;
}

size_t getEffecterDataSize(uint8_t effecterDataSize)
{
    switch (effecterDataSize)
    {
        case PLDM_EFFECTER_DATA_SIZE_UINT8:
            return sizeof(uint8_t);
        case PLDM_EFFECTER_DATA_SIZE_SINT8:
            return sizeof(int8_t);
        case PLDM_EFFECTER_DATA_SIZE_UINT16:
            return sizeof(uint16_t);
        case PLDM_EFFECTER_DATA_SIZE_SINT16:
            return sizeof(int16_t);
        case PLDM_EFFECTER_DATA_SIZE_UINT32:
            return sizeof(uint32_t);
        case PLDM_EFFECTER_DATA_SIZE_SINT32:
            return sizeof(int32_t);
        default:
            return 0;
    }
}

} // namespace pdr_utils
} // namespace responder
} // namespace pldm