/*
 * 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 <string.h>
#include "nimble/porting/nimble/include/os/endian.h"
#include "nimble/nimble/host/include/host/ble_eddystone.h"
#include "nimble/nimble/host/include/host/ble_hs_adv.h"
#include "ble_hs_priv.h"

#define BLE_EDDYSTONE_MAX_SVC_DATA_LEN  22
#define BLE_EDDYSTONE_SVC_DATA_BASE_SZ  3

#define BLE_EDDYSTONE_SERVICE_UUID      0xfeaa

#define BLE_EDDYSTONE_FRAME_TYPE_UID    0x00
#define BLE_EDDYSTONE_FRAME_TYPE_URL    0x10

static ble_uuid16_t ble_eddystone_uuids16[BLE_EDDYSTONE_MAX_UUIDS16 + 1];
static uint8_t ble_eddystone_svc_data[BLE_EDDYSTONE_MAX_SVC_DATA_LEN];

/**
 * Writes an eddystone header to the global service data buffer.
 *
 * @param frame_type                The eddystone frame type; one of the
 *                                      BLE_EDDYSTONE_FRAME_TYPE_[...] values.
 *
 * @return                          A pointer to where the service data payload
 *                                      should be written.
 */
static void *
ble_eddystone_set_svc_data_base(uint8_t frame_type)
{
    put_le16(ble_eddystone_svc_data, BLE_EDDYSTONE_SERVICE_UUID);
    ble_eddystone_svc_data[2] = frame_type;

    return ble_eddystone_svc_data + BLE_EDDYSTONE_SVC_DATA_BASE_SZ;
}

/**
 * Populates the supplied advertisement fields struct to represent an eddystone
 * advertisement.  Prior to calling this function, you must write the service
 * data header and payload using the ble_eddystone_set_svc_data_base()
 * function.
 *
 * @param adv_fields            The base advertisement fields to transform into
 *                                  an eddystone beacon.  All configured fields
 *                                  are preserved; you probably want to clear
 *                                  this struct before calling this function.
 * @param svc_data_len          The amount of data written to the global
 *                                  service data buffer.
 *
 * @return                      0 on success; BLE_HS_E... on failure.
 */
static int
ble_eddystone_set_adv_data_gen(struct ble_hs_adv_fields *adv_fields,
                               uint8_t svc_data_len)
{
    int rc;

    if (adv_fields->num_uuids16 > BLE_EDDYSTONE_MAX_UUIDS16) {
        return BLE_HS_EINVAL;
    }
    if (svc_data_len > (BLE_EDDYSTONE_MAX_SVC_DATA_LEN - BLE_EDDYSTONE_SVC_DATA_BASE_SZ)) {
        return BLE_HS_EINVAL;
    }
    if (adv_fields->num_uuids16 > 0 && !adv_fields->uuids16_is_complete) {
        return BLE_HS_EINVAL;
    }
    if (adv_fields->svc_data_uuid16_len != 0) {
        return BLE_HS_EINVAL;
    }

    ble_eddystone_uuids16[0] =
        (ble_uuid16_t) BLE_UUID16_INIT(BLE_EDDYSTONE_SERVICE_UUID);
    memcpy(ble_eddystone_uuids16 + 1, adv_fields->uuids16,
           adv_fields->num_uuids16 * sizeof(ble_uuid16_t));
    adv_fields->uuids16 = ble_eddystone_uuids16;
    adv_fields->num_uuids16++;
    adv_fields->uuids16_is_complete = 1;

    adv_fields->svc_data_uuid16 = ble_eddystone_svc_data;
    adv_fields->svc_data_uuid16_len = svc_data_len +
                                      BLE_EDDYSTONE_SVC_DATA_BASE_SZ;

    rc = ble_gap_adv_set_fields(adv_fields);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_eddystone_set_adv_data_uid(struct ble_hs_adv_fields *adv_fields,
                               void *uid, int8_t measured_power)
{
    uint8_t *svc_data;
    int rc;

    /* Eddystone UUID and frame type (0). */
    svc_data = ble_eddystone_set_svc_data_base(BLE_EDDYSTONE_FRAME_TYPE_UID);

    /* Measured Power ranging data (Calibrated tx power at 0 meters). */
    if (measured_power < -100 || measured_power > 20) {
        return BLE_HS_EINVAL;
    }
    svc_data[0] = measured_power;

    /* UID. */
    memcpy(svc_data + 1, uid, 16);

    /* Reserved. */
    svc_data[17] = 0x00;
    svc_data[18] = 0x00;

    rc = ble_eddystone_set_adv_data_gen(adv_fields, 19);
    if (rc != 0) {
        return rc;
    }

    return 0;
}

int
ble_eddystone_set_adv_data_url(struct ble_hs_adv_fields *adv_fields,
                               uint8_t url_scheme, char *url_body,
                               uint8_t url_body_len, uint8_t url_suffix,
                               int8_t measured_power)
{
    uint8_t *svc_data;
    int url_len;
    int rc;

    url_len = url_body_len;
    if (url_suffix != BLE_EDDYSTONE_URL_SUFFIX_NONE) {
        url_len++;
    }
    if (url_len > BLE_EDDYSTONE_URL_MAX_LEN) {
        return BLE_HS_EINVAL;
    }

    svc_data = ble_eddystone_set_svc_data_base(BLE_EDDYSTONE_FRAME_TYPE_URL);

    /* Measured Power ranging data (Calibrated tx power at 0 meters). */
    if (measured_power < -100 || measured_power > 20) {
        return BLE_HS_EINVAL;
    }
    svc_data[0] = measured_power;

    svc_data[1] = url_scheme;
    memcpy(svc_data + 2, url_body, url_body_len);
    if (url_suffix != BLE_EDDYSTONE_URL_SUFFIX_NONE) {
        svc_data[2 + url_body_len] = url_suffix;
    }

    rc = ble_eddystone_set_adv_data_gen(adv_fields, url_len + 2);
    if (rc != 0) {
        return rc;
    }

    return 0;
}
