// 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 "sunspecaggregatormodel.h"
#include "sunspecconnection.h"

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

    initDataPoints();
}

SunSpecAggregatorModel::~SunSpecAggregatorModel()
{

}

QString SunSpecAggregatorModel::name() const
{
    return "aggregator";
}

QString SunSpecAggregatorModel::description() const
{
    return "Aggregates a collection of models for a given model id";
}

QString SunSpecAggregatorModel::label() const
{
    return "Basic Aggregator";
}

quint16 SunSpecAggregatorModel::aid() const
{
    return m_aid;
}
quint16 SunSpecAggregatorModel::n() const
{
    return m_n;
}
quint16 SunSpecAggregatorModel::un() const
{
    return m_un;
}
SunSpecAggregatorModel::St SunSpecAggregatorModel::status() const
{
    return m_status;
}
quint16 SunSpecAggregatorModel::vendorStatus() const
{
    return m_vendorStatus;
}
SunSpecAggregatorModel::EvtFlags SunSpecAggregatorModel::eventCode() const
{
    return m_eventCode;
}
quint32 SunSpecAggregatorModel::vendorEventCode() const
{
    return m_vendorEventCode;
}
SunSpecAggregatorModel::Ctl SunSpecAggregatorModel::control() const
{
    return m_control;
}
quint32 SunSpecAggregatorModel::vendorControl() const
{
    return m_vendorControl;
}
quint32 SunSpecAggregatorModel::controlValue() const
{
    return m_controlValue;
}
void SunSpecAggregatorModel::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 aidDataPoint;
    aidDataPoint.setName("AID");
    aidDataPoint.setLabel("AID");
    aidDataPoint.setDescription("Aggregated model id");
    aidDataPoint.setMandatory(true);
    aidDataPoint.setSize(1);
    aidDataPoint.setAddressOffset(2);
    aidDataPoint.setBlockOffset(0);
    aidDataPoint.setSunSpecDataType("uint16");
    aidDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(aidDataPoint.name(), aidDataPoint);

    SunSpecDataPoint nDataPoint;
    nDataPoint.setName("N");
    nDataPoint.setLabel("N");
    nDataPoint.setDescription("Number of aggregated models");
    nDataPoint.setMandatory(true);
    nDataPoint.setSize(1);
    nDataPoint.setAddressOffset(3);
    nDataPoint.setBlockOffset(1);
    nDataPoint.setSunSpecDataType("uint16");
    nDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(nDataPoint.name(), nDataPoint);

    SunSpecDataPoint unDataPoint;
    unDataPoint.setName("UN");
    unDataPoint.setLabel("UN");
    unDataPoint.setDescription("Update Number.  Incrementing number each time the mapping is changed.  If the number is not changed from the last reading the direct access to a specific offset will result in reading the same logical model as before.  Otherwise the entire model must be read to refresh the changes");
    unDataPoint.setMandatory(true);
    unDataPoint.setSize(1);
    unDataPoint.setAddressOffset(4);
    unDataPoint.setBlockOffset(2);
    unDataPoint.setSunSpecDataType("uint16");
    unDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(unDataPoint.name(), unDataPoint);

    SunSpecDataPoint statusDataPoint;
    statusDataPoint.setName("St");
    statusDataPoint.setLabel("Status");
    statusDataPoint.setDescription("Enumerated status code");
    statusDataPoint.setMandatory(true);
    statusDataPoint.setSize(1);
    statusDataPoint.setAddressOffset(5);
    statusDataPoint.setBlockOffset(3);
    statusDataPoint.setSunSpecDataType("enum16");
    statusDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(statusDataPoint.name(), statusDataPoint);

    SunSpecDataPoint vendorStatusDataPoint;
    vendorStatusDataPoint.setName("StVnd");
    vendorStatusDataPoint.setLabel("Vendor Status");
    vendorStatusDataPoint.setDescription("Vendor specific status code");
    vendorStatusDataPoint.setSize(1);
    vendorStatusDataPoint.setAddressOffset(6);
    vendorStatusDataPoint.setBlockOffset(4);
    vendorStatusDataPoint.setSunSpecDataType("enum16");
    vendorStatusDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorStatusDataPoint.name(), vendorStatusDataPoint);

    SunSpecDataPoint eventCodeDataPoint;
    eventCodeDataPoint.setName("Evt");
    eventCodeDataPoint.setLabel("Event Code");
    eventCodeDataPoint.setDescription("Bitmask event code");
    eventCodeDataPoint.setMandatory(true);
    eventCodeDataPoint.setSize(2);
    eventCodeDataPoint.setAddressOffset(7);
    eventCodeDataPoint.setBlockOffset(5);
    eventCodeDataPoint.setSunSpecDataType("bitfield32");
    eventCodeDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(eventCodeDataPoint.name(), eventCodeDataPoint);

    SunSpecDataPoint vendorEventCodeDataPoint;
    vendorEventCodeDataPoint.setName("EvtVnd");
    vendorEventCodeDataPoint.setLabel("Vendor Event Code");
    vendorEventCodeDataPoint.setDescription("Vendor specific event code");
    vendorEventCodeDataPoint.setSize(2);
    vendorEventCodeDataPoint.setAddressOffset(9);
    vendorEventCodeDataPoint.setBlockOffset(7);
    vendorEventCodeDataPoint.setSunSpecDataType("bitfield32");
    vendorEventCodeDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorEventCodeDataPoint.name(), vendorEventCodeDataPoint);

    SunSpecDataPoint controlDataPoint;
    controlDataPoint.setName("Ctl");
    controlDataPoint.setLabel("Control");
    controlDataPoint.setDescription("Control register for all aggregated devices");
    controlDataPoint.setSize(1);
    controlDataPoint.setAddressOffset(11);
    controlDataPoint.setBlockOffset(9);
    controlDataPoint.setSunSpecDataType("enum16");
    controlDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(controlDataPoint.name(), controlDataPoint);

    SunSpecDataPoint vendorControlDataPoint;
    vendorControlDataPoint.setName("CtlVnd");
    vendorControlDataPoint.setLabel("Vendor Control");
    vendorControlDataPoint.setDescription("Vendor control register for all aggregated devices");
    vendorControlDataPoint.setSize(2);
    vendorControlDataPoint.setAddressOffset(12);
    vendorControlDataPoint.setBlockOffset(10);
    vendorControlDataPoint.setSunSpecDataType("enum32");
    vendorControlDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(vendorControlDataPoint.name(), vendorControlDataPoint);

    SunSpecDataPoint controlValueDataPoint;
    controlValueDataPoint.setName("CtlVl");
    controlValueDataPoint.setLabel("Control Value");
    controlValueDataPoint.setDescription("Numerical value used as a parameter to the control");
    controlValueDataPoint.setSize(2);
    controlValueDataPoint.setAddressOffset(14);
    controlValueDataPoint.setBlockOffset(12);
    controlValueDataPoint.setSunSpecDataType("enum32");
    controlValueDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(controlValueDataPoint.name(), controlValueDataPoint);

}

