/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include "os/mynewt.h"
#include "console/console.h"
#include "config/config.h"
#include "nimble/ble.h"
#include "host/ble_hs.h"
#include "host/util/util.h"

#include"patterns.h"

static uint8_t id_addr_type;

static void start_legacy_duration(uint8_t pattern, bool configure);
static void start_ext_max_events(uint8_t pattern, bool configure);

static int
start_ext_max_events_gap_event(struct ble_gap_event *event, void *arg)
{
    static uint8_t pattern = 1;

    switch (event->type) {
    case BLE_GAP_EVENT_ADV_COMPLETE:
        break;
    default:
        assert(0);
        return 0;
    }

    assert(event->adv_complete.instance == 4);
    assert(event->adv_complete.reason == BLE_HS_ETIMEOUT);
    assert(event->adv_complete.num_ext_adv_events == 10);

    console_printf("instance %u terminated\n", event->adv_complete.instance);

    pattern++;

    start_ext_max_events(pattern, false);

    return 0;
}

/* Starts advertising instance with 100 max events and changing adv data pattern
 * and SID.
 */
static void
start_ext_max_events(uint8_t pattern, bool configure)
{
    struct ble_gap_ext_adv_params params;
    static uint8_t adv_data[600];
    struct os_mbuf *data;
    uint8_t instance = 4;
    ble_addr_t addr;
    int events = 10;
    int rc;

    if (configure) {
        /* use defaults for non-set params */
        memset (&params, 0, sizeof(params));

        /* advertise using random addr */
        params.own_addr_type = BLE_OWN_ADDR_RANDOM;

        params.primary_phy = BLE_HCI_LE_PHY_1M;
        params.secondary_phy = BLE_HCI_LE_PHY_1M;
        params.tx_power = 127;
        params.sid = 4;

        /* allow larger interval, 400 * 0.625ms with 100 events will give up to
         * ~2.5 seconds for instance
         */
        params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
        params.itvl_max = 400;

        /* configure instance 4 */
        rc = ble_gap_ext_adv_configure(instance, &params, NULL,
                                        start_ext_max_events_gap_event, NULL);
        assert (rc == 0);

        /* set random (NRPA) address for instance */
        rc = ble_hs_id_gen_rnd(1, &addr);
        assert (rc == 0);

        rc = ble_gap_ext_adv_set_addr(instance, &addr );
        assert (rc == 0);
    }

    /* in this case both advertising data and scan response is allowed, but
     * both are limited to 31 bytes each
     */

    /* get mbuf for adv data */
    data = os_msys_get_pkthdr(600, 0);
    assert(data);

    memset(adv_data, pattern, sizeof(adv_data));

    /* fill mbuf with adv data */
    rc = os_mbuf_append(data, adv_data, 600);
    assert(rc == 0);

   rc = ble_gap_ext_adv_set_data(instance, data);
   assert (rc == 0);

   /* start advertising */
    rc = ble_gap_ext_adv_start(instance, 0, events);
    assert (rc == 0);

    console_printf("instance %u started (PDUs with max events %d)\n",
                                                            instance, events);
}

static int
start_legacy_duration_gap_event(struct ble_gap_event *event, void *arg)
{
    static uint8_t pattern = 1;

    switch (event->type) {
    case BLE_GAP_EVENT_ADV_COMPLETE:
        break;
    default:
        assert(0);
        return 0;
    }

    assert(event->adv_complete.instance == 3);
    assert(event->adv_complete.reason == BLE_HS_ETIMEOUT);

    console_printf("instance %u terminated\n", event->adv_complete.instance);

    pattern++;

    start_legacy_duration(pattern, false);

    return 0;
}

/* Starts advertising instance with 5sec timeout and changing adv data pattern
 * and SID.
 */
static void
start_legacy_duration(uint8_t pattern, bool configure)
{
    struct ble_gap_ext_adv_params params;
    uint8_t adv_data[31];
    struct os_mbuf *data;
    uint8_t instance = 3;
    ble_addr_t addr;
    int duration = 500; /* 5seconds, 10ms units */
    int rc;

    if (configure) {
        /* use defaults for non-set params */
        memset (&params, 0, sizeof(params));

        /* enable advertising using legacy PDUs */
        params.legacy_pdu = 1;

        /* advertise using random addr */
        params.own_addr_type = BLE_OWN_ADDR_RANDOM;

        params.primary_phy = BLE_HCI_LE_PHY_1M;
        params.secondary_phy = BLE_HCI_LE_PHY_1M;
        params.tx_power = 127;
        params.sid = 3;

        /* configure instance 3 */
        rc = ble_gap_ext_adv_configure(instance, &params, NULL,
                                        start_legacy_duration_gap_event, NULL);
        assert (rc == 0);

        /* set random (NRPA) address for instance */
        rc = ble_hs_id_gen_rnd(1, &addr);
        assert (rc == 0);

        rc = ble_gap_ext_adv_set_addr(instance, &addr );
        assert (rc == 0);
    }

    /* in this case both advertising data and scan response is allowed, but
     * both are limited to 31 bytes each
     */

    /* get mbuf for adv data */
    data = os_msys_get_pkthdr(31, 0);
    assert(data);

    memset(adv_data, pattern, sizeof(adv_data));

    /* fill mbuf with adv data */
    rc = os_mbuf_append(data, adv_data, 31);
    assert(rc == 0);

   rc = ble_gap_ext_adv_set_data(instance, data);
   assert (rc == 0);

   /* start advertising */
    rc = ble_gap_ext_adv_start(instance, duration, 0);
    assert (rc == 0);

    console_printf("instance %u started (legacy PDUs with duration %d)\n",
                                                            instance, duration);
}

