/**
  * Copies characters into the buffer used for Transmitting to the central device.
  *
  * @param buf a buffer containing length number of bytes.
  * @param length the size of the buffer.
  * @param mode the selected mode, one of: ASYNC, SYNC_SPINWAIT, SYNC_SLEEP. Each mode
  *        gives a different behaviour:
  *
  *            ASYNC - Will copy as many characters as it can into the buffer for transmission,
  *                    and return control to the user.
  *
  *            SYNC_SPINWAIT - will return MICROBIT_INVALID_PARAMETER
  *
  *            SYNC_SLEEP - Will perform a cooperative blocking wait until all
  *                         given characters have been received by the connected
  *                         device.
  *
  * @return the number of characters written, or MICROBIT_NOT_SUPPORTED if there is
  *         no connected device, or the connected device has not enabled indications.
  */
int MicroBitUARTService::send(const uint8_t *buf, int length, MicroBitSerialMode mode)
{
    if(length < 1 || mode == SYNC_SPINWAIT)
        return MICROBIT_INVALID_PARAMETER;

    bool updatesEnabled = false;

    ble.gattServer().areUpdatesEnabled(*txCharacteristic, &updatesEnabled);

    if(!ble.getGapState().connected && !updatesEnabled)
        return MICROBIT_NOT_SUPPORTED;

    int bytesWritten = 0;

    while(bytesWritten < length && ble.getGapState().connected && updatesEnabled)
    {
        for(int bufferIterator = bytesWritten; bufferIterator < length; bufferIterator++)
        {
            int nextHead = (txBufferHead + 1) % txBufferSize;

            if(nextHead != txBufferTail)
            {
                txBuffer[txBufferHead] = buf[bufferIterator];

                txBufferHead = nextHead;

                bytesWritten++;
            }
        }

        int size = txBufferedSize();

        uint8_t temp[size];

        memclr(&temp, size);

        circularCopy(txBuffer, txBufferSize, temp, txBufferTail, txBufferHead);


        if(mode == SYNC_SLEEP)
            fiber_wake_on_event(MICROBIT_ID_NOTIFY, MICROBIT_UART_S_EVT_TX_EMPTY);

        ble.gattServer().write(txCharacteristic->getValueAttribute().getHandle(), temp, size);

        if(mode == SYNC_SLEEP)
            schedule();
        else
            break;

        ble.gattServer().areUpdatesEnabled(*txCharacteristic, &updatesEnabled);
    }

    return bytesWritten;
}
/**
  * An internal method to configure an interrupt on tx buffer and also
  * a best effort copy operation to move bytes from a user buffer to our txBuff
  *
  * @param string a pointer to the first character of the users' buffer.
  *
  * @param len the length of the string, and ultimately the maximum number of bytes
  *        that will be copied dependent on the state of txBuff
  *
  * @param mode determines whether to configure the current fiber context or not. If
  *             The mode is SYNC_SPINWAIT, the context will not be configured, otherwise
  *             no context will be configured.
  *
  * @return the number of bytes copied into the buffer.
  */
int MicroBitSerial::setTxInterrupt(uint8_t *string, int len, MicroBitSerialMode mode)
{
    int copiedBytes = 0;

    for(copiedBytes = 0; copiedBytes < len; copiedBytes++)
    {
        uint16_t nextHead = (txBuffHead + 1) % txBuffSize;
        if(nextHead != txBuffTail)
        {
            this->txBuff[txBuffHead] = string[copiedBytes];
            txBuffHead = nextHead;
        }
        else
            break;
    }

    if(mode != SYNC_SPINWAIT)
        fiber_wake_on_event(MICROBIT_ID_NOTIFY, MICROBIT_SERIAL_EVT_TX_EMPTY);

    //set the TX interrupt
    attach(this, &MicroBitSerial::dataWritten, Serial::TxIrq);

    return copiedBytes;
}