#include <BTstack.h>
#include <SPI.h>

/* 
 * EXAMPLE_START(LECentral): LE Central
 *
 * @text Compared with the other examples, the LE Central is
 * a bit more complex. This is because it performs multiple 
 * steps in sequence as it is common with GATT Client APIs.
 *
 * It shows how to first scan for other 
 * devices and then connect to one. When connected, a series of
 * GATT Client operations are performed: first the list of 
 * GATT Services is queried. If a particular service is found,
 * the list of its GATT Characteristics is retrieved and a set 
 * of known Characteristics are cached for later access.
 */

/*
 * @section Characteristic Summary
 * @text As multiple Characteristics need to be found, a custom
 * struct is used to collect all information about it. This allows
 * to defined the list of neccessary characteristics in the 
 * characteristics[] array
 */
/* LISTING_START(LECentralSummary): Characteristic Summary */

// BLE Shield Service V2 incl. used Characteristics
UUID bleShieldServiceV2UUID("B8E06067-62AD-41BA-9231-206AE80AB550");

typedef struct characteristic_summary {
    UUID         uuid;
    const char * name;
    bool         found;
    BLECharacteristic characteristic;
} characteristic_summary_t;

typedef enum characteristicIDs {
    charRX = 0,
    charTX,
    charBaud,
    charBdAddr,
    numCharacteristics  /* last one */
} characteristicIDs_t;

characteristic_summary characteristics[] = {
    { UUID("f897177b-aee8-4767-8ecc-cc694fd5fcee"), "RX"       },
    { UUID("bf45e40a-de2a-4bc8-bba0-e5d6065f1b4b"), "TX"       },
    { UUID("2fbc0f31-726a-4014-b9fe-c8be0652e982"), "Baudrate" },
    { UUID("65c228da-bad1-4f41-b55f-3d177f4e2196"), "BD ADDR"  }
};

/* LISTING_END(LECentralSummary): Characteristic Summary */

// Application state
BLEDevice  myBLEDevice;
BLEService myBLEService;
bool serviceFound;
bool sendCounter = false;

int counter = 0;
char counterString[20];

static timer_source_t heartbeat;

/*
 * @section Setup
 * @text In the setup, various callbacks are registered. After that
 * we start scanning for other devices
 */
/* LISTING_START(LECentralSetup): LE Central Setup */
 void setup(void){
    Serial.begin(9600);
    BTstack.setBLEAdvertisementCallback(advertisementCallback);
    BTstack.setBLEDeviceConnectedCallback(deviceConnectedCallback);
    BTstack.setBLEDeviceDisconnectedCallback(deviceDisconnectedCallback);
    BTstack.setGATTServiceDiscoveredCallback(gattServiceDiscovered);
    BTstack.setGATTCharacteristicDiscoveredCallback(gattCharacteristicDiscovered);
    BTstack.setGATTCharacteristicNotificationCallback(gattCharacteristicNotification);
    BTstack.setGATTCharacteristicReadCallback(gattReadCallback);
    BTstack.setGATTCharacteristicWrittenCallback(gattWrittenCallback);
    BTstack.setGATTCharacteristicSubscribedCallback(gattSubscribedCallback);
    BTstack.setup();
    BTstack.bleStartScanning();
}
/* LISTING_END(LECentralSetup): LE Central Setup */

/*
 * @section Loop
 *
 * @text In the standard Arduino loop() function, BTstack's loop() is called first
 * If we're connected, we send the string "BTstack" plus a counter as fast as possible.
 * As the Bluetooth module might be busy, it's important to check the result of the 
 * writeCharacteristicWithoutResponse() call. If it's not ok, we just try again in the
 * next loop iteration.
 */
/* LISTING_START(LECentralLoop): Loop */
void loop(void){
    BTstack.loop();

    // send counter as fast as possible
    if (sendCounter){
        sprintf(counterString, "BTstack %u\n", counter);
        int result = myBLEDevice.writeCharacteristicWithoutResponse(&characteristics[charTX].characteristic, (uint8_t*) counterString, strlen(counterString) );
        if (result == BLE_PERIPHERAL_OK){
            Serial.print("Wrote without response: ");
            Serial.println(counterString);
            counter++;
        }
    }
}
/* LISTING_END(LECentralLoop): Loop */

