// SPDX-License-Identifier: LGPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of libnymea-sunspec.
*
* libnymea-sunspec is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* libnymea-sunspec is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libnymea-sunspec. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "sunspecextsettingsmodel.h"
#include "sunspecconnection.h"

SunSpecExtSettingsModel::SunSpecExtSettingsModel(SunSpecConnection *connection, quint16 modbusStartRegister, quint16 modelLength, SunSpecDataPoint::ByteOrder byteOrder, QObject *parent) :
    SunSpecModel(connection, modbusStartRegister, 145, modelLength, byteOrder, parent)
{
    m_modelBlockType = SunSpecModel::ModelBlockTypeFixed;

    initDataPoints();
}

SunSpecExtSettingsModel::~SunSpecExtSettingsModel()
{

}

QString SunSpecExtSettingsModel::name() const
{
    return "ext_settings";
}

QString SunSpecExtSettingsModel::description() const
{
    return "Inverter controls extended settings ";
}

QString SunSpecExtSettingsModel::label() const
{
    return "Extended Settings";
}

float SunSpecExtSettingsModel::rampUpRate() const
{
    return m_rampUpRate;
}

QModbusReply *SunSpecExtSettingsModel::setRampUpRate(float rampUpRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("NomRmpUpRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(rampUpRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::nomRmpDnRte() const
{
    return m_nomRmpDnRte;
}

QModbusReply *SunSpecExtSettingsModel::setNomRmpDnRte(float nomRmpDnRte)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("NomRmpDnRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(nomRmpDnRte, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::emergencyRampUpRate() const
{
    return m_emergencyRampUpRate;
}

QModbusReply *SunSpecExtSettingsModel::setEmergencyRampUpRate(float emergencyRampUpRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("EmgRmpUpRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(emergencyRampUpRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::emergencyRampDownRate() const
{
    return m_emergencyRampDownRate;
}

QModbusReply *SunSpecExtSettingsModel::setEmergencyRampDownRate(float emergencyRampDownRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("EmgRmpDnRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(emergencyRampDownRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::connectRampUpRate() const
{
    return m_connectRampUpRate;
}

QModbusReply *SunSpecExtSettingsModel::setConnectRampUpRate(float connectRampUpRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("ConnRmpUpRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(connectRampUpRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::connectRampDownRate() const
{
    return m_connectRampDownRate;
}

QModbusReply *SunSpecExtSettingsModel::setConnectRampDownRate(float connectRampDownRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("ConnRmpDnRte");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(connectRampDownRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
float SunSpecExtSettingsModel::defaultRampRate() const
{
    return m_defaultRampRate;
}

QModbusReply *SunSpecExtSettingsModel::setDefaultRampRate(float defaultRampRate)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("AGra");
    QVector<quint16> registers = SunSpecDataPoint::convertFromFloatWithSSF(defaultRampRate, m_rampRateScaleFactor, dp.dataType());

    QModbusDataUnit request = QModbusDataUnit(QModbusDataUnit::RegisterType::HoldingRegisters, m_modbusStartRegister + dp.addressOffset(), registers.length());
    request.setValues(registers);

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
qint16 SunSpecExtSettingsModel::rampRateScaleFactor() const
{
    return m_rampRateScaleFactor;
}
void SunSpecExtSettingsModel::initDataPoints()
{
    SunSpecDataPoint modelIdDataPoint;
    modelIdDataPoint.setName("ID");
    modelIdDataPoint.setLabel("Model ID");
    modelIdDataPoint.setDescription("Model identifier");
    modelIdDataPoint.setMandatory(true);
    modelIdDataPoint.setSize(1);
    modelIdDataPoint.setAddressOffset(0);
    modelIdDataPoint.setSunSpecDataType("uint16");
    modelIdDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(modelIdDataPoint.name(), modelIdDataPoint);

    SunSpecDataPoint modelLengthDataPoint;
    modelLengthDataPoint.setName("L");
    modelLengthDataPoint.setLabel("Model Length");
    modelLengthDataPoint.setDescription("Model length");
    modelLengthDataPoint.setMandatory(true);
    modelLengthDataPoint.setSize(1);
    modelLengthDataPoint.setAddressOffset(1);
    modelLengthDataPoint.setSunSpecDataType("uint16");
    modelLengthDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(modelLengthDataPoint.name(), modelLengthDataPoint);

    SunSpecDataPoint rampUpRateDataPoint;
    rampUpRateDataPoint.setName("NomRmpUpRte");
    rampUpRateDataPoint.setLabel("Ramp Up Rate");
    rampUpRateDataPoint.setDescription("Ramp up rate as a percentage of max current.");
    rampUpRateDataPoint.setUnits("Pct");
    rampUpRateDataPoint.setSize(1);
    rampUpRateDataPoint.setAddressOffset(2);
    rampUpRateDataPoint.setBlockOffset(0);
    rampUpRateDataPoint.setScaleFactorName("Rmp_SF");
    rampUpRateDataPoint.setSunSpecDataType("uint16");
    rampUpRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    rampUpRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(rampUpRateDataPoint.name(), rampUpRateDataPoint);

    SunSpecDataPoint nomRmpDnRteDataPoint;
    nomRmpDnRteDataPoint.setName("NomRmpDnRte");
    nomRmpDnRteDataPoint.setLabel("NomRmpDnRte");
    nomRmpDnRteDataPoint.setDescription("Ramp down rate as a percentage of max current.");
    nomRmpDnRteDataPoint.setUnits("Pct");
    nomRmpDnRteDataPoint.setSize(1);
    nomRmpDnRteDataPoint.setAddressOffset(3);
    nomRmpDnRteDataPoint.setBlockOffset(1);
    nomRmpDnRteDataPoint.setScaleFactorName("Rmp_SF");
    nomRmpDnRteDataPoint.setSunSpecDataType("uint16");
    nomRmpDnRteDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    nomRmpDnRteDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(nomRmpDnRteDataPoint.name(), nomRmpDnRteDataPoint);

    SunSpecDataPoint emergencyRampUpRateDataPoint;
    emergencyRampUpRateDataPoint.setName("EmgRmpUpRte");
    emergencyRampUpRateDataPoint.setLabel("Emergency Ramp Up Rate");
    emergencyRampUpRateDataPoint.setDescription("Emergency ramp up rate as a percentage of max current.");
    emergencyRampUpRateDataPoint.setUnits("Pct");
    emergencyRampUpRateDataPoint.setSize(1);
    emergencyRampUpRateDataPoint.setAddressOffset(4);
    emergencyRampUpRateDataPoint.setBlockOffset(2);
    emergencyRampUpRateDataPoint.setScaleFactorName("Rmp_SF");
    emergencyRampUpRateDataPoint.setSunSpecDataType("uint16");
    emergencyRampUpRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    emergencyRampUpRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(emergencyRampUpRateDataPoint.name(), emergencyRampUpRateDataPoint);

    SunSpecDataPoint emergencyRampDownRateDataPoint;
    emergencyRampDownRateDataPoint.setName("EmgRmpDnRte");
    emergencyRampDownRateDataPoint.setLabel("Emergency Ramp Down Rate");
    emergencyRampDownRateDataPoint.setDescription("Emergency ramp down rate as a percentage of max current.");
    emergencyRampDownRateDataPoint.setUnits("Pct");
    emergencyRampDownRateDataPoint.setSize(1);
    emergencyRampDownRateDataPoint.setAddressOffset(5);
    emergencyRampDownRateDataPoint.setBlockOffset(3);
    emergencyRampDownRateDataPoint.setScaleFactorName("Rmp_SF");
    emergencyRampDownRateDataPoint.setSunSpecDataType("uint16");
    emergencyRampDownRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    emergencyRampDownRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(emergencyRampDownRateDataPoint.name(), emergencyRampDownRateDataPoint);

    SunSpecDataPoint connectRampUpRateDataPoint;
    connectRampUpRateDataPoint.setName("ConnRmpUpRte");
    connectRampUpRateDataPoint.setLabel("Connect Ramp Up Rate");
    connectRampUpRateDataPoint.setDescription("Connect ramp up rate as a percentage of max current.");
    connectRampUpRateDataPoint.setUnits("Pct");
    connectRampUpRateDataPoint.setSize(1);
    connectRampUpRateDataPoint.setAddressOffset(6);
    connectRampUpRateDataPoint.setBlockOffset(4);
    connectRampUpRateDataPoint.setScaleFactorName("Rmp_SF");
    connectRampUpRateDataPoint.setSunSpecDataType("uint16");
    connectRampUpRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    connectRampUpRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(connectRampUpRateDataPoint.name(), connectRampUpRateDataPoint);

    SunSpecDataPoint connectRampDownRateDataPoint;
    connectRampDownRateDataPoint.setName("ConnRmpDnRte");
    connectRampDownRateDataPoint.setLabel("Connect Ramp Down Rate");
    connectRampDownRateDataPoint.setDescription("Connect ramp down rate as a percentage of max current.");
    connectRampDownRateDataPoint.setUnits("Pct");
    connectRampDownRateDataPoint.setSize(1);
    connectRampDownRateDataPoint.setAddressOffset(7);
    connectRampDownRateDataPoint.setBlockOffset(5);
    connectRampDownRateDataPoint.setScaleFactorName("Rmp_SF");
    connectRampDownRateDataPoint.setSunSpecDataType("uint16");
    connectRampDownRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    connectRampDownRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(connectRampDownRateDataPoint.name(), connectRampDownRateDataPoint);

    SunSpecDataPoint defaultRampRateDataPoint;
    defaultRampRateDataPoint.setName("AGra");
    defaultRampRateDataPoint.setLabel("Default Ramp Rate");
    defaultRampRateDataPoint.setDescription("Ramp rate specified in percent of max current.");
    defaultRampRateDataPoint.setUnits("Pct");
    defaultRampRateDataPoint.setSize(1);
    defaultRampRateDataPoint.setAddressOffset(8);
    defaultRampRateDataPoint.setBlockOffset(6);
    defaultRampRateDataPoint.setScaleFactorName("Rmp_SF");
    defaultRampRateDataPoint.setSunSpecDataType("uint16");
    defaultRampRateDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    defaultRampRateDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(defaultRampRateDataPoint.name(), defaultRampRateDataPoint);

    SunSpecDataPoint rampRateScaleFactorDataPoint;
    rampRateScaleFactorDataPoint.setName("Rmp_SF");
    rampRateScaleFactorDataPoint.setLabel("Ramp Rate Scale Factor");
    rampRateScaleFactorDataPoint.setDescription("Ramp Rate Scale Factor");
    rampRateScaleFactorDataPoint.setSize(1);
    rampRateScaleFactorDataPoint.setAddressOffset(9);
    rampRateScaleFactorDataPoint.setBlockOffset(7);
    rampRateScaleFactorDataPoint.setSunSpecDataType("sunssf");
    rampRateScaleFactorDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(rampRateScaleFactorDataPoint.name(), rampRateScaleFactorDataPoint);

}

void SunSpecExtSettingsModel::processBlockData()
{
    // Scale factors
    if (m_dataPoints.value("Rmp_SF").isValid())
        m_rampRateScaleFactor = m_dataPoints.value("Rmp_SF").toInt16();


    // Update properties according to the data point type
    if (m_dataPoints.value("NomRmpUpRte").isValid())
        m_rampUpRate = m_dataPoints.value("NomRmpUpRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("NomRmpDnRte").isValid())
        m_nomRmpDnRte = m_dataPoints.value("NomRmpDnRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("EmgRmpUpRte").isValid())
        m_emergencyRampUpRate = m_dataPoints.value("EmgRmpUpRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("EmgRmpDnRte").isValid())
        m_emergencyRampDownRate = m_dataPoints.value("EmgRmpDnRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("ConnRmpUpRte").isValid())
        m_connectRampUpRate = m_dataPoints.value("ConnRmpUpRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("ConnRmpDnRte").isValid())
        m_connectRampDownRate = m_dataPoints.value("ConnRmpDnRte").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("AGra").isValid())
        m_defaultRampRate = m_dataPoints.value("AGra").toFloatWithSSF(m_rampRateScaleFactor);

    if (m_dataPoints.value("Rmp_SF").isValid())
        m_rampRateScaleFactor = m_dataPoints.value("Rmp_SF").toInt16();


    qCDebug(dcSunSpecModelData()) << this;
}

QDebug operator<<(QDebug debug, SunSpecExtSettingsModel *model)
{
    debug.nospace().noquote() << "SunSpecExtSettingsModel(Model: " << model->modelId() << ", Register: " << model->modbusStartRegister() << ", Length: " << model->modelLength() << ")\n";
    debug.nospace().noquote() << "    - " << model->dataPoints().value("NomRmpUpRte") << "-->";
    if (model->dataPoints().value("NomRmpUpRte").isValid()) {
        debug.nospace().noquote() << model->rampUpRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("NomRmpDnRte") << "-->";
    if (model->dataPoints().value("NomRmpDnRte").isValid()) {
        debug.nospace().noquote() << model->nomRmpDnRte() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("EmgRmpUpRte") << "-->";
    if (model->dataPoints().value("EmgRmpUpRte").isValid()) {
        debug.nospace().noquote() << model->emergencyRampUpRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("EmgRmpDnRte") << "-->";
    if (model->dataPoints().value("EmgRmpDnRte").isValid()) {
        debug.nospace().noquote() << model->emergencyRampDownRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("ConnRmpUpRte") << "-->";
    if (model->dataPoints().value("ConnRmpUpRte").isValid()) {
        debug.nospace().noquote() << model->connectRampUpRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("ConnRmpDnRte") << "-->";
    if (model->dataPoints().value("ConnRmpDnRte").isValid()) {
        debug.nospace().noquote() << model->connectRampDownRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }

    debug.nospace().noquote() << "    - " << model->dataPoints().value("AGra") << "-->";
    if (model->dataPoints().value("AGra").isValid()) {
        debug.nospace().noquote() << model->defaultRampRate() << "\n";
    } else {
        debug.nospace().noquote() << "NaN\n";
    }


    return debug.space().quote();
}
