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

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* nymea-zigbee
* Zigbee integration module for nymea
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-zigbee.
*
* nymea-zigbee 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.
*
* nymea-zigbee 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 nymea-zigbee. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#ifndef ZIGBEENETWORK_H
#define ZIGBEENETWORK_H

#include <QDir>
#include <QUuid>
#include <QObject>
#include <QSettings>

#include "zigbeenode.h"
#include "zigbeechannelmask.h"
#include "zigbeesecurityconfiguration.h"

class ZigbeeNetworkDatabase;
class ZigbeeBridgeController;

class ZigbeeNetwork : public QObject
{
    Q_OBJECT

public:
    enum State {
        StateUninitialized,
        StateOffline,
        StateStarting,
        StateUpdating,
        StateRunning,
        StateStopping
    };
    Q_ENUM(State)

    enum Error {
        ErrorNoError,
        ErrorHardwareUnavailable,
        ErrorHardwareModuleChanged,
        ErrorZigbeeError
    };
    Q_ENUM(Error)

    explicit ZigbeeNetwork(const QUuid &networkUuid, QObject *parent = nullptr);

    QUuid networkUuid() const;

    State state() const;

    Error error() const;

    QDir settingsDirectory() const;
    void setSettingsDirectory(const QDir &settingsDirectory);

    virtual ZigbeeBridgeController *bridgeController() const = 0;
    virtual Zigbee::ZigbeeBackendType backendType() const = 0;

    // Serial port configuration
    QString serialPortName() const;
    void setSerialPortName(const QString &serialPortName);

    qint32 serialBaudrate() const;
    void setSerialBaudrate(qint32 baudrate);

    QString serialNumber() const;
    void setSerialNumber(const QString &serialNumber);

    ZigbeeAddress macAddress() const;
    void setMacAddress(const ZigbeeAddress &zigbeeAddress);

    // Network configurations
    QString firmwareVersion() const;

    quint16 panId();
    void setPanId(quint16 panId);

    quint64 extendedPanId() const;
    void setExtendedPanId(quint64 extendedPanId);

    quint32 channel() const;
    void setChannel(quint32 channel);

    ZigbeeChannelMask channelMask() const;
    void setChannelMask(const ZigbeeChannelMask &channelMask);

    ZigbeeSecurityConfiguration securityConfiguration() const;
    void setSecurityConfiguration(const ZigbeeSecurityConfiguration &securityConfiguration);

    bool permitJoiningEnabled() const;
    quint8 permitJoiningDuration() const;
    quint8 permitJoiningRemaining() const;
    virtual void setPermitJoining(quint8 duration, quint16 address = Zigbee::BroadcastAddressAllRouters) = 0;

    quint8 generateSequenceNumber();

    // Network nodes
    QList<ZigbeeNode *> nodes() const;

    ZigbeeNode *coordinatorNode() const;

    ZigbeeNode *getZigbeeNode(quint16 shortAddress) const;
    ZigbeeNode *getZigbeeNode(const ZigbeeAddress &address) const;

    bool hasNode(quint16 shortAddress) const;
    bool hasNode(const ZigbeeAddress &address) const;

    virtual ZigbeeNetworkReply *sendRequest(const ZigbeeNetworkRequest &request) = 0;

    void loadNetwork();

    void removeZigbeeNode(const ZigbeeAddress &address);

    void refreshNeighborTables();

private:
    QUuid m_networkUuid;
    State m_state = StateUninitialized;

    // Serial port configuration
    QString m_serialPortName = "/dev/ttyUSB0";
    QString m_serialNumber;
    qint32 m_serialBaudrate = 115200;
    ZigbeeAddress m_macAddress;

    ZigbeeNetworkDatabase *m_database = nullptr;
    bool m_networkLoaded = false;

    // Continuous ASP sequence number for network requests
    quint8 m_sequenceNumber = 0;

    // Network configurations
    quint16 m_panId = 0;
    quint64 m_extendedPanId = 0;
    quint32 m_channel = 0;
    ZigbeeChannelMask m_channelMask = ZigbeeChannelMask(ZigbeeChannelMask::ChannelConfigurationAllChannels);
    ZigbeeDeviceProfile::NodeType m_nodeType = ZigbeeDeviceProfile::NodeTypeCoordinator;

    // Network storage
    QDir m_settingsDirectory = QDir("/var/lib/nymea/");
    QList<ZigbeeNode *> m_nodes;
    QList<ZigbeeNode *> m_uninitializedNodes;
    QList<ZigbeeNode *> m_temporaryNodes;

    void printNetwork();