/* this is simple non-connectable scannable instance using legacy PUDs that
 * runs forever
 */
static void
start_scannable_legacy_ext(void)
{
    struct ble_gap_ext_adv_params params;
    struct os_mbuf *data;
    uint8_t instance = 2;
    ble_addr_t addr;
    int rc;

    /* use defaults for non-set params */
    memset (&params, 0, sizeof(params));

    /* enable scannable advertising using legacy PDUs */
    params.scannable = 1;
    params.legacy_pdu = 1;

    /* advertise using random addr */
    params.own_addr_type = BLE_OWN_ADDR_RANDOM;

    params.primary_phy = BLE_HCI_LE_PHY_1M;
    params.secondary_phy = BLE_HCI_LE_PHY_1M;
    params.tx_power = 127;
    params.sid = 2;

    /* configure instance 2 */
    rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
    assert (rc == 0);

    /* set random (NRPA) address for instance */
    rc = ble_hs_id_gen_rnd(1, &addr);
    assert (rc == 0);

    rc = ble_gap_ext_adv_set_addr(instance, &addr );
    assert (rc == 0);

    /* in this case both advertising data and scan response is allowed, but
     * both are limited to 31 bytes each
     */

    /* get mbuf for adv data */
    data = os_msys_get_pkthdr(31, 0);
    assert(data);

    /* fill mbuf with adv data */
    rc = os_mbuf_append(data, ext_adv_pattern_1, 31);
    assert(rc == 0);

   rc = ble_gap_ext_adv_set_data(instance, data);
   assert (rc == 0);

    /* get mbuf for scan rsp data */
    data = os_msys_get_pkthdr(31, 0);
    assert(data);

    /* fill mbuf with scan rsp data */
    rc = os_mbuf_append(data, ext_adv_pattern_1 + 31, 31);
    assert(rc == 0);

   rc = ble_gap_ext_adv_rsp_set_data(instance, data);
   assert (rc == 0);

   /* start advertising */
    rc = ble_gap_ext_adv_start(instance, 0, 0);
    assert (rc == 0);

    console_printf("instance %u started (scannable legacy PDUs)\n", instance);
}

static int
scannable_ext_gap_event(struct ble_gap_event *event, void *arg)
{
    switch (event->type) {
    default:
        break;
    }

    return 0;
}

/* this is simple scannable instance that runs forever
 * TODO Get scan request notifications.
 */
static void
start_scannable_ext(void)
{
    struct ble_gap_ext_adv_params params;
    struct os_mbuf *data;
    uint8_t instance = 1;
    ble_addr_t addr;
    int rc;

    /* use defaults for non-set params */
    memset (&params, 0, sizeof(params));

    /* enable scannable advertising */
    params.scannable = 1;

    /* enable scan request notification */
    params.scan_req_notif = 1;

    /* advertise using random addr */
    params.own_addr_type = BLE_OWN_ADDR_RANDOM;

    params.primary_phy = BLE_HCI_LE_PHY_1M;
    params.secondary_phy = BLE_HCI_LE_PHY_1M;
    params.tx_power = 127;
    params.sid = 1;

    /* configure instance 1 */
    rc = ble_gap_ext_adv_configure(instance, &params, NULL,
                                   scannable_ext_gap_event, NULL);
    assert (rc == 0);

    /* set random (NRPA) address for instance */
    rc = ble_hs_id_gen_rnd(1, &addr);
    assert (rc == 0);

    rc = ble_gap_ext_adv_set_addr(instance, &addr );
    assert (rc == 0);

    /* in this case only scan response is allowed */

    /* get mbuf for scan rsp data */
    data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
    assert(data);

    /* fill mbuf with scan rsp data */
    rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
    assert(rc == 0);

   rc = ble_gap_ext_adv_rsp_set_data(instance, data);
   assert (rc == 0);

   /* start advertising */
    rc = ble_gap_ext_adv_start(instance, 0, 0);
    assert (rc == 0);

    console_printf("instance %u started (scannable)\n", instance);
}

