// 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.
*
* nymea-plugins 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 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. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "integrationplugintcpcommander.h"
#include "plugininfo.h"
#include "tcpserver.h"

#include <QTimer>

IntegrationPluginTcpCommander::IntegrationPluginTcpCommander()
{
}


void IntegrationPluginTcpCommander::setupThing(ThingSetupInfo *info)
{
    Thing *thing = info->thing();

    if (thing->thingClassId() == tcpClientThingClassId) {

        quint16 port = thing->paramValue(tcpClientThingPortParamTypeId).toUInt();
        QHostAddress address= QHostAddress(thing->paramValue(tcpClientThingIpv4addressParamTypeId).toString());
        QTcpSocket *tcpSocket = m_tcpSockets.value(thing);
        if (!tcpSocket) {
            tcpSocket = new QTcpSocket(this);
            m_tcpSockets.insert(thing, tcpSocket);
        } else {
            // In case of a reconfigure, make sure we reconnect
            tcpSocket->disconnectFromHost();
        }

        connect(tcpSocket, &QTcpSocket::stateChanged, thing, [=](QAbstractSocket::SocketState state){
            thing->setStateValue(tcpClientConnectedStateTypeId, state == QAbstractSocket::ConnectedState);
            if (state == QAbstractSocket::UnconnectedState) {
                QTimer::singleShot(10000, tcpSocket, [tcpSocket, address, port](){
                    qCDebug(dcTCPCommander()) << "Reconnecting to server" << address << port;
                    tcpSocket->connectToHost(address, port);
                });
            }
        });

        connect(tcpSocket, &QTcpSocket::readyRead, thing, [this, thing, tcpSocket](){
            QByteArray data = tcpSocket->readAll();
            ParamList params;
            params << Param(tcpClientTriggeredEventDataParamTypeId, data);
            emit emitEvent(Event(tcpClientTriggeredEventTypeId, thing->id(), params));
        });

        tcpSocket->connectToHost(address, port);
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (thing->thingClassId() == tcpServerThingClassId) {
        int port = thing->paramValue(tcpServerThingPortParamTypeId).toInt();

        TcpServer *tcpServer = m_tcpServers.value(thing);
        if (tcpServer) {
            // In case of reconfigure, make sure to re-setup the server
            delete tcpServer;
        }

        tcpServer = new TcpServer(port, this);
        tcpServer->setConfirmCommands(thing->setting(tcpServerSettingsConfirmCommandParamTypeId).toBool());

        if (tcpServer->isValid()) {
            m_tcpServers.insert(thing, tcpServer);
            connect(thing, &Thing::settingChanged, tcpServer, [tcpServer](const ParamTypeId &paramTypeId, const QVariant &value){
                if (paramTypeId == tcpServerSettingsConfirmCommandParamTypeId) {
                    tcpServer->setConfirmCommands(value.toBool());
                }
            });

            connect(tcpServer, &TcpServer::connectionCountChanged, this, &IntegrationPluginTcpCommander::onTcpServerConnectionCountChanged);
            connect(tcpServer, &TcpServer::commandReceived, this, &IntegrationPluginTcpCommander::onTcpServerCommandReceived);
            info->finish(Thing::ThingErrorNoError);

            // Set the initial connected state since the server is running
            thing->setStateValue("connected", true);
            return;
        } else {
            tcpServer->deleteLater();
            qCDebug(dcTCPCommander()) << "Could not open TCP Server";
            info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("Error opening TCP port."));
            return;
        }
    }
}


void IntegrationPluginTcpCommander::executeAction(ThingActionInfo *info)
{
    Thing *thing = info->thing();
    Action action = info->action();

    if (action.actionTypeId() == tcpClientTriggerActionTypeId) {
        QTcpSocket *tcpSocket = m_tcpSockets.value(thing);
        QByteArray data = action.param(tcpClientTriggerActionDataParamTypeId).value().toByteArray();
        qint64 len = tcpSocket->write(data);
        if (len == data.length()) {
            info->finish(Thing::ThingErrorNoError);
        } else {
            info->finish(Thing::ThingErrorHardwareNotAvailable);
        }
    }

    else if (action.actionTypeId() == tcpServerTriggerActionTypeId){
        TcpServer *server = m_tcpServers.value(thing);
        QByteArray data = action.param(tcpServerTriggerActionDataParamTypeId).value().toByteArray();
        QString clientIp = action.param(tcpServerTriggerActionClientIpParamTypeId).value().toString();
        bool success = server->sendCommand(clientIp, data);
        if (success) {
            info->finish(Thing::ThingErrorNoError);
        } else {
            info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Failed to send the command to the specified client(s)."));
        }
    }
}


void IntegrationPluginTcpCommander::thingRemoved(Thing *thing)
{
    if(thing->thingClassId() == tcpClientThingClassId){
        QTcpSocket *tcpSocket = m_tcpSockets.take(thing);
        tcpSocket->deleteLater();

    } else if(thing->thingClassId() == tcpServerThingClassId){
        TcpServer *tcpServer = m_tcpServers.take(thing);
        tcpServer->deleteLater();
    }
}


void IntegrationPluginTcpCommander::onTcpSocketConnectionChanged(bool connected)
{
    QTcpSocket *tcpSocket = static_cast<QTcpSocket *>(sender());
    Thing *thing = m_tcpSockets.key(tcpSocket);
    if (thing->thingClassId() == tcpClientThingClassId) {
        thing->setStateValue(tcpClientConnectedStateTypeId, connected);
    }
}


void IntegrationPluginTcpCommander::onTcpServerConnectionCountChanged(int connections)
{
    TcpServer *tcpServer = static_cast<TcpServer *>(sender());
    Thing *thing = m_tcpServers.key(tcpServer);
    if (thing && thing->thingClassId() == tcpServerThingClassId) {
        qCDebug(dcTCPCommander()) << thing->name() << "Tcp Server Client connected";
        thing->setStateValue(tcpServerConnectionCountStateTypeId, connections);
    }
}

void IntegrationPluginTcpCommander::onTcpServerCommandReceived(const QString &clientIp, const QByteArray &data)
{
    TcpServer *tcpServer = static_cast<TcpServer *>(sender());
    Thing *thing = m_tcpServers.key(tcpServer);
    qCDebug(dcTCPCommander()) << thing->name() << "Message received" << data;

    ParamList params;
    params.append(Param(tcpServerTriggeredEventDataParamTypeId, data));
    params.append(Param(tcpServerTriggeredEventClientIpParamTypeId, clientIp));
    emit emitEvent(Event(tcpServerTriggeredEventTypeId, thing->id(), params));
}
