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

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

    initDataPoints();
}

SunSpecCommonModel::~SunSpecCommonModel()
{

}

QString SunSpecCommonModel::name() const
{
    return "common";
}

QString SunSpecCommonModel::description() const
{
    return "All SunSpec compliant devices must include this as the first model";
}

QString SunSpecCommonModel::label() const
{
    return "Common";
}

QString SunSpecCommonModel::manufacturer() const
{
    return m_manufacturer;
}
QString SunSpecCommonModel::model() const
{
    return m_model;
}
QString SunSpecCommonModel::options() const
{
    return m_options;
}
QString SunSpecCommonModel::version() const
{
    return m_version;
}
QString SunSpecCommonModel::serialNumber() const
{
    return m_serialNumber;
}
quint16 SunSpecCommonModel::deviceAddress() const
{
    return m_deviceAddress;
}

QModbusReply *SunSpecCommonModel::setDeviceAddress(quint16 deviceAddress)
{
    if (!m_initialized)
        return nullptr;

    SunSpecDataPoint dp = m_dataPoints.value("DA");
    QVector<quint16> registers = SunSpecDataPoint::convertFromUInt16(deviceAddress);

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

    return m_connection->modbusTcpClient()->sendWriteRequest(request, m_connection->slaveId());
}
quint16 SunSpecCommonModel::pad() const
{
    return m_pad;
}
void SunSpecCommonModel::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 manufacturerDataPoint;
    manufacturerDataPoint.setName("Mn");
    manufacturerDataPoint.setLabel("Manufacturer");
    manufacturerDataPoint.setDescription("Well known value registered with SunSpec for compliance");
    manufacturerDataPoint.setMandatory(true);
    manufacturerDataPoint.setSize(16);
    manufacturerDataPoint.setAddressOffset(2);
    manufacturerDataPoint.setBlockOffset(0);
    manufacturerDataPoint.setSunSpecDataType("string");
    manufacturerDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(manufacturerDataPoint.name(), manufacturerDataPoint);

    SunSpecDataPoint modelDataPoint;
    modelDataPoint.setName("Md");
    modelDataPoint.setLabel("Model");
    modelDataPoint.setDescription("Manufacturer specific value (32 chars)");
    modelDataPoint.setMandatory(true);
    modelDataPoint.setSize(16);
    modelDataPoint.setAddressOffset(18);
    modelDataPoint.setBlockOffset(16);
    modelDataPoint.setSunSpecDataType("string");
    modelDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(modelDataPoint.name(), modelDataPoint);

    SunSpecDataPoint optionsDataPoint;
    optionsDataPoint.setName("Opt");
    optionsDataPoint.setLabel("Options");
    optionsDataPoint.setDescription("Manufacturer specific value (16 chars)");
    optionsDataPoint.setSize(8);
    optionsDataPoint.setAddressOffset(34);
    optionsDataPoint.setBlockOffset(32);
    optionsDataPoint.setSunSpecDataType("string");
    optionsDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(optionsDataPoint.name(), optionsDataPoint);

    SunSpecDataPoint versionDataPoint;
    versionDataPoint.setName("Vr");
    versionDataPoint.setLabel("Version");
    versionDataPoint.setDescription("Manufacturer specific value (16 chars)");
    versionDataPoint.setSize(8);
    versionDataPoint.setAddressOffset(42);
    versionDataPoint.setBlockOffset(40);
    versionDataPoint.setSunSpecDataType("string");
    versionDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(versionDataPoint.name(), versionDataPoint);

    SunSpecDataPoint serialNumberDataPoint;
    serialNumberDataPoint.setName("SN");
    serialNumberDataPoint.setLabel("Serial Number");
    serialNumberDataPoint.setDescription("Manufacturer specific value (32 chars)");
    serialNumberDataPoint.setMandatory(true);
    serialNumberDataPoint.setSize(16);
    serialNumberDataPoint.setAddressOffset(50);
    serialNumberDataPoint.setBlockOffset(48);
    serialNumberDataPoint.setSunSpecDataType("string");
    serialNumberDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(serialNumberDataPoint.name(), serialNumberDataPoint);

    SunSpecDataPoint deviceAddressDataPoint;
    deviceAddressDataPoint.setName("DA");
    deviceAddressDataPoint.setLabel("Device Address");
    deviceAddressDataPoint.setDescription("Modbus device address");
    deviceAddressDataPoint.setSize(1);
    deviceAddressDataPoint.setAddressOffset(66);
    deviceAddressDataPoint.setBlockOffset(64);
    deviceAddressDataPoint.setSunSpecDataType("uint16");
    deviceAddressDataPoint.setAccess(SunSpecDataPoint::AccessReadWrite);
    deviceAddressDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(deviceAddressDataPoint.name(), deviceAddressDataPoint);

    SunSpecDataPoint padDataPoint;
    padDataPoint.setName("Pad");
    padDataPoint.setDescription("Force even alignment");
    padDataPoint.setSize(1);
    padDataPoint.setAddressOffset(67);
    padDataPoint.setBlockOffset(65);
    padDataPoint.setSunSpecDataType("pad");
    padDataPoint.setByteOrder(m_byteOrder);
    m_dataPoints.insert(padDataPoint.name(), padDataPoint);

}

void SunSpecCommonModel::processBlockData()
{
    // Update properties according to the data point type
    if (m_dataPoints.value("Mn").isValid())
        m_manufacturer = m_dataPoints.value("Mn").toString();

    if (m_dataPoints.value("Md").isValid())
        m_model = m_dataPoints.value("Md").toString();

    if (m_dataPoints.value("Opt").isValid())
        m_options = m_dataPoints.value("Opt").toString();

    if (m_dataPoints.value("Vr").isValid())
        m_version = m_dataPoints.value("Vr").toString();

    if (m_dataPoints.value("SN").isValid())
        m_serialNumber = m_dataPoints.value("SN").toString();

    if (m_dataPoints.value("DA").isValid())
        m_deviceAddress = m_dataPoints.value("DA").toUInt16();

    if (m_dataPoints.value("Pad").isValid())
        m_pad = m_dataPoints.value("Pad").toUInt16();


    qCDebug(dcSunSpecModelData()) << this;
}

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

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

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

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

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

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

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


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