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

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-plugins-modbus.
*
* nymea-plugins-modbus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-plugins-modbus 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-plugins-modbus. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "speedwireinterface.h"
#include "extern-plugininfo.h"

#include <QNetworkInterface>

SpeedwireInterface::SpeedwireInterface(quint32 sourceSerialNumber, QObject *parent) :
    QObject(parent),
    m_sourceSerialNumber(sourceSerialNumber)
{
    m_unicast = new QUdpSocket(this);
    connect(m_unicast, &QUdpSocket::readyRead, this, [=](){
        QByteArray datagram;
        QHostAddress senderAddress;
        quint16 senderPort;

        while (m_unicast->hasPendingDatagrams()) {
            datagram.resize(m_unicast->pendingDatagramSize());
            m_unicast->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);

            qCDebug(dcSma()).noquote() << "SpeedwireInterface: Unicast socket received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
            qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
            emit dataReceived(senderAddress, senderPort, datagram, false);
        }
    });

    connect(m_unicast, &QUdpSocket::stateChanged, this, [=](QAbstractSocket::SocketState socketState){
        qCDebug(dcSma()) << "SpeedwireInterface: Unicast socket state changed" << socketState;
    });

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    connect(m_unicast, &QUdpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error){
#else
    connect(m_unicast, static_cast<void (QUdpSocket::*)( QAbstractSocket::SocketError )>(&QAbstractSocket::error), this, [=](QAbstractSocket::SocketError error){
#endif
        qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket error occurred" << error << m_unicast->errorString();
    });


    m_multicast = new QUdpSocket(this);
    connect(m_multicast, &QUdpSocket::readyRead, this, [=](){
        QByteArray datagram;
        QHostAddress senderAddress;
        quint16 senderPort;

        while (m_multicast->hasPendingDatagrams()) {
            datagram.resize(m_multicast->pendingDatagramSize());
            m_multicast->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort);

            // Filter our own requests on the multicast
            if (isOwnInterface(senderAddress))
                return;

            qCDebug(dcSma()).noquote() << "SpeedwireInterface: Multicast socket received data from" << QString("%1:%2").arg(senderAddress.toString()).arg(senderPort);
            //qCDebug(dcSma()) << "SpeedwireInterface: " << datagram.toHex();
            emit dataReceived(senderAddress, senderPort, datagram, true);
        }
    });

    connect(m_multicast, &QUdpSocket::stateChanged, this, [=](QAbstractSocket::SocketState socketState){
        qCDebug(dcSma()) << "SpeedwireInterface: Multicast socket state changed" << socketState;
    });
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    connect(m_multicast, &QUdpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError error){
#else
    connect(m_multicast, static_cast<void (QUdpSocket::*)( QAbstractSocket::SocketError )>(&QAbstractSocket::error), this, [=](QAbstractSocket::SocketError error){
#endif
        qCWarning(dcSma()) << "SpeedwireInterface: Multicast socket error occurred" << error << m_multicast->errorString();
    });

    if (initialize()) {
        qCDebug(dcSma()) << "SpeedwireInterface: Initialized sucessfully unicast and multicast interface.";
    } else {
        qCWarning(dcSma()) << "SpeedwireInterface: Failed to initialize.";
    }
}

SpeedwireInterface::~SpeedwireInterface()
{
    if (m_unicast)
        m_unicast->close();

    if (m_multicast) {
        if (!m_multicast->leaveMulticastGroup(Speedwire::multicastAddress())) {
            qCWarning(dcSma()) << "SpeedwireInterface: Failed to leave multicast group" << Speedwire::multicastAddress().toString();
        }

        m_multicast->close();
    }
}

bool SpeedwireInterface::available() const
{
    return m_available;
}

bool SpeedwireInterface::isOwnInterface(const QHostAddress &hostAddress)
{
    foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
        if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack))
            continue;

        if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp))
            continue;

        if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning))
            continue;

        foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {

            // Only IPv4
            if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
                continue;

            if (entry.ip() == hostAddress) {
                return true;
            }
        }
    }

    return false;
}

