// 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/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "zdo/zigbeedeviceprofile.h"
#include "zigbeenetworkti.h"
#include "loggingcategory.h"
#include "zigbeeutils.h"
#include "zigbeenetworkdatabase.h"

#include <QDataStream>
#include <QtCrypto>

ZigbeeNetworkTi::ZigbeeNetworkTi(const QUuid &networkUuid, QObject *parent) :
    ZigbeeNetwork(networkUuid, parent)
{
    m_controller = new ZigbeeBridgeControllerTi(this);
    connect(m_controller, &ZigbeeBridgeControllerTi::availableChanged, this, &ZigbeeNetworkTi::onControllerAvailableChanged);
    connect(m_controller, &ZigbeeBridgeControllerTi::controllerStateChanged, this, &ZigbeeNetworkTi::onControllerStateChanged);
    connect(m_controller, &ZigbeeBridgeControllerTi::firmwareVersionChanged, this, &ZigbeeNetworkTi::firmwareVersionChanged);
    connect(m_controller, &ZigbeeBridgeControllerTi::permitJoinStateChanged, this, &ZigbeeNetworkTi::onPermitJoinStateChanged);
    connect(m_controller, &ZigbeeBridgeControllerTi::deviceIndication, this, &ZigbeeNetworkTi::onDeviceIndication);
    connect(m_controller, &ZigbeeBridgeControllerTi::apsDataIndicationReceived, this, &ZigbeeNetworkTi::onApsDataIndicationReceived);
    connect(m_controller, &ZigbeeBridgeControllerTi::apsDataConfirmReceived, this, &ZigbeeNetworkTi::onApsDataConfirmReceived);
    connect(m_controller, &ZigbeeBridgeControllerTi::nodeLeft, this, &ZigbeeNetworkTi::onNodeLeaveIndication);
}

ZigbeeBridgeController *ZigbeeNetworkTi::bridgeController() const
{
    if (!m_controller)
        return nullptr;

    return m_controller;
}

Zigbee::ZigbeeBackendType ZigbeeNetworkTi::backendType() const
{
    return Zigbee::ZigbeeBackendTypeTi;
}

ZigbeeNetworkReply *ZigbeeNetworkTi::sendRequest(const ZigbeeNetworkRequest &request)
{
    ZigbeeNetworkReply *reply = createNetworkReply(request);

    // Finish the reply right away if the network is offline
    if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline || state() == ZigbeeNetwork::StateStopping) {
        finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline);
        return reply;
    }

    if (state() == ZigbeeNetwork::StateStarting) {
        m_requestQueue.append(reply);
        return reply;
    }

    ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(request);
    connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
        if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
            qCWarning(dcZigbeeController()) << "Could not send request to controller." << interfaceReply->statusCode();
            finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
            return;
        }

        finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
    });

    return reply;
}

void ZigbeeNetworkTi::setPermitJoining(quint8 duration, quint16 address)
{
    if (duration > 0) {
        qCDebug(dcZigbeeNetwork()) << "Set permit join for" << duration << "s on" << ZigbeeUtils::convertUint16ToHexString(address);
    } else {
        qCDebug(dcZigbeeNetwork()) << "Disable permit join on"<< ZigbeeUtils::convertUint16ToHexString(address);
    }

    ZigbeeInterfaceTiReply *requestPermitJoinReply = m_controller->requestPermitJoin(duration, address);
    connect(requestPermitJoinReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
        if (requestPermitJoinReply->statusCode() != Ti::StatusCodeSuccess) {
            qCWarning(dcZigbeeNetwork()) << "Could not set permit join to" << duration << ZigbeeUtils::convertUint16ToHexString(address) << requestPermitJoinReply->statusCode();
            return;
        }
        qCDebug(dcZigbeeNetwork()) << "Permit join request finished successfully:" << duration;

        setPermitJoiningState(true, duration);

        // Opening the green power network too
        // Todo: This should probably be somewhere else, but not yet sure how other backeds deal with this
        QByteArray payload;
        QDataStream stream(&payload, QIODevice::WriteOnly);
        stream.setByteOrder(QDataStream::LittleEndian);
        stream << static_cast<quint8>(0x0b); // options
        stream << static_cast<quint16>(duration);


        // Build ZCL frame control
        ZigbeeClusterLibrary::FrameControl frameControl;
        frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
        frameControl.manufacturerSpecific = false;
        frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
        frameControl.disableDefaultResponse = true;

        // Build ZCL header
        ZigbeeClusterLibrary::Header header;
        header.frameControl = frameControl;
        header.command = 0x02;// TODO: ZigbeeClusterGreenPower::ClusterCommandCommissioningMode;
        header.transactionSequenceNumber = generateSequenceNumber();

        // Build ZCL frame
        ZigbeeClusterLibrary::Frame frame;
        frame.header = header;
        frame.payload = payload;

        ZigbeeNetworkRequest request;
        request.setRequestId(generateSequenceNumber());
        request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
        request.setDestinationShortAddress(0xFFFD);
        request.setDestinationEndpoint(242); // Green Power Endpoint
        request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
        request.setClusterId(0x21);
        request.setTxOptions(static_cast<Zigbee::ZigbeeTxOption>(0x00)); // FIXME: There should be TxOptionsNone  I guess...
        request.setSourceEndpoint(242); // Green Power Endpoint
        request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
        request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));

        m_controller->requestSendRequest(request);
    });
}

