// 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 distributed by the Qt Company under the BSD License were used to write this class
// http://doc.qt.io/qt-5/qtbluetooth-le-overview.html
// http://doc.qt.io/qt-5/qtbluetooth-lowenergyscanner-example.html
//
// 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.



// Class is used to connect to a remote device (including SensorTag) using the Bluetooth low energy protocol
#include "bluetoothlowenergy.h"
#include <QLowEnergyService>
#include <QList>

BluetoothLowEnergy::BluetoothLowEnergy(QObject *parent) : QObject(parent)
{
    // Initializing the low energy Bluetooth controller
    controller = 0;

    // Setting the default value for the random address value
    this->isRandomAddress = false;

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

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

// Method returns a pointer to the controller
QLowEnergyController *BluetoothLowEnergy::getController() { return this->controller; }

bool BluetoothLowEnergy::getIsRandomAddress() const { return this->isRandomAddress; }
void BluetoothLowEnergy::setIsRandomAddress(bool newValue) { this->isRandomAddress = newValue; }

void BluetoothLowEnergy::disconnectFromDevice() { controller->disconnectFromDevice(); }

// Service discovery for the currently selected device
void BluetoothLowEnergy::discoverServicesForSelectedDevice() {
    discoverServices(this->devicesDiscover->getBluetoothDeviceDiscoveryAgent()->discoveredDevices().at(this->devicesDiscover->getSelectedDeviceIndex()));
}
// Method to discover bluetooth services
void BluetoothLowEnergy::discoverServices(QBluetoothDeviceInfo device)
{
    // If a new device is conntected, the Low Energy Bluetooth controller is to be re-initialized
    if (controller && (previousDevice != device))
    {
        controller->disconnectFromDevice();
        delete controller;
        controller = 0;
    }

    // Need to discover available services to connect to for further use
    services.clear();
    if (!controller)
    {
        controller = new QLowEnergyController(device);
        connect(controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), this, SLOT(addLowEnergyService(QBluetoothUuid)));
        connect(controller, SIGNAL(connected()), this, SLOT(deviceConnected()));
        connect(controller, QOverload<QLowEnergyController::Error>::of(&QLowEnergyController::error), this, &BluetoothLowEnergy::errorReceived);
        connect(controller, SIGNAL(disconnected()), this, SLOT(deviceDisconnected()));
        connect(controller, SIGNAL(discoveryFinished()), this, SLOT(serviceScanDone()));
    }

    if (this->isRandomAddress)
        controller->setRemoteAddressType(QLowEnergyController::RandomAddress);
    else
        controller->setRemoteAddressType(QLowEnergyController::PublicAddress);
    controller->connectToDevice();
    this->previousDevice = device;
    // Further operations are carried out through a signal - slot connection

}

void BluetoothLowEnergy::addLowEnergyService(const QBluetoothUuid &serviceUuid)
{
    QLowEnergyService *service = controller->createServiceObject(serviceUuid);
    if (!service) {
        qWarning() << "Cannot create Bluetooth LE service for UUID";
        return;
    }
    services.append(service);
    qDebug() << "Discovered service: " << service->serviceName();
}

void BluetoothLowEnergy::deviceConnected() { controller->discoverServices(); }
void BluetoothLowEnergy::deviceDisconnected() { emit disconnected(); }
void BluetoothLowEnergy::errorReceived(QLowEnergyController::Error) { qWarning() << "Error: " << controller->errorString(); }

void BluetoothLowEnergy::serviceScanDone()
{
    emit servicesDiscovered();
}

// This method will locate and connect to the service which starts with the string supplied
void BluetoothLowEnergy::connectToServiceWithUuidContaining(QString uuidContains)
{
    int i;   // Loop variable


    // Finding the service containing the substring in the UUID identifier
    for (i = 0; i < services.count(); i++)
    {
        if (services.at(i)->serviceUuid().toString().toUpper().contains(uuidContains.toUpper())) { serviceToConnectTo = services.at(i); break; }
    }

    // If the service is unavailable then return
    if (!serviceToConnectTo) { return; }

    // Discovering the service details if necessary
    if (serviceToConnectTo->state() == QLowEnergyService::DiscoveryRequired)
    {
        connect(serviceToConnectTo, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), this, SLOT(serviceDetailsDiscovered(QLowEnergyService::ServiceState)));
        serviceToConnectTo->discoverDetails();
    }
}

// Slot is triggered when the service details have been discovered
void BluetoothLowEnergy::serviceDetailsDiscovered(QLowEnergyService::ServiceState newState)
{
    serviceToConnectTo = qobject_cast<QLowEnergyService *>(sender());
    if (serviceToConnectTo->characteristics().count() > 0) { emit characteristicsDiscovered(serviceToConnectTo); }
}
