// bytran -|- spectral calculations for portable devices using the HITRAN database
//
// Developed by Dzianis Pliutau (Denis Plutov) and Kostya Roslyakov
// www.bytran.org | bytran@bytran.org
// Copyright (c) 2016 Dzianis Pliutau
// Distributed under the MIT license
//
// The following examples were used to write this class
// https://doc.qt.io/qt-5.10/qtwebsockets-index.html
// https://www.varvet.com/blog/using-websockets-in-native-ios-and-android-apps/
// https://doc.qt.io/qt-5.6/qtwebsockets-simplechat-example.html - most useful
// https://doc.qt.io/qt-5.10/qtwebsockets-echoclient-example.html
// https://doc.qt.io/qt-5.10/qtwebsockets-echoserver-example.html
// https://stackoverflow.com/questions/13835989/get-local-ip-address-in-qt
// https://stackoverflow.com/questions/5572263/get-current-qnetworkinterface-active-and-connected-to-the-internet
// http://www.websocket.org/echo.html
// https://stackoverflow.com/questions/25327039/can-websockets-work-on-mobile-phones
//
// The MIT License (MIT)
// Copyright (c) 2016 Dzianis Pliutau
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.



// Web sockets clss is used for remote control and data transfer between devices using the WebSockets protocol

// This class was developed mostly using the Qt Simple Chat example
// https://doc.qt.io/qt-5.6/qtwebsockets-simplechat-example.html dstributed under the BSD license
#include "websockets.h"
#include <QtCore>

static QString getIdentifier(QWebSocket *peer)
{
    return QStringLiteral("%1:%2").arg(peer->peerAddress().toString(), QString::number(peer->peerPort()));
}

