/*
// Copyright (c) 2018 Intel Corporation
//
// 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 "cpu.hpp"

#include <bitset>
#include <map>

namespace phosphor
{
namespace smbios
{

void Cpu::socket(const uint8_t positionNum, const uint8_t structLen,
                 uint8_t* dataIn)
{
    std::string result = positionToString(positionNum, structLen, dataIn);

    processor::socket(result);

    location::locationCode(result);
}

static constexpr uint8_t processorFamily2Indicator = 0xfe;
void Cpu::family(const uint8_t family, const uint16_t family2)
{
    std::map<uint8_t, const char*>::const_iterator it =
        familyTable.find(family);
    if (it == familyTable.end())
    {
        processor::family("Unknown Processor Family");
    }
    else if (it->first == processorFamily2Indicator)
    {
        std::map<uint16_t, const char*>::const_iterator it2 =
            family2Table.find(family2);
        if (it2 == family2Table.end())
        {
            processor::family("Unknown Processor Family");
        }
        else
        {
            processor::family(it2->second);
            processor::effectiveFamily(family2);
        }
    }
    else
    {
        processor::family(it->second);
        processor::effectiveFamily(family);
    }
}

void Cpu::manufacturer(const uint8_t positionNum, const uint8_t structLen,
                       uint8_t* dataIn)
{
    std::string result = positionToString(positionNum, structLen, dataIn);

    asset::manufacturer(result);
}

void Cpu::partNumber(const uint8_t positionNum, const uint8_t structLen,
                     uint8_t* dataIn)
{
    std::string result = positionToString(positionNum, structLen, dataIn);

    asset::partNumber(result);
}

void Cpu::serialNumber(const uint8_t positionNum, const uint8_t structLen,
                       uint8_t* dataIn)
{
    std::string result = positionToString(positionNum, structLen, dataIn);

    asset::serialNumber(result);
}

void Cpu::version(const uint8_t positionNum, const uint8_t structLen,
                  uint8_t* dataIn)
{
    std::string result;

    result = positionToString(positionNum, structLen, dataIn);

    rev::version(result);
}

void Cpu::characteristics(uint16_t value)
{
    std::vector<processor::Capability> result;
    std::optional<processor::Capability> cap;

    std::bitset<16> charBits = value;
    for (uint8_t index = 0; index < charBits.size(); index++)
    {
        if (charBits.test(index))
        {
            if ((cap = characteristicsTable[index]))
            {
                result.emplace_back(*cap);
            }
        }
    }

    processor::characteristics(result);
}

static constexpr uint8_t maxOldVersionCount = 0xff;
void Cpu::infoUpdate(uint8_t* smbiosTableStorage,
                     const std::string& motherboard)
{
    storage = smbiosTableStorage;
    motherboardPath = motherboard;

    uint8_t* dataIn = storage;

    dataIn = getSMBIOSTypePtr(dataIn, processorsType);
    if (dataIn == nullptr)
    {
        return;
    }

    for (uint8_t index = 0; index < cpuNum; index++)
    {
        dataIn = smbiosNextPtr(dataIn);
        if (dataIn == nullptr)
        {
            return;
        }
        dataIn = getSMBIOSTypePtr(dataIn, processorsType);
        if (dataIn == nullptr)
        {
            return;
        }
    }

    auto cpuInfo = reinterpret_cast<struct ProcessorInfo*>(dataIn);

    socket(cpuInfo->socketDesignation, cpuInfo->length, dataIn); // offset 4h

    constexpr uint32_t socketPopulatedMask = 1 << 6;
    constexpr uint32_t statusMask = 0x07;
    if ((cpuInfo->status & socketPopulatedMask) == 0)
    {
        // Don't attempt to fill in any other details if the CPU is not present.
        present(false);
        functional(false);
        return;
    }
    present(true);
    if ((cpuInfo->status & statusMask) == 1)
    {
        functional(true);
    }
    else
    {
        functional(false);
    }

    // this class is for type CPU  //offset 5h
    family(cpuInfo->family, cpuInfo->family2); // offset 6h and 28h
    manufacturer(cpuInfo->manufacturer, cpuInfo->length,
                 dataIn);                      // offset 7h
    id(cpuInfo->id);                           // offset 8h

    // Step, EffectiveFamily, EffectiveModel computation for Intel processors.
    std::map<uint8_t, const char*>::const_iterator it =
        familyTable.find(cpuInfo->family);
    if (it != familyTable.end())
    {
        std::string familyStr = it->second;
        if ((familyStr.find(" Xeon ") != std::string::npos) ||
            (familyStr.find(" Intel ") != std::string::npos) ||
            (familyStr.find(" Zen ") != std::string::npos))
        {
            // Processor ID field
            // SteppinID:   4;
            // Model:       4;
            // Family:      4;
            // Type:        2;
            // Reserved1:   2;
            // XModel:      4;
            // XFamily:     8;
            // Reserved2:   4;
            uint16_t cpuStep = cpuInfo->id & 0xf;
            uint16_t cpuModel = (cpuInfo->id & 0xf0) >> 4;
            uint16_t cpuFamily = (cpuInfo->id & 0xf00) >> 8;
            uint16_t cpuXModel = (cpuInfo->id & 0xf0000) >> 16;
            uint16_t cpuXFamily = (cpuInfo->id & 0xff00000) >> 20;
            step(cpuStep);
            if (cpuFamily == 0xf)
            {
                effectiveFamily(cpuXFamily + cpuFamily);
            }
            else
            {
                effectiveFamily(cpuFamily);
            }
            if (cpuFamily == 0x6 || cpuFamily == 0xf)
            {
                effectiveModel((cpuXModel << 4) | cpuModel);
            }
            else
            {
                effectiveModel(cpuModel);
            }
        }
    }

    version(cpuInfo->version, cpuInfo->length, dataIn); // offset 10h
    maxSpeedInMhz(cpuInfo->maxSpeed);                   // offset 14h
    serialNumber(cpuInfo->serialNum, cpuInfo->length,
                 dataIn);                               // offset 20h
    partNumber(cpuInfo->partNum, cpuInfo->length,
               dataIn);                                 // offset 22h
    if (cpuInfo->coreCount < maxOldVersionCount)        // offset 23h or 2Ah
    {
        coreCount(cpuInfo->coreCount);
    }
    else
    {
        coreCount(cpuInfo->coreCount2);
    }

    if (cpuInfo->threadCount < maxOldVersionCount) // offset 25h or 2Eh)
    {
        threadCount(cpuInfo->threadCount);
    }
    else
    {
        threadCount(cpuInfo->threadCount2);
    }

    characteristics(cpuInfo->characteristics); // offset 26h

    if (!motherboardPath.empty())
    {
        std::vector<std::tuple<std::string, std::string, std::string>> assocs;
        assocs.emplace_back("chassis", "processors", motherboardPath);
        association::associations(assocs);
    }
}

} // namespace smbios
} // namespace phosphor