/**
  * Post constructor initialisation method.
  * After *MUCH* pain, it's noted that the BLE stack can't be brought up in a
  * static context, so we bring it up here rather than in the constructor.
  * n.b. This method *must* be called in main() or later, not before.
  *
  * Example:
  * @code
  * uBit.init();
  * @endcode
  */
void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, bool enableBonding)
{
	ManagedString BLEName("BBC micro:bit");

	this->deviceName = deviceName;

    // Start the BLE stack.
    ble = new BLEDevice();
    ble->init();


    // automatically restart advertising after a device disconnects.
    ble->onDisconnection(bleDisconnectionCallback);
    ble->onConnection(bleConnectionCallback);


#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES)
	// Configure for private addresses, so kids' behaviour can't be easily tracked.
	ble->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0});
#endif

    // Setup our security requirements.
    ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback);
    ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
    ble->securityManager().init(enableBonding, MICROBIT_BLE_REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY);

    // If we're in pairing mode, review the size of the bond table.
    if (enableBonding)
    {
        // TODO: It would be much better to implement some sort of LRU/NFU policy here,
        // but this isn't currently supported in mbed, so we'd need to layer break...
        int bonds = getBondCount();

        // If we're full, empty the bond table.
        if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS)
            ble->securityManager().purgeAllBondingState();
    }

#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    // Configure a whitelist to filter all connection requetss from unbonded devices. 
    // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks.
    BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS];
    Gap::Whitelist_t whitelist;
    whitelist.addresses = bondedAddresses;
    whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS;

    ble->securityManager().getAddressesFromBondTable(whitelist);
    ble->gap().setWhitelist(whitelist);

    ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST);
    ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS);
#endif    

    // Configure the radio at our default power level
    setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER); 

    // Bring up any configured auxiliary services.
#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE)
    new MicroBitDFUService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE)
    DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE)
    new MicroBitEventService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE)
    new MicroBitLEDService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE)
    new MicroBitAccelerometerService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE)
    new MicroBitMagnetometerService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE)
    new MicroBitButtonService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE)
    new MicroBitIOPinService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE)
    new MicroBitTemperatureService(*ble);
#endif

    // Configure for high speed mode where possible.
    Gap::ConnectionParams_t fast;
    ble->getPreferredConnectionParams(&fast);
    fast.minConnectionInterval = 8; // 10 ms
    fast.maxConnectionInterval = 16; // 20 ms
    fast.slaveLatency = 0;
    ble->setPreferredConnectionParams(&fast);

    // Setup advertising.
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
#else
    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
#endif

    ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
    ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble->setAdvertisingInterval(200);

#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0)
    ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT);
#endif

    // If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices...
    // This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable.
    // If whiltelisting is disabled, then we always advertise.
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    if (whitelist.size > 0)
#endif        
        ble->startAdvertising();
}
/**
  * Post constructor initialisation method as the BLE stack cannot be brought
  * up in a static context.
  *
  * @param deviceName The name used when advertising
  * @param serialNumber The serial number exposed by the device information service
  * @param messageBus An instance of an EventModel, used during pairing.
  * @param enableBonding If true, the security manager enabled bonding.
  *
  * @code
  * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true);
  * @endcode
  */
void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding)
{
	ManagedString BLEName("BBC micro:bit");
	this->deviceName = deviceName;

#if !(CONFIG_ENABLED(MICROBIT_BLE_WHITELIST))
	ManagedString namePrefix(" [");
	ManagedString namePostfix("]");
	BLEName = BLEName + namePrefix + deviceName + namePostfix;
#endif

    // Start the BLE stack.
#if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD)
    btle_set_gatt_table_size(MICROBIT_SD_GATT_TABLE_SIZE);
#endif

    ble = new BLEDevice();
    ble->init();

    // automatically restart advertising after a device disconnects.
    ble->gap().onDisconnection(bleDisconnectionCallback);
    ble->gattServer().onSysAttrMissing(bleSysAttrMissingCallback);

    // generate an event when a Bluetooth connection is established
    ble->gap().onConnection(bleConnectionCallback);

    // Configure the stack to hold onto the CPU during critical timing events.
    // mbed-classic performs __disable_irq() calls in its timers that can cause
    // MIC failures on secure BLE channels...
    ble_common_opt_radio_cpu_mutex_t opt;
    opt.enable = 1;
    sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);

#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES)
	// Configure for private addresses, so kids' behaviour can't be easily tracked.
	ble->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0});
#endif

    // Setup our security requirements.
    ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback);
    ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
    ble->securityManager().init(enableBonding, (SecurityManager::MICROBIT_BLE_SECURITY_LEVEL == SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM), SecurityManager::IO_CAPS_DISPLAY_ONLY);

    if (enableBonding)
    {
        // If we're in pairing mode, review the size of the bond table.
        int bonds = getBondCount();

        // TODO: It would be much better to implement some sort of LRU/NFU policy here,
        // but this isn't currently supported in mbed, so we'd need to layer break...

        // If we're full, empty the bond table.
        if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS)
            ble->securityManager().purgeAllBondingState();
    }

#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    // Configure a whitelist to filter all connection requetss from unbonded devices.
    // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks.
    BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS];
    Gap::Whitelist_t whitelist;
    whitelist.addresses = bondedAddresses;
    whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS;

    ble->securityManager().getAddressesFromBondTable(whitelist);

    ble->gap().setWhitelist(whitelist);
    ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST);
    ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS);
#endif

    // Configure the radio at our default power level
    setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER);

    // Bring up core BLE services.
#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE)
    new MicroBitDFUService(*ble);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_DEVICE_INFORMATION_SERVICE)
    DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
#endif

#if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE)
    new MicroBitEventService(*ble, messageBus);
#else
    (void)messageBus;
#endif


    // Configure for high speed mode where possible.
    Gap::ConnectionParams_t fast;
    ble->getPreferredConnectionParams(&fast);
    fast.minConnectionInterval = 8; // 10 ms
    fast.maxConnectionInterval = 16; // 20 ms
    fast.slaveLatency = 0;
    ble->setPreferredConnectionParams(&fast);

    // Setup advertising.
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
#else
    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
#endif

    ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
    ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble->setAdvertisingInterval(200);

#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0)
    ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT);
#endif

    // If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices...
    // This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable.
    // If whiltelisting is disabled, then we always advertise.
#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
    if (whitelist.size > 0)
#endif
        ble->startAdvertising();
}