void ZigbeeNetworkTi::initController()
{
    qCDebug(dcZigbeeNetwork()) << "Initializing controller";
    setState(StateStarting);
    setError(ErrorNoError);

    ZigbeeInterfaceTiReply *initReply = m_controller->init();
    connect(initReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
        if (initReply->statusCode() != Ti::StatusCodeSuccess) {
            qCWarning(dcZigbeeNetwork()) << "Error initializing controller";
            setState(StateUninitialized);
            setError(ErrorZigbeeError);
            return;
        }
    });
}

void ZigbeeNetworkTi::commissionController()
{
    qCDebug(dcZigbeeNetwork()) << "Commissioning controller";

    quint16 panId = ZigbeeUtils::generateRandomPanId();

    ZigbeeInterfaceTiReply *commissionReply = m_controller->commission(Ti::DeviceLogicalTypeCoordinator, panId, channelMask());
    connect(commissionReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
        if (commissionReply->statusCode() != Ti::StatusCodeSuccess) {
            qCWarning(dcZigbeeNetwork()) << "Error commissioning controller.";
            setState(StateUninitialized);
            setError(ErrorZigbeeError);
            return;
        }

        qCDebug(dcZigbeeNetwork()) << "Controller commissioned";

        startControllerNetwork();
        setMacAddress(m_controller->networkConfiguration().ieeeAddress);
    });
}

void ZigbeeNetworkTi::startControllerNetwork()
{
    setState(StateStarting);
    qCDebug(dcZigbeeNetwork()) << "Starting network on controller...";
    ZigbeeInterfaceTiReply *startReply = m_controller->start();
    connect(startReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
        if (startReply->statusCode() != Ti::StatusCodeSuccess) {
            qCWarning(dcZigbeeNetwork()) << "Error starting network.";
            setState(StateOffline);
            setError(ErrorZigbeeError);
            return;
        }
    });
}

