/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2020, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea.
* This project including source code and documentation is protected by copyright law, and
* remains the property of nymea GmbH. All rights, including reproduction, publication,
* editing and translation, are reserved. The use of this project is subject to the terms of a
* license agreement to be concluded with nymea GmbH in accordance with the terms
* of use of nymea GmbH, available under https://nymea.io/license
*
* GNU Lesser General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation; version 3.
* this project 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 this project.
* If not, see <https://www.gnu.org/licenses/>.
*
* For any further details and any questions please contact us under contact@nymea.io
* or see our FAQ/Licensing Information on https://nymea.io/license/faq
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "zigbeenode.h"
#include "zigbeeutils.h"
#include "loggingcategory.h"

#include <QDataStream>

/* Node initialisation steps (sequentially)
 * - Authenticate
 * - Node descriptor
 * - Power descriptor
 * - Active endpoints
 * - for each endpoint do:
 *    * Simple descriptor request
 */


ZigbeeNode::ZigbeeNode(QObject *parent) :
    QObject(parent)
{

}

ZigbeeNode::State ZigbeeNode::state() const
{
    return m_state;
}

bool ZigbeeNode::connected() const
{
    return m_connected;
}

quint16 ZigbeeNode::shortAddress() const
{
    return m_shortAddress;
}

ZigbeeAddress ZigbeeNode::extendedAddress() const
{
    return m_extendedAddress;
}

QList<quint8> ZigbeeNode::endPoints() const
{
    return m_endPoints;
}

ZigbeeNode::NodeType ZigbeeNode::nodeType() const
{
    return m_nodeType;
}

ZigbeeNode::FrequencyBand ZigbeeNode::frequencyBand() const
{
    return m_frequencyBand;
}

ZigbeeNode::Relationship ZigbeeNode::relationship() const
{
    return m_relationship;
}

quint16 ZigbeeNode::manufacturerCode() const
{
    return m_manufacturerCode;
}

bool ZigbeeNode::complexDescriptorAvailable() const
{
    return m_complexDescriptorAvailable;
}

bool ZigbeeNode::userDescriptorAvailable() const
{
    return m_userDescriptorAvailable;
}

quint16 ZigbeeNode::maximumRxSize() const
{
    return m_maximumRxSize;
}

quint16 ZigbeeNode::maximumTxSize() const
{
    return m_maximumTxSize;
}

quint8 ZigbeeNode::maximumBufferSize() const
{
    return m_maximumBufferSize;
}


bool ZigbeeNode::isPrimaryTrustCenter() const
{
    return m_isPrimaryTrustCenter;
}

bool ZigbeeNode::isBackupTrustCenter() const
{
    return m_isBackupTrustCenter;
}

bool ZigbeeNode::isPrimaryBindingCache() const
{
    return m_isPrimaryBindingCache;
}

bool ZigbeeNode::isBackupBindingCache() const
{
    return m_isBackupBindingCache;
}

bool ZigbeeNode::isPrimaryDiscoveryCache() const
{
    return m_isPrimaryDiscoveryCache;
}

bool ZigbeeNode::isBackupDiscoveryCache() const
{
    return m_isBackupDiscoveryCache;
}

bool ZigbeeNode::isNetworkManager() const
{
    return m_isNetworkManager;
}

bool ZigbeeNode::extendedActiveEndpointListAvailable() const
{
    return m_extendedActiveEndpointListAvailable;
}

bool ZigbeeNode::extendedSimpleDescriptorListAvailable() const
{
    return m_extendedSimpleDescriptorListAvailable;
}

bool ZigbeeNode::alternatePanCoordinator() const
{
    return m_alternatePanCoordinator;
}

ZigbeeNode::DeviceType ZigbeeNode::deviceType() const
{
    return m_deviceType;
}

bool ZigbeeNode::powerSourceFlagMainPower() const
{
    return m_powerSourceFlagMainPower;
}

bool ZigbeeNode::receiverOnWhenIdle() const
{
    return m_receiverOnWhenIdle;
}

bool ZigbeeNode::securityCapability() const
{
    return m_securityCapability;
}

bool ZigbeeNode::allocateAddress() const
{
    return m_allocateAddress;
}

ZigbeeNode::PowerMode ZigbeeNode::powerMode() const
{
    return m_powerMode;
}

ZigbeeNode::PowerSource ZigbeeNode::powerSource() const
{
    return m_powerSource;
}

