/*
 * 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 <stdio.h>
#include <string.h>
#include "os/mynewt.h"
#include "host/ble_hs.h"
#include "mgmt/mgmt.h"
#include "smp/smp.h"
#include "console/console.h"
#include "mynewt_smp/smp.h"

/**
 * \addtogroup SMP
 * @{
 */

/* smp ble mqueue */
struct os_mqueue g_smp_ble_mq;

/* ble smp transport */
struct smp_transport g_smp_ble_transport;

/* ble smp attr handle */
uint16_t g_ble_smp_attr_handle;

/**
 * The vendor specific "smp" service consists of one write no-rsp
 * characteristic for smp requests: a single-byte characteristic that can
 * only accepts write-without-response commands.  The contents of each write
 * command contains an NMP request.  NMP responses are sent back in the form of
 * unsolicited notifications from the same characteristic.
 */

/* {8D53DC1D-1DB7-4CD3-868B-8A527460AA84} */
static const ble_uuid128_t gatt_svr_svc_smp =
    BLE_UUID128_INIT(0x84, 0xaa, 0x60, 0x74, 0x52, 0x8a, 0x8b, 0x86,
                     0xd3, 0x4c, 0xb7, 0x1d, 0x1d, 0xdc, 0x53, 0x8d);

/* {DA2E7828-FBCE-4E01-AE9E-261174997C48} */
static const ble_uuid128_t gatt_svr_chr_smp =
    BLE_UUID128_INIT(0x48, 0x7c, 0x99, 0x74, 0x11, 0x26, 0x9e, 0xae,
                     0x01, 0x4e, 0xce, 0xfb, 0x28, 0x78, 0x2e, 0xda);

static int
gatt_svr_chr_access_smp(uint16_t conn_handle, uint16_t attr_handle,
                            struct ble_gatt_access_ctxt *ctxt, void *arg);

static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
    {
        /* Service: smp */
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = &gatt_svr_svc_smp.u,
        .characteristics = (struct ble_gatt_chr_def[]) { {
            /* Characteristic: Write No Rsp */
            .uuid = &gatt_svr_chr_smp.u,
            .access_cb = gatt_svr_chr_access_smp,
            .flags = BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY,
            .val_handle = &g_ble_smp_attr_handle,
        }, {
            0, /* No more characteristics in this service */
        } },
    },

    {
        0, /* No more services */
    },
};

static int
gatt_svr_chr_access_smp(uint16_t conn_handle, uint16_t attr_handle,
                            struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    int rc;
    struct os_mbuf *m_req;

    switch (ctxt->op) {
        case BLE_GATT_ACCESS_OP_WRITE_CHR:
            /* Try to reuse the BLE packet mbuf as the smp request.  This
             * requires a two-byte usrhdr to hold the BLE connection handle so
             * that the smp response can be sent to the correct peer.  If
             * it is not possible to reuse the mbuf, then allocate a new one
             * and copy the request contents.
             */
            if (OS_MBUF_USRHDR_LEN(ctxt->om) >= sizeof (conn_handle)) {
                /* Sufficient usrhdr space already present. */
                m_req = ctxt->om;
                ctxt->om = NULL;
            } else if (OS_MBUF_LEADINGSPACE(ctxt->om) >=
                       sizeof (conn_handle)) {

                /* Usrhdr isn't present, but there is enough leading space to
                 * add one.
                 */
                m_req = ctxt->om;
                ctxt->om = NULL;

                m_req->om_pkthdr_len += sizeof (conn_handle);
            } else {
                /* The mbuf can't be reused.  Allocate a new one and perform a
                 * copy.  Don't set ctxt->om to NULL; let the NimBLE host free
                 * it.
                 */
                m_req = os_msys_get_pkthdr(OS_MBUF_PKTLEN(ctxt->om),
                                           sizeof (conn_handle));
                if (!m_req) {
                    return BLE_ATT_ERR_INSUFFICIENT_RES;
                }
                rc = os_mbuf_appendfrom(m_req, ctxt->om, 0,
                                        OS_MBUF_PKTLEN(ctxt->om));
                if (rc) {
                    return BLE_ATT_ERR_INSUFFICIENT_RES;
                }
            }

            /* Write the connection handle to the smp request usrhdr.  This
             * is necessary so that we later know who to send the smp
             * response to.
             */
            memcpy(OS_MBUF_USRHDR(m_req), &conn_handle, sizeof(conn_handle));

            rc = smp_rx_req(&g_smp_ble_transport, m_req);
            if (rc) {
                return BLE_ATT_ERR_UNLIKELY;
            }
            
            return 0;

        default:
            assert(0);
            return BLE_ATT_ERR_UNLIKELY;
    }
}

uint16_t
smp_ble_get_mtu(struct os_mbuf *req) {

    uint16_t conn_handle;
    uint16_t mtu;

    memcpy(&conn_handle, OS_MBUF_USRHDR(req), sizeof (conn_handle));
    mtu = ble_att_mtu(conn_handle);
    if (!mtu) {
        /* No longer connected. */
        return 0;
    }

    /* 3 is the number of bytes for ATT notification base */
    mtu = mtu - 3;

    return (mtu);
}

/**
 * SMP ble process mqueue event
 * Gets an event from the smp mqueue and does a notify with the response
 *
 * @param eventq
 * @return 0 on success; non-zero on failure
 */

static void
smp_ble_event_data_in(struct os_event *ev)
{
    struct os_mbuf *m_resp;
    uint16_t conn_handle;

    while ((m_resp = os_mqueue_get(&g_smp_ble_mq)) != NULL) {
        assert(OS_MBUF_USRHDR_LEN(m_resp) >= sizeof (conn_handle));
        memcpy(&conn_handle, OS_MBUF_USRHDR(m_resp), sizeof (conn_handle));
        ble_gatts_notify_custom(conn_handle, g_ble_smp_attr_handle, m_resp);
    }
}

static int
smp_ble_out(struct os_mbuf *om)
{
    int rc;

    rc = os_mqueue_put(&g_smp_ble_mq, os_eventq_dflt_get(), om);
    if (rc != 0) {
        goto err;
    }

    return 0;
err:
    os_mbuf_free_chain(om);
    return rc;
}

/**
 * SMP ble GATT server initialization
 *
 * @param eventq
 * @return 0 on success; non-zero on failure
 */
int
smp_ble_gatt_svr_init(void)
{
    int rc;

    rc = ble_gatts_count_cfg(gatt_svr_svcs);
    if (rc != 0) {
        goto err;
    }

    rc = ble_gatts_add_svcs(gatt_svr_svcs);
    if (rc != 0) {
        return rc;
    }

    os_mqueue_init(&g_smp_ble_mq, &smp_ble_event_data_in, NULL);

    rc = smp_transport_init(&g_smp_ble_transport, smp_ble_out, smp_ble_get_mtu);

err:
    return rc;
}

void
smp_ble_pkg_init(void)
{
    int rc;

    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

    rc = smp_ble_gatt_svr_init();
    SYSINIT_PANIC_ASSERT(rc == 0);
}

/**
 * @} SMP
 */