void ZigbeeNetworkTi::processGreenPowerFrame(const Zigbee::ApsdeDataIndication &indication)
{
    ZigbeeClusterLibrary::Frame inputFrame = ZigbeeClusterLibrary::parseFrameData(indication.asdu);
    QDataStream inputStream(inputFrame.payload);
    inputStream.setByteOrder(QDataStream::LittleEndian);
    quint8 commandId, payloadSize;
    quint16 options;
    quint32 srcId, frameCounter;
    inputStream >> options >> srcId >> frameCounter >> commandId >> payloadSize;
    QByteArray commandFrame = inputFrame.payload.right(payloadSize);
    qCWarning(dcZigbeeNetwork()) << "Green Power frame:" << options << srcId << frameCounter << commandId << payloadSize << commandFrame.toHex();

    if (commandId == 0xE0) {
        qCWarning(dcZigbeeNetwork()) << "Green power commissioning";
        QDataStream commandStream(commandFrame);
        commandStream.setByteOrder(QDataStream::LittleEndian);
        quint8 deviceId, inputOptions, extendedOptions;
        QByteArray securityKey;
        quint32 keyMic;
        quint32 outgoingCounter;
        commandStream >> deviceId >> inputOptions >> extendedOptions;
        for (int i = 0; i < 16; i++) {
            quint8 byte;
            commandStream >> byte;
            securityKey.append(byte);
        }
        commandStream >> keyMic >> outgoingCounter;

        // Send commissioning reply
        QByteArray payload;
        QDataStream stream(&payload, QIODevice::WriteOnly);
        stream.setByteOrder(QDataStream::LittleEndian);
        ZigbeeDataType options = ZigbeeDataType(static_cast<quint32>(0x00e548), Zigbee::Uint24); // options
        for (int i = 0; i < options.data().length(); i++) {
            stream << static_cast<quint8>(options.data().at(i));
        }
        stream << srcId;
        stream << static_cast<quint16>(0x0b84); // Green Power group as configured during startup
        stream << deviceId;
        stream << outgoingCounter;
        payload.append(encryptSecurityKey(srcId, securityKey));

        // Build ZCL frame control
        ZigbeeClusterLibrary::FrameControl frameControl;
        frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
        frameControl.manufacturerSpecific = false;
        frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
        frameControl.disableDefaultResponse = true;

        // Build ZCL header
        ZigbeeClusterLibrary::Header header;
        header.frameControl = frameControl;
        header.command = 0x01; // TODO: ZigbeeClusterGreenPower::ClusterCommandPairing;
        header.transactionSequenceNumber = generateSequenceNumber() - 1;

        // Build ZCL frame
        ZigbeeClusterLibrary::Frame frame;
        frame.header = header;
        frame.payload = payload;

        ZigbeeNetworkRequest request;
        request.setRequestId(generateSequenceNumber() + 1);
        request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
        request.setDestinationShortAddress(0xFFFD);
        request.setDestinationEndpoint(242); // Green Power Endpoint
        request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
        request.setClusterId(0x21);
        request.setTxOptions(static_cast<Zigbee::ZigbeeTxOption>(0x00)); // FIXME: There should be TxOptionsNone  I guess...
        request.setSourceEndpoint(242); // Green Power Endpoint
        request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
        request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));

        m_controller->requestSendRequest(request);

        QStringList l;
        for (int i = 0; i < request.asdu().length(); i++) {
            l.append(QString::number(static_cast<quint8>(request.asdu().at(i))));
        }

        // TODO: create the GreenPower Node here once GreenPower support is added properly
        // emit greenPowerDeviceJoined(srcId, deviceId, indication.sourceIeeeAddress())
    }
}

QByteArray ZigbeeNetworkTi::encryptSecurityKey(quint32 sourceId, const QByteArray &securityKey)
{
#if (QCA_VERSION >= QCA_VERSION_CHECK(2, 2, 0))
    QByteArray sourceIdArray;
    sourceIdArray.append(static_cast<quint8>(sourceId & 0x000000ff));
    sourceIdArray.append(static_cast<quint8>((sourceId & 0x0000ff00) >> 8));
    sourceIdArray.append(static_cast<quint8>((sourceId & 0x00ff0000) >> 16));
    sourceIdArray.append(static_cast<quint8>((sourceId & 0xff000000) >> 24));

    QByteArray nonce(13, Qt::Uninitialized);
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            nonce[4 * i + j] = sourceIdArray.at(j);
        }
    }
    nonce[12] = 0x05;

    QByteArray zigbeeLinkKey = securityConfiguration().globalTrustCenterLinkKey().toByteArray();

    QCA::Initializer init;
    QCA::Cipher cipher("aes128", QCA::Cipher::CCM, QCA::Cipher::DefaultPadding, QCA::Encode, zigbeeLinkKey, nonce);
    QByteArray encrypted = cipher.update(securityKey).toByteArray();
    encrypted.append(cipher.final().toByteArray());
    return encrypted;
#else
    Q_UNUSED(sourceId)
    qCWarning(dcZigbeeNetwork()) << "Greenpower encryption requires AES-128-CCM (requires qca-qt5-2 >= 2.2.0)";
    return securityKey;
#endif
}

void ZigbeeNetworkTi::onControllerAvailableChanged(bool available)
{
    if (!available) {
        qCWarning(dcZigbeeNetwork()) << "Hardware controller is not available any more.";
        setError(ErrorHardwareUnavailable);
        setPermitJoiningState(false);
        setState(StateOffline);
        setError(ErrorHardwareUnavailable);
    } else {
        setPermitJoiningState(false);
        setState(StateOffline);
        setError(ErrorNoError);
        qCDebug(dcZigbeeNetwork()) << "Hardware controller is now available.";
        initController();
    }
}