QList<ZigbeeNode::PowerSource> ZigbeeNode::availablePowerSources() const
{
    return m_availablePowerSources;
}

ZigbeeNode::PowerLevel ZigbeeNode::powerLevel() const
{
    return m_powerLevel;
}

void ZigbeeNode::setState(ZigbeeNode::State state)
{
    if (m_state == state)
        return;

    qCDebug(dcZigbeeNode()) << "State changed" << state;
    m_state = state;
    emit stateChanged(m_state);
}

void ZigbeeNode::setConnected(bool connected)
{
    if (m_connected == connected)
        return;

    qCDebug(dcZigbeeNode()) << "Connected changed" << connected;
    m_connected = connected;
    emit connectedChanged(m_connected);
}


//void ZigbeeNode::identify()
//{
//    QByteArray data;
//    QDataStream stream(&data, QIODevice::WriteOnly);
//    stream << m_shortAddress;
//    stream << static_cast<quint8>(0);

//    ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeManagementLqiRequest, data));
//    request.setExpectedAdditionalMessageType(Zigbee::MessageTypeManagementLqiResponse);
//    request.setDescription("Node link quality request for " + ZigbeeUtils::convertUint16ToHexString(m_shortAddress));
//    request.setTimoutIntervall(10000);

//    ZigbeeInterfaceReply *reply = controller()->sendRequest(request);
    //    connect(reply, &ZigbeeInterfaceReply::finished, this, &ZigbeeNode::onRequestLinkQuality);
//}

//void ZigbeeNode::toggle(int addressMode)
//{
//    QByteArray data;
//    QDataStream stream(&data, QIODevice::WriteOnly);
//    stream << static_cast<quint8>(addressMode); // adress mode
//    stream << m_shortAddress;
//    stream << static_cast<quint8>(1); // source endpoint
//    stream << static_cast<quint8>(1); // destination endpoint
//    stream << static_cast<quint8>(2); // command toggle

//    ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeCluserOnOff, data));
//    request.setDescription("Toggle request for " + ZigbeeUtils::convertUint16ToHexString(m_shortAddress));

//    ZigbeeInterfaceReply *reply = controller()->sendRequest(request);
//    connect(reply, &ZigbeeInterfaceReply::finished, this, &ZigbeeNode::onToggleFinished);
//}



void ZigbeeNode::setShortAddress(const quint16 &shortAddress)
{
    m_shortAddress = shortAddress;
}

void ZigbeeNode::setExtendedAddress(const ZigbeeAddress &extendedAddress)
{
    m_extendedAddress = extendedAddress;
}

void ZigbeeNode::setEndPoints(QList<quint8> endPoints)
{
    m_endPoints = endPoints;
}

QByteArray ZigbeeNode::nodeDescriptorRawData() const
{
    return m_nodeDescriptorRawData;
}

