/**
  * Class definition for the custom MicroBit LED Service.
  * Provides a BLE service to remotely read and write the state of the LED display.
  */
  
#include "MicroBit.h"
#include "ble/UUID.h"

#include "MicroBitLEDService.h"

/**
  * Constructor. 
  * Create a representation of the LEDService
  * @param _ble The instance of a BLE device that we're running on.
  */
MicroBitLEDService::MicroBitLEDService(BLEDevice &_ble) : 
        ble(_ble), 
        matrixCharacteristic(MicroBitLEDServiceMatrixUUID, (uint8_t *)&matrixCharacteristicBuffer, 0, sizeof(matrixCharacteristicBuffer), 
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ)
{
    // Create the data structures that represent each of our characteristics in Soft Device.
    GattCharacteristic  textCharacteristic(MicroBitLEDServiceTextUUID, (uint8_t *)textCharacteristicBuffer, 0, MICROBIT_BLE_MAXIMUM_SCROLLTEXT, 
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE);

    GattCharacteristic  scrollingSpeedCharacteristic(MicroBitLEDServiceScrollingSpeedUUID, (uint8_t *)&scrollingSpeedCharacteristicBuffer, 0, 
    sizeof(scrollingSpeedCharacteristicBuffer), GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);

    // Initialise our characteristic values.
    memclr(matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer));
    textCharacteristicBuffer[0] = 0;
    scrollingSpeedCharacteristicBuffer = MICROBIT_DEFAULT_SCROLL_SPEED;
    
    matrixCharacteristic.setReadAuthorizationCallback(this, &MicroBitLEDService::onDataRead);
            
    // Set default security requirements
    matrixCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    textCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);
    scrollingSpeedCharacteristic.requireSecurity(SecurityManager::MICROBIT_BLE_SECURITY_LEVEL);

    GattCharacteristic *characteristics[] = {&matrixCharacteristic, &textCharacteristic, &scrollingSpeedCharacteristic};
    GattService         service(MicroBitLEDServiceUUID, characteristics, sizeof(characteristics) / sizeof(GattCharacteristic *));

    ble.addService(service);

    matrixCharacteristicHandle = matrixCharacteristic.getValueHandle();
    textCharacteristicHandle = textCharacteristic.getValueHandle();
    scrollingSpeedCharacteristicHandle = scrollingSpeedCharacteristic.getValueHandle();

    ble.gattServer().write(scrollingSpeedCharacteristicHandle, (const uint8_t *)&scrollingSpeedCharacteristicBuffer, sizeof(scrollingSpeedCharacteristicBuffer));
    ble.gattServer().write(matrixCharacteristicHandle, (const uint8_t *)&matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer));

    ble.onDataWritten(this, &MicroBitLEDService::onDataWritten);
}


/**
  * Callback. Invoked when any of our attributes are written via BLE.
  */
void MicroBitLEDService::onDataWritten(const GattWriteCallbackParams *params)
{   
    uint8_t *data = (uint8_t *)params->data;

    if (params->handle == matrixCharacteristicHandle && params->len > 0 && params->len < 6)
    {
        for (int y=0; y<params->len; y++)        
            for (int x=0; x<5; x++)        
                uBit.display.image.setPixelValue(x, y, (data[y] & (0x01 << (4-x))) ? 255 : 0);
    }

    else if (params->handle == textCharacteristicHandle) 
    {
        // Create a ManagedString representation from the UTF8 data.
        // We do this explicitly to control the length (in case the string is not NULL terminated!)
        ManagedString s((char *)params->data, params->len);

        // Start the string scrolling and we're done.
        uBit.display.scrollAsync(s, (int) scrollingSpeedCharacteristicBuffer);
    }

    else if (params->handle == scrollingSpeedCharacteristicHandle && params->len >= sizeof(scrollingSpeedCharacteristicBuffer)) 
    {
        // Read the speed requested, and store it locally.
        // We use this as the speed for all scroll operations subsquently initiated from BLE.
        scrollingSpeedCharacteristicBuffer = *((uint16_t *)params->data);
    }
}

/**
  * Callback. Invoked when any of our attributes are read via BLE.
  */
void MicroBitLEDService::onDataRead(GattReadAuthCallbackParams *params)
{
    if (params->handle == matrixCharacteristicHandle)
    {
        for (int y=0; y<5; y++)        
        {
            matrixCharacteristicBuffer[y] = 0;

            for (int x=0; x<5; x++)        
            {
                if (uBit.display.image.getPixelValue(x, y))
                    matrixCharacteristicBuffer[y] |= 0x01 << (4-x);
            }
        }

        ble.gattServer().write(matrixCharacteristicHandle, (const uint8_t *)&matrixCharacteristicBuffer, sizeof(matrixCharacteristicBuffer));
    }
}


const uint8_t  MicroBitLEDServiceUUID[] = {
    0xe9,0x5d,0xd9,0x1d,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8
};

const uint8_t  MicroBitLEDServiceMatrixUUID[] = {
    0xe9,0x5d,0x7b,0x77,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8
};

const uint8_t  MicroBitLEDServiceTextUUID[] = {
    0xe9,0x5d,0x93,0xee,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8
};

const uint8_t  MicroBitLEDServiceScrollingSpeedUUID[] = {
    0xe9,0x5d,0x0d,0x2d,0x25,0x1d,0x47,0x0a,0xa0,0x62,0xfa,0x19,0x22,0xdf,0xa9,0xa8
};


