 // 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 MIT License (MIT)
// Copyright (c) 2016 Dzianis Pliutau
//
// This class is based in part on the Qt Bluetooth chat example distributed under the BSD license copyrighted to The Qt Company Ltd.
// available at the following link: http://doc.qt.io/qt-5/qtbluetooth-chat-example.html
//
// Additionally, these links may be useful in implementing Bluetooth interaction
// http://doc.qt.io/qt-5/qtbluetooth-overview.html
// http://stackoverflow.com/questions/27229813/real-time-bluetooth-spp-data-streaming-on-android-only-works-for-5-seconds
//
// 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.



// This class is based in part on the Qt Bluetooth chat example
#include "bluetoothspp.h"
#include <QBluetoothAddress>
#include <QBluetoothServiceInfo>
#include <QMessageBox>
#include <QDebug>
#ifdef Q_OS_ANDROID
    #include <QtAndroid>
#endif

// Saving the UUID for the Bluetooth Serial Port Profile (SPP)
static const QLatin1String serviceUuid("00001101-0000-1000-8000-00805F9B34FB");
#ifdef Q_OS_ANDROID
static const QLatin1String reverseUuid("FB349B5F-8000-0080-0010-000001110000");
#endif

// This is an old implementation from the Qt example
//static const QLatin1String serviceUuid("e8e10f95-1a70-4b27-9ccf-02010264e9c8");
//#ifdef Q_OS_ANDROID
//static const QLatin1String reverseUuid("c8e96402-0102-cf9c-274b-701a950fe1e8");
//#endif

BluetoothSpp::BluetoothSpp(QObject *parent) : QObject(parent)
{
    // Making connections to monitor when the device is connected or disconnected
    // Maybe need to add a posibility to handle more than one adapters (as in the Qt Bluetooth chat example)
    localDevice = new QBluetoothLocalDevice();

    // Object to discover bluetooth devices
    devicesDiscover = new BluetoothDiscoverDevices();
    connect(this, SIGNAL(connected()), this->devicesDiscover, SLOT(setDeviceConnected()));
    connect(this, SIGNAL(disconnected()), this->devicesDiscover, SLOT(setDeviceDisconnected()));

    // Object to discover available services for a bluetooth device
    servicesDiscoveryAgent = new QBluetoothServiceDiscoveryAgent(this);
    connect(this->servicesDiscoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), this, SLOT(appendServiceDiscovered(const QBluetoothServiceInfo)));
    connect(this->servicesDiscoveryAgent, SIGNAL(finished()), this, SLOT(servicesDiscoveryFinished()));

    // Bluetooth server to accept incoming bluetooth communication
    bluetoothServer = new ChatServer(this);
    connect(bluetoothServer, SIGNAL(clientConnected(QString)), this, SLOT(clientConnected(QString)));
    connect(bluetoothServer, SIGNAL(clientDisconnected(QString)), this, SLOT(clientDisconnected(QString)));
    connect(bluetoothServer, SIGNAL(messageReceived(QString,QString)), this, SLOT(showMessage(QString,QString)));
}

// Methods to enable and disable bluetooth
void BluetoothSpp::enableBluetooth() {
    localDevice->setHostMode(QBluetoothLocalDevice::HostDiscoverable);
    bluetoothServer->startServer(localDevice->address());
}
void BluetoothSpp::disableBluetooth() {
    bluetoothServer->stopServer();
    disconnectFromAllDevices();
}

// Method to get a reference to the Bluetooth discovery object
BluetoothDiscoverDevices *BluetoothSpp::getBluetoothDevicesDiscover() { return this->devicesDiscover; }

// These are the responsible to client connections to the server including the retrieval of the incoming data and sending responses
void BluetoothSpp::clientConnected(const QString &name) { emit connectedToServer(name); }
void BluetoothSpp::clientDisconnected(const QString &name) { emit disconnectedFromServer(name); }
void BluetoothSpp::clientDisconnected()
{
    ChatClient *client = qobject_cast<ChatClient *>(sender());
    if (client) {
        clients.removeOne(client);
        client->deleteLater();
    }

    // Emitting a signal indicating that the client has been disconnected
    emit disconnected();
}
void BluetoothSpp::showMessage(const QString &sender, const QString &message) { emit commandReceived(message); }

// Method to send data to the bluetooth COM port emulator
void BluetoothSpp::sendMessage(const QString &message) { bluetoothServer->sendMessage(message); }

// Method to connect to the currently selected device
void BluetoothSpp::connectToSelectedDevice()
{
    // Discovering services available for a specific device
    discoverServicesForSelectedDevice();

    // The rest of the connection sequence is carried out through the signal / slots
}

// Service discovery for the currently selected device
void BluetoothSpp::discoverServicesForSelectedDevice() {
    discoverServices(this->devicesDiscover->getBluetoothDeviceDiscoveryAgent()->discoveredDevices().at(this->devicesDiscover->getSelectedDeviceIndex()));
}
// Method is used to discover services for a given device
void BluetoothSpp::discoverServices(QBluetoothDeviceInfo device)
{
    // Check if Bluetooth is available on this device
    if (localDevice->isValid()) {

        // Stopping the services discovery agent if it is active
        if (servicesDiscoveryAgent->isActive()) { servicesDiscoveryAgent->stop(); }

        // Need to discover available services to connect to for further use
        services.clear();
        servicesDiscoveryAgent->setRemoteAddress(device.address());  // For iOS probably need to use the UUID since they don't have addresses there.
        #ifdef Q_OS_ANDROID
            if (QtAndroid::androidSdkVersion() >= 23)
                servicesDiscoveryAgent->setUuidFilter(QBluetoothUuid(reverseUuid));
            else
                servicesDiscoveryAgent->setUuidFilter(QBluetoothUuid(serviceUuid));
        #else
            servicesDiscoveryAgent->setUuidFilter(QBluetoothUuid(serviceUuid));
        #endif
        servicesDiscoveryAgent->start(QBluetoothServiceDiscoveryAgent::FullDiscovery);

        // Further operations are carried out through a signal - slot connection
    }
}

void BluetoothSpp::appendServiceDiscovered(const QBluetoothServiceInfo &bluetoothServiceInfo) { services.append(bluetoothServiceInfo); }
void BluetoothSpp::servicesDiscoveryFinished()
{
    // Emitting a signal indicating that the service discovery is complete
    emit servicesDiscovered();

    if (servicesDiscoveryAgent->discoveredServices().count() > 0)
    {
        QBluetoothServiceInfo service = servicesDiscoveryAgent->discoveredServices().at(0);

        ChatClient *client = new ChatClient(this);
        connect(client, SIGNAL(messageReceived(QString,QString)), this, SLOT(showMessage(QString,QString)));
        connect(client, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
        connect(client, SIGNAL(connected(QString)), this, SLOT(connected(QString)));
        connect(this, SIGNAL(sendMessage(QString)), client, SLOT(sendMessage(QString)));
        client->startClient(service);
        clients.append(client);

        // Emitting a signal indcating that a device has been connected
        emit connected();
    }
}

// Method to disconnect from all currently connected devices
void BluetoothSpp::disconnectFromAllDevices()
{
    int i;  // Loop variable

    for (i = 0; i < clients.count(); i++) { clients.at(i)->disconnectClient(); }
    emit disconnected();
}

// Method to determine if any remote Bluetooth devices are connected (as a client or a server)
bool  BluetoothSpp::getRemoteDeviceIsConnected()
{
    if ((clients.count() > 0) || bluetoothServer->getClientsConnected()) { return true; }
                                                                    else { return false; }
}
