/*
    SPDX-FileCopyrightText: 2018 Caio Jordão Carvalho <caiojcarvalho@gmail.com>
    SPDX-FileCopyrightText: 2018-2020 Andrius Štikonas <andrius@stikonas.eu>

    SPDX-License-Identifier: GPL-3.0-or-later
*/

#include "core/smartdiskinformation.h"
#include "core/smartattributeparseddata.h"

#include <memory>
#include <utility>

static quint64 u64log2(quint64 n);

/** Creates a new SmartDiskInformationObject */
SmartDiskInformation::SmartDiskInformation() :
    m_ModelName(QString()),
    m_FirmwareVersion(QString()),
    m_SerialNumber(QString()),
    m_Sectors(0),
    m_Temperature(0),
    m_BadSectors(0),
    m_PoweredOn(0),
    m_PowerCycles(0),
    m_SmartStatus(false),
    m_BadAttributeNow(false),
    m_BadAttributeInThePast(false),
    m_SelfTestExecutionStatus(SmartStatus::SelfTestStatus::Success),
    m_Overall(SmartStatus::Overall::Bad)
{
}

/** Update the number of bad sectors based on reallocated sector count and current pending sector attributes data */
void SmartDiskInformation::updateBadSectors()
{
    std::unique_ptr<SmartAttributeParsedData> reallocatedSectorCt(findAttribute(5));
    std::unique_ptr<SmartAttributeParsedData> currentPendingSector(findAttribute(197));

    if (!reallocatedSectorCt && !currentPendingSector)
        m_BadSectors = 0;
    else if (reallocatedSectorCt && currentPendingSector)
        m_BadSectors = reallocatedSectorCt->prettyValue() + currentPendingSector->prettyValue();
    else if (reallocatedSectorCt)
        m_BadSectors = reallocatedSectorCt->prettyValue();
    else
        m_BadSectors = currentPendingSector->prettyValue();
}

/** Update SMART overall data based on the quantity of bad sectors and the status of SMART attributes */
void SmartDiskInformation::updateOverall()
{
    if (!smartStatus()) {
        m_Overall = SmartStatus::Overall::Bad;
        return;
    }

    quint64 sector_threshold = u64log2(sectors()) * 1024;

    if (badSectors() >= sector_threshold) {
        m_Overall = SmartStatus::Overall::BadSectorsMany;
        return;
    }

    validateBadAttributes();

    if (m_BadAttributeNow) {
        m_Overall = SmartStatus::Overall::BadNow;
        return;
    }

    if (badSectors() > 0) {
        m_Overall = SmartStatus::Overall::BadSectors;
        return;
    }

    if (m_BadAttributeInThePast) {
        m_Overall = SmartStatus::Overall::BadPast;
        return;
    }

    m_Overall = SmartStatus::Overall::Good;
}

/** Update the temperature value based on SMART attributes
    @return a boolean representing the status of the operation
*/
bool SmartDiskInformation::updateTemperature()
{
    std::unique_ptr<SmartAttributeParsedData> temperatureCelsius(findAttribute(231));
    std::unique_ptr<SmartAttributeParsedData> temperatureCelsius2(findAttribute(194));
    std::unique_ptr<SmartAttributeParsedData> airflowTemperatureCelsius(findAttribute(190));

    if (temperatureCelsius != nullptr
            && temperatureCelsius->prettyUnit() == SmartAttributeUnit::Milikelvin) {
        m_Temperature = temperatureCelsius->prettyValue();
        return true;
    } else if (temperatureCelsius2 != nullptr
               && temperatureCelsius2->prettyUnit() == SmartAttributeUnit::Milikelvin) {
        m_Temperature = temperatureCelsius2->prettyValue();
        return true;
    } else if (airflowTemperatureCelsius != nullptr
               && airflowTemperatureCelsius->prettyUnit() ==
               SmartAttributeUnit::Milikelvin) {
        m_Temperature = airflowTemperatureCelsius->prettyValue();
        return true;
    }
    return false;
}

/** Update the powered on value based on SMART attributes
    @return a boolean representing the status of the operation
*/
bool SmartDiskInformation::updatePowerOn()
{
    std::unique_ptr<SmartAttributeParsedData> powerOnHours(findAttribute(9));
    std::unique_ptr<SmartAttributeParsedData> powerOnSeconds(findAttribute(233));

    if (powerOnHours != nullptr
            && powerOnHours->prettyUnit() == SmartAttributeUnit::Miliseconds) {
        m_PoweredOn = powerOnHours->prettyValue();
        return true;
    } else if (powerOnSeconds != nullptr
               && powerOnSeconds->prettyUnit() == SmartAttributeUnit::Miliseconds) {
        m_PoweredOn = powerOnSeconds->prettyValue();
        return true;
    }
    return false;
}

/** Update the power cycles value based on SMART attributes
    @return a boolean representing the status of the operation
*/
bool SmartDiskInformation::updatePowerCycle()
{
    std::unique_ptr<SmartAttributeParsedData> powerCycleCount(findAttribute(12));

    if (powerCycleCount != nullptr
            && powerCycleCount->prettyUnit() == SmartAttributeUnit::None) {
        m_PowerCycles = powerCycleCount->prettyValue();
        return true;
    }
    return false;
}

/** Validate disk attributes status */
void SmartDiskInformation::validateBadAttributes()
{
    for (const SmartAttributeParsedData &attribute : std::as_const(m_Attributes)) {
        if (attribute.prefailure()) {
            if (attribute.goodNowValid() && !attribute.goodNow())
                m_BadAttributeNow = true;
            if (attribute.goodInThePastValid() && !attribute.goodInThePast())
                m_BadAttributeInThePast = true;
        }
    }
}

/** Search for a attribute based on its id number
    @return a reference to the attribute
*/
SmartAttributeParsedData *SmartDiskInformation::findAttribute(quint32 id)
{
    SmartAttributeParsedData *attr = nullptr;
    for (const SmartAttributeParsedData &attribute : std::as_const(m_Attributes)) {
        if (id == attribute.id()) {
            attr = new SmartAttributeParsedData(attribute);
            break;
        }
    }
    return attr;
}

static quint64 u64log2(quint64 n)
{
    quint64 r;

    if (n <= 1)
        return 0;

    r = 0;
    for (;;) {
        n = n >> 1;
        if (!n)
            return r;
        r++;
    }
    return 0;
}