void ZigbeeNode::setNodeDescriptorRawData(const QByteArray nodeDescriptorRawData)
{
    m_nodeDescriptorRawData = nodeDescriptorRawData;

    // Parse the raw data
    quint8 sequenceNumber = 0;
    quint8 status = 0;
    quint16 shortAddress = 0;
    quint16 manufacturerCode = 0;
    quint16 maximalRxSize = 0;
    quint16 maximalTxSize = 0;
    quint16 serverMask = 0;
    quint8 descriptorFlag = 0;
    quint8 macFlags = 0;
    quint8 maxBufferSize = 0;
    quint16 bitField = 0;

    QDataStream stream(&m_nodeDescriptorRawData, QIODevice::ReadOnly);
    stream >> sequenceNumber;
    stream >> status;
    stream >> shortAddress;
    stream >> manufacturerCode;
    stream >> maximalRxSize;
    stream >> maximalTxSize;
    stream >> serverMask;
    stream >> descriptorFlag;
    stream >> macFlags;
    stream >> maxBufferSize;
    stream >> bitField;

    // Set node data
    m_manufacturerCode = manufacturerCode;
    m_maximumRxSize = maximalRxSize;
    m_maximumTxSize = maximalTxSize;
    m_maximumBufferSize = maxBufferSize;

    setServerMask(serverMask);
    setMacCapabilitiesFlag(macFlags);
    setDescriptorFlag(descriptorFlag);

    // Parse bit field
    // 0-2 Bit = logical type, 0 = coordinator, 1 = router, 2 = end device
    if (!ZigbeeUtils::checkBitUint16(bitField, 0) && !ZigbeeUtils::checkBitUint16(bitField, 1)) {
        m_nodeType = NodeTypeCoordinator;
    } else if (!ZigbeeUtils::checkBitUint16(bitField, 0) && ZigbeeUtils::checkBitUint16(bitField, 1)) {
        m_nodeType = NodeTypeRouter;
    } else if (ZigbeeUtils::checkBitUint16(bitField, 0) && !ZigbeeUtils::checkBitUint16(bitField, 1)) {
        m_nodeType = NodeTypeEndDevice;
    }

    m_complexDescriptorAvailable = (bitField >> 3) & 0x0001;
    m_userDescriptorAvailable = (bitField >> 4) & 0x0001;

    qCDebug(dcZigbeeNode()) << "Node descriptor:";
    qCDebug(dcZigbeeNode()) << "    Node type:" << nodeType();
    qCDebug(dcZigbeeNode()) << "    Sequence number:" << ZigbeeUtils::convertByteToHexString(sequenceNumber);
    qCDebug(dcZigbeeNode()) << "    Status:" << ZigbeeUtils::convertByteToHexString(status);
    qCDebug(dcZigbeeNode()) << "    Short address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress);
    qCDebug(dcZigbeeNode()) << "    Manufacturer code:" << ZigbeeUtils::convertUint16ToHexString(manufacturerCode);
    qCDebug(dcZigbeeNode()) << "    Maximum Rx size:" << ZigbeeUtils::convertUint16ToHexString(maximumRxSize());
    qCDebug(dcZigbeeNode()) << "    Maximum Tx size:" << ZigbeeUtils::convertUint16ToHexString(maximumTxSize());
    qCDebug(dcZigbeeNode()) << "    Maximum buffer size:" << ZigbeeUtils::convertByteToHexString(maximumBufferSize());
    qCDebug(dcZigbeeNode()) << "    Server mask:" << ZigbeeUtils::convertUint16ToHexString(serverMask);
    qCDebug(dcZigbeeNode()) << "        Primary Trust center:" << isPrimaryTrustCenter();
    qCDebug(dcZigbeeNode()) << "        Backup Trust center:" << isBackupTrustCenter();
    qCDebug(dcZigbeeNode()) << "        Primary Binding cache:" << isPrimaryBindingCache();
    qCDebug(dcZigbeeNode()) << "        Backup Binding cache:" << isBackupBindingCache();
    qCDebug(dcZigbeeNode()) << "        Primary Discovery cache:" << isPrimaryDiscoveryCache();
    qCDebug(dcZigbeeNode()) << "        Backup Discovery cache:" << isBackupDiscoveryCache();
    qCDebug(dcZigbeeNode()) << "        Network Manager:" << isNetworkManager();
    qCDebug(dcZigbeeNode()) << "    Descriptor flag:" << ZigbeeUtils::convertByteToHexString(descriptorFlag);
    qCDebug(dcZigbeeNode()) << "        Extended active endpoint list available:" << extendedActiveEndpointListAvailable();
    qCDebug(dcZigbeeNode()) << "        Extended simple descriptor list available:" << extendedSimpleDescriptorListAvailable();
    qCDebug(dcZigbeeNode()) << "    MAC flags:" << ZigbeeUtils::convertByteToHexString(macFlags);
    qCDebug(dcZigbeeNode()) << "        Alternate PAN coordinator:" << alternatePanCoordinator();
    qCDebug(dcZigbeeNode()) << "        Device type:" << deviceType();
    qCDebug(dcZigbeeNode()) << "        Power source flag main power:" << powerSourceFlagMainPower();
    qCDebug(dcZigbeeNode()) << "        Receiver on when idle:" << receiverOnWhenIdle();
    qCDebug(dcZigbeeNode()) << "        Security capability:" << securityCapability();
    qCDebug(dcZigbeeNode()) << "        Allocate address:" << allocateAddress();
    qCDebug(dcZigbeeNode()) << "    Bit field:" << ZigbeeUtils::convertUint16ToHexString(bitField);
    qCDebug(dcZigbeeNode()) << "        Complex desciptor available:" << complexDescriptorAvailable();
    qCDebug(dcZigbeeNode()) << "        User desciptor available:" << userDescriptorAvailable();
}

quint16 ZigbeeNode::serverMask() const
{
    return m_serverMask;
}