    // Permit join
    QTimer *m_permitJoinTimer = nullptr;
    bool m_permitJoiningEnabled = false;
    quint8 m_permitJoiningDuration = 120;
    quint8 m_permitJoiningRemaining = 0;

private:
    void addNodeInternally(ZigbeeNode *node);
    void removeNodeInternally(ZigbeeNode *node);

protected:
    Error m_error = ErrorNoError;
    ZigbeeNode *m_coordinatorNode = nullptr;
    ZigbeeSecurityConfiguration m_securityConfiguration;

    void initializeDatabase();

    ZigbeeNode *createNode(quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent);
    ZigbeeNode *createNode(quint16 shortAddress, const ZigbeeAddress &extendedAddress, quint8 macCapabilities, QObject *parent);

    QTimer *m_reachableRefreshTimer = nullptr;
    QList<ZigbeeAddress> m_reachableRefreshAddresses;

    void fetchNextNodeLqiAndRtgTables();
    QList<ZigbeeAddress> m_refreshLqiAndRtgTablesAddresses;

    void setPermitJoiningState(bool permitJoiningEnabled, quint8 duration = 0);

    void clearSettings();

    bool hasUninitializedNode(const ZigbeeAddress &address) const;

    void addNode(ZigbeeNode *node);
    void addUnitializedNode(ZigbeeNode *node);
    void removeNode(ZigbeeNode *node);
    void removeUninitializedNode(ZigbeeNode *node);

    void setNodeReachable(ZigbeeNode *node, bool reachable);
    void updateReplyRequest(ZigbeeNetworkReply *reply, const ZigbeeNetworkRequest &request);

    // Set the coordinator infromation since they cannot be fetched
    void setNodeInformation(ZigbeeNode *node, const QString &manufacturerName, const QString &modelName, const QString &version);

    void setState(State state);
    void setError(Error error);

    bool networkConfigurationAvailable() const;

    void handleNodeIndication(ZigbeeNode *node, const Zigbee::ApsdeDataIndication indication);

    // ZDO
    void handleZigbeeDeviceProfileIndication(const Zigbee::ApsdeDataIndication &indication);

    // ZCL
    void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication);

    void onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress ieeeAddress, quint8 macCapabilities);

    void verifyUnrecognizedNode(quint16 shortAddress);
    void updateNodeNetworkAddress(ZigbeeNode *node, quint16 shortAddress);

    // Network reply methods
    ZigbeeNetworkReply *createNetworkReply(const ZigbeeNetworkRequest &request = ZigbeeNetworkRequest());
    void setReplyResponseError(ZigbeeNetworkReply *reply, quint8 zigbeeStatus = Zigbee::ZigbeeApsStatusSuccess);
    void finishNetworkReply(ZigbeeNetworkReply *reply, ZigbeeNetworkReply::Error error = ZigbeeNetworkReply::ErrorNoError);
    void startWaitingReply(ZigbeeNetworkReply *reply);

signals:
    void settingsDirectoryChanged(const QDir &settingsDirectory);
    void serialPortNameChanged(const QString &serialPortName);
    void serialBaudrateChanged(qint32 serialBaudrate);
    void macAddressChanged(const ZigbeeAddress &macAddress);
    void firmwareVersionChanged(const QString &firmwareVersion);

    void panIdChanged(quint16 panId);
    void extendedPanIdChanged(quint64 extendedPanId);
    void channelChanged(uint channel);
    void channelMaskChanged(const ZigbeeChannelMask &channelMask);
    void securityConfigurationChanged(const ZigbeeSecurityConfiguration &securityConfiguration);

    // Will be emitted if node has joined and the initialization has been finished
    void nodeAdded(ZigbeeNode *node);
    void nodeRemoved(ZigbeeNode *node);

    // Will be emitted when a node joined and starts initializing
    void nodeJoined(ZigbeeNode *node);

    void permitJoiningEnabledChanged(bool permitJoiningEnabled);
    void permitJoinDurationChanged(quint8 duration);
    // Will be emitted when the remaining duration changes in an unexpected
    // manner. That means, it will not be emitted repeatedly every second,
    // despite calling permitJoinRemainining() returning updated values,
    // but it will be emitted for instance when it restarts or is aborted.
    void permitJoinRemainingChanged(quint8 remaining);

    void errorOccured(Error error);
    void stateChanged(State state);

private slots:
    void onNodeStateChanged(ZigbeeNode::State state);
    void onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);
    void evaluateNodeReachableStates();

public slots:
    virtual void startNetwork() = 0;
    virtual void stopNetwork() = 0;
    virtual void reset() = 0;
    virtual void factoryResetNetwork() = 0;
    virtual void destroyNetwork() = 0;

};

QDebug operator<<(QDebug debug, ZigbeeNetwork *network);


#endif // ZIGBEENETWORK_H