/*
 * @section Advertisement Callback
 *
 * @text When an Advertisement is received, we check if it contains 
 * the UUID of the service we're interested in. Only a single service
 * with a 128-bit UUID can be contained in and Advertisement and not
 * all BLE devices provides this. Other options are to match on the
 * reported device name or the BD ADDR prefix.
 *
 * If we found an interesting device, we try to connect to it.
 */
/* LISTING_START(LECentralAdvertisementCallback): Advertisement Callback */
void advertisementCallback(BLEAdvertisement *bleAdvertisement) {
    Serial.print("Device discovered: ");
    Serial.print(bleAdvertisement->getBdAddr()->getAddressString());
    Serial.print(", RSSI: ");
    Serial.println(bleAdvertisement->getRssi());
    if (bleAdvertisement->containsService(&bleShieldServiceV2UUID)) {
        Serial.println("\nBLE ShieldService V2 found!\n");
        BTstack.bleStopScanning();
        BTstack.bleConnect(bleAdvertisement, 10000);  // 10 s
    }
}
/* LISTING_END(LECentralAdvertisementCallback): Advertisement Callback */

/*
 * @section Device Connected Callback
 *
 * @text At the end of bleConnect(), the device connected callback is callec.
 * The status argument tells if the connection timed out, or if the connection
 * was established successfully.
 *
 * On a successful connection, a GATT Service Discovery is started.
 */
/* LISTING_START(LECentralDeviceConnectedCallback): Device Connected Callback */
void deviceConnectedCallback(BLEStatus status, BLEDevice *device) {
    switch (status){
        case BLE_STATUS_OK:
            Serial.println("Device connected!");
            myBLEDevice = *device;
            counter = 0;
            myBLEDevice.discoverGATTServices();
            break;
        case BLE_STATUS_CONNECTION_TIMEOUT:
            Serial.println("Error while Connecting the Peripheral");
            BTstack.bleStartScanning();
            break;
        default:
            break;
    }
}
/* LISTING_END(LECentralDeviceConnectedCallback): Device Connected Callback */

/*
 * @section Device Disconnected Callback
 *
 * @text If the connection to a device breaks, the device disconnected callback
 * is called. Here, we start scanning for new devices again.
 */
/* LISTING_START(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */
void deviceDisconnectedCallback(BLEDevice * device){
    Serial.println("Disconnected, starting over..");
    sendCounter = false;
    BTstack.bleStartScanning();
}
/* LISTING_END(LECentralDeviceDisconnectedCallback): Device Disconnected Callback */

/*
 * @section Service Discovered Callback
 *
 * @text The service discovered callback is called for each service and after the 
 * service discovery is complete. The status argument is provided for this.
 *
 * The main information about a discovered Service is its UUID.
 * If we find our service, we store the reference to this service.
 * This allows to discover the Characteristics for our service after 
 * the service discovery is complete.
 */
/* LISTING_START(LECentralServiceDiscoveredCallback): Service Discovered Callback */
void gattServiceDiscovered(BLEStatus status, BLEDevice *device, BLEService *bleService) {
    switch(status){
        case BLE_STATUS_OK:
            Serial.print("Service Discovered: :");
            Serial.println(bleService->getUUID()->getUuidString());
            if (bleService->matches(&bleShieldServiceV2UUID)) {
                serviceFound = true;
                Serial.println("Our service located!");
                myBLEService = *bleService;
            }
            break;
        case BLE_STATUS_DONE:
            Serial.println("Service discovery finished");
            if (serviceFound) {
                device->discoverCharacteristicsForService(&myBLEService);
            }
            break;
        default:
            Serial.println("Service discovery error");
            break;
    }
}
/* LISTING_END(LECentralServiceDiscoveredCallback): Service Discovered Callback */