void SunSpecAggregatorModel::processBlockData()
{
    // Update properties according to the data point type
    if (m_dataPoints.value("AID").isValid())
        m_aid = m_dataPoints.value("AID").toUInt16();

    if (m_dataPoints.value("N").isValid())
        m_n = m_dataPoints.value("N").toUInt16();

    if (m_dataPoints.value("UN").isValid())
        m_un = m_dataPoints.value("UN").toUInt16();

    if (m_dataPoints.value("St").isValid())
        m_status = static_cast<St>(m_dataPoints.value("St").toUInt16());

    if (m_dataPoints.value("StVnd").isValid())
        m_vendorStatus = m_dataPoints.value("StVnd").toUInt16();

    if (m_dataPoints.value("Evt").isValid())
        m_eventCode = static_cast<EvtFlags>(m_dataPoints.value("Evt").toUInt32());

    if (m_dataPoints.value("EvtVnd").isValid())
        m_vendorEventCode = m_dataPoints.value("EvtVnd").toUInt32();

    if (m_dataPoints.value("Ctl").isValid())
        m_control = static_cast<Ctl>(m_dataPoints.value("Ctl").toUInt16());

    if (m_dataPoints.value("CtlVnd").isValid())
        m_vendorControl = m_dataPoints.value("CtlVnd").toUInt32();

    if (m_dataPoints.value("CtlVl").isValid())
        m_controlValue = m_dataPoints.value("CtlVl").toUInt32();


    qCDebug(dcSunSpecModelData()) << this;
}

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

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

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

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

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

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

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

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

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

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


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