/* this is simple non-connectable instance that runs forever */
static void
start_non_connectable_ext(void)
{
    struct ble_gap_ext_adv_params params;
    struct os_mbuf *data;
    uint8_t instance = 0;
    int rc;

    /* use defaults for non-set params */
    memset (&params, 0, sizeof(params));

    /* advertise using ID addr */
    params.own_addr_type = id_addr_type;

    params.primary_phy = BLE_HCI_LE_PHY_1M;
    params.secondary_phy = BLE_HCI_LE_PHY_1M;
    params.tx_power = 127;
    params.sid = 0;

    /* configure instance 0 */
    rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
    assert (rc == 0);

    /* in this case only advertisign data is allowed */

    /* get mbuf for adv data */
    data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
    assert(data);

    /* fill mbuf with adv data */
    rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
    assert(rc == 0);

   rc = ble_gap_ext_adv_set_data(instance, data);
   assert (rc == 0);

   /* start advertising */
    rc = ble_gap_ext_adv_start(instance, 0, 0);
    assert (rc == 0);

    console_printf("instance %u started (non-con non-scan)\n", instance);
}

static void start_periodic(void)
{
    struct ble_gap_periodic_adv_params pparams;
    struct ble_gap_ext_adv_params params;
    struct ble_hs_adv_fields adv_fields;
    struct os_mbuf *data;
    uint8_t instance = 5;
    ble_addr_t addr;
    int rc;

    /* For periodic we use instance with non-connectable advertising */
    memset (&params, 0, sizeof(params));

    /* advertise using random addr */
    params.own_addr_type = BLE_OWN_ADDR_RANDOM;

    params.primary_phy = BLE_HCI_LE_PHY_1M;
    params.secondary_phy = BLE_HCI_LE_PHY_1M;
    params.tx_power = 127;
    params.sid = 5;

    /* configure instance 5 */
    rc = ble_gap_ext_adv_configure(instance, &params, NULL, NULL, NULL);
    assert (rc == 0);

    /* set random (NRPA) address for instance */
    rc = ble_hs_id_gen_rnd(1, &addr);
    assert (rc == 0);

    rc = ble_gap_ext_adv_set_addr(instance, &addr );
    assert (rc == 0);

    memset(&adv_fields, 0, sizeof(adv_fields));
    adv_fields.name = (const uint8_t *)"nimble with periodic";
    adv_fields.name_len = strlen((char *)adv_fields.name);

    /* Default to legacy PDUs size, mbuf chain will be increased if needed
     */
    data = os_msys_get_pkthdr(BLE_HCI_MAX_ADV_DATA_LEN, 0);
    assert(data);

    rc = ble_hs_adv_set_fields_mbuf(&adv_fields, data);
    assert(rc == 0);

    rc = ble_gap_ext_adv_set_data(instance, data);
    assert(rc == 0);

    /* configure periodic advertising */
    memset(&pparams, 0, sizeof(pparams));
    pparams.include_tx_power = 1;
    pparams.itvl_min = 160;
    pparams.itvl_max = 240;

    rc = ble_gap_periodic_adv_configure(instance, &pparams);
    assert(rc == 0);

    /* get mbuf for periodic data */
    data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
    assert(data);

    /* fill mbuf with periodic data */
    rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
    assert(rc == 0);

    rc = ble_gap_periodic_adv_set_data(instance, data);
    assert (rc == 0);

    /* start periodic advertising */
    rc = ble_gap_periodic_adv_start(instance);
    assert (rc == 0);

    /* start advertising */
     rc = ble_gap_ext_adv_start(instance, 0, 0);
     assert (rc == 0);

     console_printf("instance %u started (periodic)\n", instance);
}

static void
on_sync(void)
{
    int rc;

    console_printf("Synced, starting advertising\n");

    /* Make sure we have proper identity address set (public preferred) */
    rc = ble_hs_util_ensure_addr(0);
    assert(rc == 0);

    /* configure global address */
    rc = ble_hs_id_infer_auto(0, &id_addr_type);
    assert(rc == 0);

    start_non_connectable_ext();

    start_scannable_ext();

    start_scannable_legacy_ext();

    start_legacy_duration(0, true);

    start_ext_max_events(0, true);

    start_periodic();
}

/*
 * main
 *
 * The main task for the project. This function initializes the packages,
 * then starts serving events from default event queue.
 *
 * @return int NOTE: this function should never return!
 */
int
main(void)
{
    /* Initialize OS */
    sysinit();

    console_printf("Extended Advertising sample application\n");

    /* Set sync callback */
    ble_hs_cfg.sync_cb = on_sync;

    /* As the last thing, process events from default event queue */
    while (1) {
        os_eventq_run(os_eventq_dflt_get());
    }
    return 0;
}