void ZigbeeNetworkTi::onControllerStateChanged(ZigbeeBridgeControllerTi::ControllerState state)
{
    switch (state) {
    case ZigbeeBridgeControllerTi::ControllerStateDown:
        setState(StateOffline);
        break;
    case ZigbeeBridgeControllerTi::ControllerStateInitialized:

        // The mac address of the controller will be stored locally when the coordinator node replies
        // to the node descriptor request which includes the IEEE address.
        // If we don't know the mac address yet, it means that we've never had the zigbee network up and running
        // => start the commissioning procedure, else, directly start the network
        // Note: if the user messes around with the stick by provisioning stuff with another instance/software, it
        // will not be detected currently and the "wrong" network starts up.
        qCDebug(dcZigbeeNetwork()) << "Controller initialized";
        qCDebug(dcZigbeeNetwork()) << "Stored MAC address:" << macAddress() << "Controller MAC address:" << m_controller->networkConfiguration().ieeeAddress;

        if (macAddress() != ZigbeeAddress("00:00:00:00:00:00:00:00") && m_controller->networkConfiguration().ieeeAddress != macAddress()) {
            qCWarning(dcZigbeeNetwork()) << "The controller MAC address changed. Please connect the original controller or create a new network.";
            setState(StateUninitialized);
            setError(ErrorHardwareModuleChanged);
            stopNetwork();
            return;
        }
        if (macAddress() == ZigbeeAddress("00:00:00:00:00:00:00")) {
            // Controller network is not commissioned yet
            qCDebug(dcZigbeeNetwork()) << "Controller is not comissioned yet. Comissioning now...";
            commissionController();
            return;
        }

        qCDebug(dcZigbeeNetwork()) << "Controller is ready and commissioned.";
        startControllerNetwork();

        break;
    case ZigbeeBridgeControllerTi::ControllerStateRunning: {
        qCDebug(dcZigbeeNetwork()) << "Controller network running. Registering endpoints on controller..";

        setPanId(m_controller->networkConfiguration().panId);
        setExtendedPanId(m_controller->networkConfiguration().extendedPanId);
        setChannel(m_controller->networkConfiguration().currentChannel);

        // TODO: This should be public API of libnymea-zigbee so that the application layer (e.g. nymea-plugins)
        // can register the endpoints it needs for the particular application/device.
        // Fow now we're registering HomeAutomation, LightLink and GreenPower endpoints.
        m_controller->registerEndpoint(1, Zigbee::ZigbeeProfileHomeAutomation, 5, 0, {ZigbeeClusterLibrary::ClusterIdOtaUpgrade});
        m_controller->registerEndpoint(12, Zigbee::ZigbeeProfileLightLink, 5, 0);

        // The Green Power endpoing is a bit special, it also needs to be added to a group
        ZigbeeInterfaceTiReply *registerLLEndpointReply = m_controller->registerEndpoint(242, Zigbee::ZigbeeProfileGreenPower, 5, 0);
        connect(registerLLEndpointReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
            if (registerLLEndpointReply->statusCode() != Ti::StatusCodeSuccess) {
                qCWarning(dcZigbeeNetwork()) << "Error registering GreenPower endpoint.";
                setState(StateOffline);
                return;
            }
            qCDebug(dcZigbeeNetwork()) << "Registered GreenPower endpoint on coordinator node.";

            ZigbeeInterfaceTiReply *addGEndpointGroupReply = m_controller->addEndpointToGroup(242, 0x0b84);
            connect(addGEndpointGroupReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
                if (addGEndpointGroupReply->statusCode() != Ti::StatusCodeSuccess) {
                    qCWarning(dcZigbeeNetwork()) << "Error adding GreenPower endpoint to group.";
                    setState(StateOffline);
                    return;
                }

                // Now we're done. If this is a first start (no coordinator node loaded from configs) we'll add
                // ourselves now and start the inspection.
                if (!m_coordinatorNode) {
                    qCDebug(dcZigbeeNetwork()) << "Initializing coordinator node:" << m_controller->networkConfiguration();
                    ZigbeeNode *coordinatorNode = createNode(m_controller->networkConfiguration().shortAddress, m_controller->networkConfiguration().ieeeAddress, this);
                    m_coordinatorNode = coordinatorNode;
                    addUnitializedNode(coordinatorNode);
                }


                ZigbeeInterfaceTiReply *ledReply = m_controller->setLed(false);
                connect(ledReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {

                    setState(StateRunning);

                    // Introspecing ourselves on every start. Most of the times this wouldn't be needed, but if the above
                    // endpoints are changed (e.g. on a future upgrade), we'll want to refresh.
                    m_coordinatorNode->startInitialization();
                    connect(m_coordinatorNode, &ZigbeeNode::stateChanged, this, [=](ZigbeeNode::State state){
                        if (state == ZigbeeNode::StateInitialized) {
                            setNodeInformation(m_coordinatorNode, "z-Stack", "", bridgeController()->firmwareVersion());
                        }
                    });

                    while (!m_requestQueue.isEmpty()) {
                        ZigbeeNetworkReply *reply = m_requestQueue.takeFirst();
                        ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(reply->request());
                        connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
                            if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
                                qCWarning(dcZigbeeController()) << "Could send request to controller." << interfaceReply->statusCode();
                                finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
                                return;
                            }
                            finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
                        });
                    }
                });
            });
        });

        break;
    }
    }
}