void ZigbeeNode::setServerMask(quint16 serverMask)
{
    m_serverMask = serverMask;
    m_isPrimaryTrustCenter = ((m_serverMask >> 0) & 0x0001);
    m_isBackupTrustCenter = ((m_serverMask >> 1) & 0x0001);
    m_isPrimaryBindingCache = ((m_serverMask >> 2) & 0x0001);
    m_isBackupBindingCache = ((m_serverMask >> 3) & 0x0001);
    m_isPrimaryDiscoveryCache = ((m_serverMask >> 4) & 0x0001);
    m_isBackupDiscoveryCache = ((m_serverMask >> 5) & 0x0001);
    m_isNetworkManager = ((m_serverMask >> 6) & 0x0001);
}

quint8 ZigbeeNode::macCapabilitiesFlag() const
{
    return m_macCapabilitiesFlag;
}

void ZigbeeNode::setMacCapabilitiesFlag(quint8 macFlag)
{
    m_macCapabilitiesFlag = macFlag;
    m_alternatePanCoordinator = ((m_macCapabilitiesFlag >> 0) & 0x01);
    if (((m_macCapabilitiesFlag >> 1) & 0x01)) {
        m_deviceType = DeviceTypeFullFunction;
    } else {
        m_deviceType = DeviceTypeReducedFunction;
    }
    m_powerSourceFlagMainPower = ((m_macCapabilitiesFlag >> 2) & 0x01);
    m_receiverOnWhenIdle = ((m_macCapabilitiesFlag >> 3) & 0x01);
    m_securityCapability = ((m_macCapabilitiesFlag >> 6) & 0x01);
    m_allocateAddress = ((m_macCapabilitiesFlag >> 7) & 0x01);
}

void ZigbeeNode::setDescriptorFlag(quint8 descriptorFlag)
{
    m_descriptorFlag = descriptorFlag;
    m_extendedActiveEndpointListAvailable = ((m_descriptorFlag >> 0) & 0x01);
    m_extendedSimpleDescriptorListAvailable = ((m_descriptorFlag >> 1) & 0x01);
}

quint16 ZigbeeNode::powerDescriptorFlag() const
{
    return m_powerDescriptorFlag;
}

void ZigbeeNode::setPowerDescriptorFlag(quint16 powerDescriptorFlag)
{
    m_powerDescriptorFlag = powerDescriptorFlag;

    // Bit 0 - 3 Power mode
    // 0000: Receiver configured according to “Receiver on when idle” MAC flag in the Node Descriptor
    // 0001: Receiver switched on periodically
    // 0010: Receiver switched on when stimulated, e.g. by pressing a button

    m_powerMode = PowerModeAlwaysOn;
    if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) {
        m_powerMode = PowerModeAlwaysOn;
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) {
        m_powerMode = PowerModeOnPeriodically;
    } else if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) {
        m_powerMode = PowerModeOnWhenStimulated;
    }

    // Bit 4 - 7 Available power sources
    // Bit 0: Permanent mains supply
    // Bit 1: Rechargeable battery
    // Bit 2: Disposable battery
    // Bit 4: Reserved

    m_availablePowerSources.clear();
    if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 4)) {
        m_availablePowerSources.append(PowerSourcePermanentMainSupply);
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 5)) {
        m_availablePowerSources.append(PowerSourceRecharchableBattery);
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 6)) {
        m_availablePowerSources.append(PowerSourceDisposableBattery);
    }

    // Bit 8 - 11 Active source: according to the same schema as available power sources
    m_powerSource = PowerSourcePermanentMainSupply;
    if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 8)) {
        m_powerSource = PowerSourcePermanentMainSupply;
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 9)) {
        m_powerSource = PowerSourceRecharchableBattery;
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 10)) {
        m_powerSource = PowerSourceDisposableBattery;
    }

    // Bit 12 - 15: Battery level if available
    // 0000: Critically low
    // 0100: Approximately 33%
    // 1000: Approximately 66%
    // 1100: Approximately 100% (near fully charged)
    m_powerLevel = PowerLevelCriticalLow;
    if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) {
        m_powerLevel = PowerLevelCriticalLow;
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) {
        m_powerLevel = PowerLevelLow;
    } else if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) {
        m_powerLevel = PowerLevelOk;
    } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) {
        m_powerLevel = PowerLevelFull;
    }

    qCDebug(dcZigbeeNode()) << "Node power descriptor (" << ZigbeeUtils::convertUint16ToHexString(m_powerDescriptorFlag) << "):";
    qCDebug(dcZigbeeNode()) << "    Power mode:" << m_powerMode;
    qCDebug(dcZigbeeNode()) << "    Available power sources:";
    foreach (const PowerSource &source, m_availablePowerSources) {
        qCDebug(dcZigbeeNode()) << "        " << source;
    }
    qCDebug(dcZigbeeNode()) << "    Power source:" << m_powerSource;
    qCDebug(dcZigbeeNode()) << "    Power level:" << m_powerLevel;

}