void SpeedwireInterface::reconfigureMulticastGroup()
{
    qCDebug(dcSma()) << "Reconfigure multicast interfaces";
    if (m_multicast->joinMulticastGroup(Speedwire::multicastAddress())) {
        qCDebug(dcSma()) << "SpeedwireInterface: Joined successfully multicast group" << Speedwire::multicastAddress().toString();
        m_multicastWarningPrintCount = 0;
    } else {
        // FIXME: It would probably be better to monitor the network interfaces and re-join if necessary
        uint mod = m_multicastWarningPrintCount % 120;

        if (m_multicastWarningPrintCount < 12) {
            qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << Speedwire::multicastAddress().toString() << m_multicast->errorString() << "Retrying in 5 seconds...";
        }

        if (m_multicastWarningPrintCount >= 12 && mod == 0) {
            qCWarning(dcSma()) << "SpeedwireInterface: Failed to join multicast group" << Speedwire::multicastAddress().toString() << m_multicast->errorString() << "Retrying in 10 minutes...";
        }

        QTimer::singleShot(5000, this, &SpeedwireInterface::reconfigureMulticastGroup);
        m_multicastWarningPrintCount++;
    }

    //    foreach (const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) {
    //        if(interface.isValid() && !interface.flags().testFlag(QNetworkInterface::IsLoopBack)
    //                && interface.flags().testFlag(QNetworkInterface::CanMulticast)
    //                && interface.flags().testFlag(QNetworkInterface::IsRunning)) {

    //            QList<QNetworkAddressEntry> addressEntries = interface.addressEntries();
    //            for (int i = 0; i < addressEntries.length(); i++) {
    //                if (addressEntries.at(i).ip().protocol() == QAbstractSocket::IPv4Protocol) {

    //                    if (!m_multicast->joinMulticastGroup(Speedwire::multicastAddress(), interface)) {
    //                        qCWarning(dcSma()) << "SpeedwireInterface: Could not join multicast group" << Speedwire::multicastAddress().toString() << "on interface" << interface << m_multicast->errorString();
    //                    } else {
    //                        qCDebug(dcSma()) << "SpeedwireInterface: Joined successfully multicast group" << Speedwire::multicastAddress().toString() << "on interface" << interface ;
    //                    }
    //                }
    //            }
    //        }
    //    }

    //    qCDebug(dcSma()) << "Multicast outgoing interface" << m_multicast->multicastInterface();
}

quint32 SpeedwireInterface::sourceSerialNumber() const
{
    return m_sourceSerialNumber;
}

bool SpeedwireInterface::initialize()
{
    bool success = true;
    if (m_unicast->state() != QUdpSocket::BoundState) {
        m_unicast->close();
        if (!m_unicast->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
            qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket could not be bound to port" << Speedwire::port();
            success = false;
        }
    }

    if (m_multicast->state() != QUdpSocket::BoundState) {
        if (!m_multicast->bind(QHostAddress::AnyIPv4, Speedwire::port(), QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint)) {
            qCWarning(dcSma()) << "SpeedwireInterface: Unicast socket could not be bound to port" << Speedwire::port();
            success = false;
        }
    }


    reconfigureMulticastGroup();
    m_available = success;
    return success;
}

void SpeedwireInterface::sendDataUnicast(const QHostAddress &address, const QByteArray &data)
{
    qCDebug(dcSma()) << "SpeedwireInterface: Unicast -->" << address.toString() << Speedwire::port() << data.toHex();

    if (!m_unicast) {
        qCWarning(dcSma()) << "SpeedwireInterface: Failed to send unicast data, the socket is not available";
        return;
    }

    if (m_unicast->writeDatagram(data, address, Speedwire::port()) < 0) {
        qCWarning(dcSma()) << "SpeedwireInterface: Failed to send unicast data to" << address.toString() << m_unicast->errorString();
    }
}

void SpeedwireInterface::sendDataMulticast(const QByteArray &data)
{
    qCDebug(dcSma()) << "SpeedwireInterface: Multicast -->" << Speedwire::multicastAddress().toString() << Speedwire::port() << data.toHex();
    if (!m_multicast) {
        qCWarning(dcSma()) << "SpeedwireInterface: Failed to send multicast data, the socket is not available";
        return;
    }

    if (m_multicast->writeDatagram(data, Speedwire::multicastAddress(), Speedwire::port()) < 0) {
        qCWarning(dcSma()) << "SpeedwireInterface: Failed to send multicast data to" << Speedwire::multicastAddress().toString() << m_multicast->errorString();
    }
}