/*
 * @section Characteristic Discovered Callback
 *
 * @text Similar to the Service Discovered callback, the Characteristic Discovered
 * callback is called for each Characteristic found and after the discovery is complete.
 * 
 * The main information is again its UUID. If we find a Characteristic that we're 
 * interested in, it's name is printed and a reference stored for later.
 * 
 * On discovery complete, we subscribe to a particular Characteristic to receive 
 * Characteristic Value updates in the Notificaation Callback.
 */
/* LISTING_START(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */
void gattCharacteristicDiscovered(BLEStatus status, BLEDevice *device, BLECharacteristic *characteristic) {
    switch(status){
        case BLE_STATUS_OK:
            Serial.print("Characteristic Discovered: ");
            Serial.print(characteristic->getUUID()->getUuidString());
            Serial.print(", handle 0x");
            Serial.println(characteristic->getCharacteristic()->value_handle, HEX);
            int i;
            for (i=0;i<numCharacteristics;i++){
                if (characteristic->matches(&characteristics[i].uuid)){
                    Serial.print("Characteristic found: ");
                    Serial.println(characteristics[i].name);
                    characteristics[i].found = 1;
                    characteristics[i].characteristic = *characteristic;
                    break;
                }
            }
            break;
        case BLE_STATUS_DONE:
            Serial.print("Characteristic discovery finished, status ");
            Serial.println(status, HEX);
            if (characteristics[charRX].found) {
                device->subscribeForNotifications(&characteristics[charRX].characteristic);
            }
            break;
        default:
            Serial.println("Characteristics discovery error");
            break;
    }
}
/* LISTING_END(LECentralCharacteristicDiscoveredCallback): Characteristic Discovered Callback */

/*
 * @section Subscribed Callback
 *
 * @text After the subcribe operation is complete, we get notified if it was 
 * successful. In this example, we read the Characteristic that contains the 
 * BD ADDR of the other device. This isn't strictly neccessary as we already
 * know the device address from the Advertisement, but it's a common pattern
 * with iOS as the device address is hidden from applications.
 */
/* LISTING_START(LECentralSubscribedCallback): Subscribed Callback */
void gattSubscribedCallback(BLEStatus status, BLEDevice * device){
    device->readCharacteristic(&characteristics[charBdAddr].characteristic);
}
/* LISTING_END(LECentralSubscribedCallback): Subscribed Callback */

/*
 * @section Read Callback
 *
 * @text The Read callback is called with the result from a read operation.
 * Here, we write to the TX Characteristic next.
 */
/* LISTING_START(LECentralReadCallback): Read Callback */
void gattReadCallback(BLEStatus status, BLEDevice *device, uint8_t *value, uint16_t length) {
    Serial.print("Read callback: ");
    Serial.println((const char *)value);
    device->writeCharacteristic(&characteristics[charTX].characteristic, (uint8_t*) "Hello!", 6);
}
/* LISTING_END(LECentralReadCallback): Read Callback */

/*
 * @section Written Callback
 *
 * @text After the write operation is complete, the Written Callback is callbed with
 * the result in the status argument. As we're done with the initial setup of the remote
 * device, we set the flag to write the test string as fast as possible.
 */
/* LISTING_START(LECentralWrittenCallback): Written Callback */
void gattWrittenCallback(BLEStatus status, BLEDevice *device){
    sendCounter = true;
}
/* LISTING_END(LECentralWrittenCallback): Written Callback */

/*
 * @section Notification Callback
 *
 * @text Notifictions for Characteristic Value Updates are delivered via the 
 * Notification Callback. When more than one Characteristic is subscribed,
 * the value handle can be used to distinguish between them. The 
 * BLECharacteristic.isValueHandle(int handle) allows to test if a value handle
 * belongs to a particular Characteristic.
 */
/* LISTING_START(LECentralNotificationCallback): Notification Callback */
void gattCharacteristicNotification(BLEDevice *device, uint16_t value_handle, uint8_t *value, uint16_t length) {
    Serial.print("Notification: ");
    Serial.println((const char *)value);
}
/* LISTING_END(LECentralNotificationCallback): Notification Callback */