void ZigbeeNetworkTi::onPermitJoinStateChanged(quint8 duration)
{
    setPermitJoiningState(duration > 0, duration);
    m_controller->setLed(duration > 0);
}

void ZigbeeNetworkTi::onDeviceIndication(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, quint8 macCapabilities)
{
    onDeviceAnnounced(shortAddress, ieeeAddress, macCapabilities);
}

void ZigbeeNetworkTi::onNodeLeaveIndication(const ZigbeeAddress &ieeeAddress, bool request, bool remove, bool rejoin)
{
    qCDebug(dcZigbeeNetwork()) << "Received node leave indication" << ieeeAddress.toString() << "request:" << request << "remove:" << remove << "rejoining:" << rejoin;
    if (!hasNode(ieeeAddress)) {
        qCDebug(dcZigbeeNetwork()) << "Node left the network" << ieeeAddress.toString();
        return;
    }

    ZigbeeNode *node = getZigbeeNode(ieeeAddress);
    qCDebug(dcZigbeeNetwork()) << node << "left the network";
    removeNode(node);
}

void ZigbeeNetworkTi::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm)
{
    qCDebug(dcZigbeeNetwork()) << "Data confirm received:" << confirm;
}

void ZigbeeNetworkTi::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication)
{
    // If it's for the green power endpoint, we'll have to do a commissioning.
    // TODO: This should probably be in ZigbeeNode or ZigbeeDeviceObject, but not yet sure how other backends deal with it
    if (indication.destinationEndpoint == 242) {
        processGreenPowerFrame(indication);
    }

    // Check if this indocation is related to any pending reply
    if (indication.profileId == Zigbee::ZigbeeProfileDevice) {
        handleZigbeeDeviceProfileIndication(indication);
        return;
    }

    // Else let the node handle this indication
    handleZigbeeClusterLibraryIndication(indication);
}

void ZigbeeNetworkTi::startNetwork()
{
    loadNetwork();

    if (!m_controller->enable(serialPortName(), serialBaudrate())) {
        setPermitJoiningState(false);
        setState(StateOffline);
        setError(ErrorHardwareUnavailable);
        return;
    }

    setPermitJoiningState(false);
}

void ZigbeeNetworkTi::stopNetwork()
{
    setState(StateStopping);
    m_controller->disable();
    setState(StateOffline);
}

void ZigbeeNetworkTi::reset()
{
    qCDebug(dcZigbeeNetwork()) << "Resetting controller.";
    m_controller->reset();
}

void ZigbeeNetworkTi::factoryResetNetwork()
{
    qCDebug(dcZigbeeNetwork()) << "Factory resetting network and forget all information. This cannot be undone.";

    ZigbeeInterfaceTiReply *reply = m_controller->factoryReset();
    connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
        qCDebug(dcZigbeeNetwork()) << "Factory reset reply finished" << reply->statusCode();

        m_controller->disable();
        clearSettings();
        setMacAddress(ZigbeeAddress("00:00:00:00:00:00"));
        setState(StateOffline);
        setError(ErrorNoError);
        qCDebug(dcZigbeeNetwork()) << "The factory reset is finished. Restarting with a fresh network.";
        startNetwork();
    });
}

void ZigbeeNetworkTi::destroyNetwork()
{
    qCDebug(dcZigbeeNetwork()) << "Destroy network and delete the database";
    m_controller->disable();
    clearSettings();
}