WebSockets::WebSockets(quint16 port, QObject *parent) : QObject(parent)
{
    this->portNumber = port;
    webSocketServer = new QWebSocketServer(QStringLiteral("bytran web socket server"), QWebSocketServer::NonSecureMode, this);

    if (webSocketServer->listen(QHostAddress::Any, this->portNumber))
    {        
        connect(webSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
        connect(webSocketServer, SIGNAL(closed()), this, SIGNAL(closed()));
    }

    // Getting the IP address(es) for the current device
    getSelfDeviceIpAddresses();
}

WebSockets::~WebSockets()
{
    webSocketServer->close();
    qDeleteAll(clients.begin(), clients.end());
}

// See if there is a different or better way to disable the Web Socket Server temporarily
void WebSockets::enableWebSockets(bool state)
{
    if (state == false)
    {
        webSocketServer->close();
        qDeleteAll(clients.begin(), clients.end());
    }
    else
    {
        webSocketServer->close();
        qDeleteAll(clients.begin(), clients.end());
        webSocketServer = new QWebSocketServer(QStringLiteral("bytran web socket server"), QWebSocketServer::NonSecureMode, this);
        if (webSocketServer->listen(QHostAddress::Any, this->portNumber))
        {
            connect(webSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
            connect(webSocketServer, SIGNAL(closed()), this, SIGNAL(closed()));
        }
    }
}
bool WebSockets::getEnableWebSockets() { return webSocketServer->isListening(); }

// Methods to get and set the port numbers
quint32 WebSockets::getPortNumber() { return this->portNumber; }
void WebSockets::setPortNumber(quint16 newPortNumber)
{
    this->portNumber = newPortNumber;
    webSocketServer->close();
    qDeleteAll(clients.begin(), clients.end());
    webSocketServer = new QWebSocketServer(QStringLiteral("bytran web socket server"), QWebSocketServer::NonSecureMode, this);
    if (webSocketServer->listen(QHostAddress::Any, newPortNumber))
    {
        connect(webSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
        connect(webSocketServer, SIGNAL(closed()), this, SIGNAL(closed()));
    }
}

// Method is used to get the IP address assigned to the device
QString WebSockets::getSelfDeviceIpAddresses()
{
    int i;  // Loop variable
    QList<QNetworkInterface> allInterfaces;
    QNetworkInterface interface;
    QNetworkInterface::InterfaceFlags flags;
    QList<QNetworkAddressEntry> allEntries;
    QNetworkAddressEntry entry;
    bool isRunning;
    bool isNotLoopBack;
    bool isNotPointToPoint;
    bool isUp;
    bool canBroadcast;
    QList<QString> foundIpAddresses;
    QString ipAddressStr;


    foundIpAddresses.clear();
    allInterfaces = QNetworkInterface::allInterfaces();

    foreach(interface, allInterfaces)
    {
        flags = interface.flags();
        isRunning = (bool) (flags & QNetworkInterface::IsRunning);
        isNotLoopBack = !(bool)(flags & QNetworkInterface::IsLoopBack);
        isNotPointToPoint = !(bool)(flags & QNetworkInterface::IsPointToPoint);
        isUp = (bool)(flags & QNetworkInterface::IsUp);
        canBroadcast = (bool)(flags & QNetworkInterface::CanBroadcast);

        if ( isRunning && isNotLoopBack && isNotPointToPoint && isUp && canBroadcast)
        {
            allEntries = interface.addressEntries();
            foreach(entry, allEntries)
            {
                if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol)
                {                    
                    foundIpAddresses.append(entry.ip().toString()); // + " - " + interface.humanReadableName());
                    qDebug() << entry.ip().toString() << " / " << entry.netmask().toString() << " / " << interface.humanReadableName() << " / " << interface.hardwareAddress();
                }
            }
        }
    }

    if (foundIpAddresses.count() > 0) {
        ipAddressStr = foundIpAddresses.first();
        for (i = 1; i < foundIpAddresses.length(); i++) { ipAddressStr = ipAddressStr + "\n" + foundIpAddresses.at(i); }
    }
    else {
        ipAddressStr = "No connection";
    }
    return ipAddressStr;
}

// Method to get the IP address of the connected device
QString WebSockets::getConnectedDeviceIpAddress()
{
    QString ipAddress;

    ipAddress = "";
    if (clients.count() > 0)
    {
        ipAddress = clients.last()->peerAddress().toString();
        ipAddress.remove(":", Qt::CaseInsensitive);
        ipAddress.remove("f", Qt::CaseInsensitive);
        ipAddress.remove(" ", Qt::CaseInsensitive);

    }
    return ipAddress;  // If the IP address is blank, then no clients are connected to the server
}

void WebSockets::onNewConnection()
{
    int i;  // Loop variable
    bool newClient;  // Variable indicates if this client has already connected before


    // New client connection
    auto pSocket = webSocketServer->nextPendingConnection();
    pSocket->setParent(this);

    newClient = true;
    for (i = 0; i < clients.count(); i++)
    {
        if (clients.at(i)->peerAddress() == pSocket->peerAddress()) { pSocket->abort();
                                                                      newClient = false; }
    }

    // Only append a new connection if no connection from the same IP already exists
    if (newClient)
    {
        // Creating connections for the new client
        connect(pSocket, SIGNAL(textMessageReceived(QString)), this, SLOT(processTextMessage(QString)));
        connect(pSocket, SIGNAL(binaryMessageReceived(QByteArray)), this, SLOT(processBinaryMessage(QByteArray)));
        connect(pSocket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));

        // Appending another client to the list of connected clients
        clients << pSocket;

        // Emitring a signal indicating that the connected state has been changed
        emit connectedStateChanged();
    }
}
void WebSockets::processTextMessage(const QString &message)
{
    QWebSocket *pSender = qobject_cast<QWebSocket *>(sender());
    for (QWebSocket *pClient : qAsConst(clients)) {
        if (pClient != pSender) // Do not echo message back to sender, only to other connected clients, this will need to be removed
            pClient->sendTextMessage(message);

        // Sending a message with a current command
        emit commandReceived(message);
    }
}
void WebSockets::processBinaryMessage(QByteArray message)
{
    QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
    qDebug() << "Binary message received: " << message;
    pClient->sendBinaryMessage(message);
}
void WebSockets::socketDisconnected()
{    
//    This throws a compilation error for some reason
    QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
    if (pClient)
    {
        clients.removeAll(pClient);
        pClient->deleteLater();
    }

    // Emitring a signal indicating that the connected state has been changed
    emit connectedStateChanged();
}

// Methods to send a message to a remote device (in a string or binary representation)
void WebSockets::sendMessage(const QString &message)
{
    // Sending the message to all connected clients (even the ones which did not request a response)
    for (QWebSocket *pClient : qAsConst(clients)) {
            pClient->sendTextMessage(message);
    }
}
void WebSockets::sendMessage(const QByteArray &message)
{
    // Sending the message to all connected clients (even the ones which did not request a response)
    for (QWebSocket *pClient : qAsConst(clients)) {
            pClient->sendBinaryMessage(message);
    }
}