void ZigbeeNode::setClusterAttribute(Zigbee::ClusterId clusterId, const ZigbeeClusterAttribute &attribute)
{
    qCDebug(dcZigbeeNode()) << this << "cluster attribute changed" << clusterId << attribute;
//    ZigbeeCluster *cluster = m_outputClusters.value(clusterId);

//    // Note: create the cluster if not there yet
//    bool clusterCreated = false;
//    if (!cluster) {
//        cluster = new ZigbeeCluster(clusterId, this);
//        qCDebug(dcZigbeeNode()) << "Created cluster" << cluster;
//        connect(cluster, &ZigbeeCluster::attributeChanged, this, &ZigbeeNode::onClusterAttributeChanged);
//        m_outputClusters.insert(clusterId, cluster);
//        clusterCreated = true;
//    }

//    // Set the attribute if valid
//    if (attribute.isValid())
//        cluster->setAttribute(attribute);

//    if (clusterCreated)
//        emit clusterAdded(cluster);

}

void ZigbeeNode::onClusterAttributeChanged(const ZigbeeClusterAttribute &attribute)
{
    ZigbeeCluster *cluster = static_cast<ZigbeeCluster *>(sender());
    qCDebug(dcZigbeeNode()) << "Cluster" << cluster << "attribute changed" << attribute;
    emit clusterAttributeChanged(cluster, attribute);
}

//void ZigbeeNode::onRequestUserDescriptorFinished()
//{
//    ZigbeeInterfaceReply *reply = static_cast<ZigbeeInterfaceReply *>(sender());
//    reply->deleteLater();

//    if (reply->status() != ZigbeeInterfaceReply::Success) {
//        qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
//        return;
//    }

//    qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully";
//    qCDebug(dcZigbeeController()) << reply->additionalMessage();

//    quint8 sequenceNumber = static_cast<quint8>(reply->additionalMessage().data().at(0));
//    quint8 status = static_cast<quint8>(reply->additionalMessage().data().at(1));

//    quint16 nwkAddress = static_cast<quint16>(reply->additionalMessage().data().at(2));
//    nwkAddress <<= 8;
//    nwkAddress |= reply->additionalMessage().data().at(3);

//    quint8 length = static_cast<quint8>(reply->additionalMessage().data().at(4));

//    QByteArray data;
//    if (length > 0) {
//        data = reply->additionalMessage().data().mid(5, length);
//    }

//    qCDebug(dcZigbeeNode()) << "User descriptor:";
//    qCDebug(dcZigbeeNode()) << "    Sequence number:" << ZigbeeUtils::convertByteToHexString(sequenceNumber);
//    qCDebug(dcZigbeeNode()) << "    Status:" << ZigbeeUtils::convertByteToHexString(status);
//    qCDebug(dcZigbeeNode()) << "    Attribute address:" << ZigbeeUtils::convertUint16ToHexString(nwkAddress);
//    qCDebug(dcZigbeeNode()) << "    Lenght:" << ZigbeeUtils::convertByteToHexString(length);
//    qCDebug(dcZigbeeNode()) << "    Data:" << data;
//}

//void ZigbeeNode::onToggleFinished()
//{
//    ZigbeeInterfaceReply *reply = static_cast<ZigbeeInterfaceReply *>(sender());
//    reply->deleteLater();

//    if (reply->status() != ZigbeeInterfaceReply::Success) {
//        qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
//        return;
//    }

//    qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully";
//}

//void ZigbeeNode::onIdentifyFinished()
//{
    
//}

QDebug operator<<(QDebug debug, ZigbeeNode *node)
{
    debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress());
    debug.nospace().noquote() << ", " << node->extendedAddress().toString();
//    debug.nospace().noquote() << ", End points: ";

//    for (int i = 0; i < node->endPoints().count(); i++) {
//        debug.nospace().noquote() << ZigbeeUtils::convertByteToHexString(node->endPoints().at(i));
//        if (i != node->endPoints().count() - 1 && node->endPoints().count() > 1) {
//            debug.nospace().noquote() << ", ";
//        }
//    }

    debug.nospace().noquote() << ") ";
    return debug;
}
