/*  Bluetooth Mesh */

/*
 * Copyright (c) 2017 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "syscfg/syscfg.h"
#define MESH_LOG_MODULE BLE_MESH_LOG

#if MYNEWT_VAL(BLE_MESH)

#include <stdbool.h>
#include <errno.h>

#include "os/os_mbuf.h"
#include "mesh/mesh.h"
#include "host/ble_uuid.h"

#include "adv.h"
#include "prov.h"
#include "net.h"
#include "beacon.h"
#include "lpn.h"
#include "friend.h"
#include "transport.h"
#include "access.h"
#include "foundation.h"
#include "proxy.h"
#include "shell.h"
#include "mesh_priv.h"
#include "settings.h"

u8_t g_mesh_addr_type;
static struct ble_gap_event_listener mesh_event_listener;

int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx,
                      u8_t flags, u32_t iv_index, u16_t addr,
                      const u8_t dev_key[16])
{
    bool pb_gatt_enabled;
    int err;
    BT_INFO("Primary Element: 0x%04x", addr);
    BT_DBG("net_idx 0x%04x flags 0x%02x iv_index 0x%04x",
           net_idx, flags, (unsigned) iv_index);

    if(atomic_test_and_set_bit(bt_mesh.flags, BT_MESH_VALID)) {
        return -EALREADY;
    }

    if((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
        if(bt_mesh_proxy_prov_disable(false) == 0) {
            pb_gatt_enabled = true;
        } else {
            pb_gatt_enabled = false;
        }
    } else {
        pb_gatt_enabled = false;
    }

    err = bt_mesh_net_create(net_idx, flags, net_key, iv_index);

    if(err) {
        atomic_clear_bit(bt_mesh.flags, BT_MESH_VALID);

        if(MYNEWT_VAL(BLE_MESH_PB_GATT)  && pb_gatt_enabled) {
            bt_mesh_proxy_prov_enable();
        }

        return err;
    }

    bt_mesh.seq = 0;
    bt_mesh_comp_provision(addr);
    memcpy(bt_mesh.dev_key, dev_key, 16);

    if(IS_ENABLED(CONFIG_BT_SETTINGS)) {
        BT_DBG("Storing network information persistently");
        bt_mesh_store_net();
        bt_mesh_store_subnet(&bt_mesh.sub[0]);
        bt_mesh_store_iv(false);
    }

    bt_mesh_net_start();
    return 0;
}

int bt_mesh_provision_adv(const u8_t uuid[16], u16_t net_idx, u16_t addr,
                          u8_t attention_duration)
{
    if(!atomic_test_bit(bt_mesh.flags, BT_MESH_PROVISIONER)) {
        printf("E1\r\n");
        return -EINVAL;
    }

    if(bt_mesh_subnet_get(net_idx) == NULL) {
        return -EINVAL;
    }

    if(IS_ENABLED(CONFIG_BT_MESH_PROVISIONER) &&
            IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
        return bt_mesh_pb_adv_open(uuid, net_idx, addr,
                                   attention_duration);
    }

    return -ENOTSUP;
}

bool bt_mesh_is_provisioned(void)
{
    return atomic_test_bit(bt_mesh.flags, BT_MESH_VALID);
}
bool bt_mesh_role_is_provisioner(void)
{
    return atomic_test_bit(bt_mesh.flags, BT_MESH_PROVISIONER);
}
bool bt_mesh_role_is_node(void)
{
    return atomic_test_bit(bt_mesh.flags, BT_MESH_NODE);
}

int bt_mesh_get_role(void)
{
    if(atomic_test_bit(bt_mesh.flags, BT_MESH_NODE)) {
        return (int)1;
    } else if(atomic_test_bit(bt_mesh.flags, BT_MESH_PROVISIONER)) {
        return (int)2;
    } else {
        return 0; /*MESH_ROLE_UNKNOWN*/
    }
}

int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers)
{
    if(bt_mesh_is_provisioned()) {
        return -EALREADY;
    }

    char uuid_buf[BLE_UUID_STR_LEN];
    const struct bt_mesh_prov *prov = bt_mesh_prov_get();
    ble_uuid_t *uuid = BLE_UUID128_DECLARE();
    memcpy(BLE_UUID128(uuid)->value, prov->uuid, 16);
    BT_INFO("Device UUID: %s", ble_uuid_to_str(uuid, uuid_buf));

    if(IS_ENABLED(CONFIG_BT_MESH_PB_ADV) &&
            (bearers & BT_MESH_PROV_ADV)) {
        /* Make sure we're scanning for provisioning inviations */
        bt_mesh_scan_enable();
        /* Enable unprovisioned beacon sending */
        bt_mesh_beacon_enable();
    }

    if(IS_ENABLED(CONFIG_BT_MESH_PB_GATT) &&
            (bearers & BT_MESH_PROV_GATT)) {
        bt_mesh_proxy_prov_enable();
        bt_mesh_adv_update();
    }

    return 0;
}

int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers)
{
    if(bt_mesh_is_provisioned()) {
        return -EALREADY;
    }

    if(IS_ENABLED(CONFIG_BT_MESH_PB_ADV) &&
            (bearers & BT_MESH_PROV_ADV)) {
        bt_mesh_beacon_disable();
        bt_mesh_scan_disable();
    }

    if(IS_ENABLED(CONFIG_BT_MESH_PB_GATT) &&
            (bearers & BT_MESH_PROV_GATT)) {
        bt_mesh_proxy_prov_disable(true);
    }

    return 0;
}

static int bt_mesh_gap_event(struct ble_gap_event *event, void *arg)
{
    ble_adv_gap_mesh_cb(event, arg);
#if (MYNEWT_VAL(BLE_MESH_PROXY))
    ble_mesh_proxy_gap_event(event, arg);
#endif
    return 0;
}

static void model_suspend(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
                          bool vnd, bool primary, void *user_data)
{
    if(mod->pub && mod->pub->update) {
        mod->pub->count = 0;
        k_delayed_work_cancel(&mod->pub->timer);
    }
}

int bt_mesh_suspend(void)
{
    int err;

    if(!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) {
        return -EINVAL;
    }

    if(atomic_test_and_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
        return -EALREADY;
    }

    err = bt_mesh_scan_disable();

    if(err) {
        atomic_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED);
        BT_WARN("Disabling scanning failed (err %d)", err);
        return err;
    }

    bt_mesh_hb_pub_disable();

    if(bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED) {
        bt_mesh_beacon_disable();
    }

    bt_mesh_model_foreach(model_suspend, NULL);
    return 0;
}

static void model_resume(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
                         bool vnd, bool primary, void *user_data)
{
    if(mod->pub && mod->pub->update) {
        s32_t period_ms = bt_mesh_model_pub_period_get(mod);

        if(period_ms) {
            k_delayed_work_submit(&mod->pub->timer, period_ms);
        }
    }
}

int bt_mesh_resume(void)
{
    int err;

    if(!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) {
        return -EINVAL;
    }

    if(!atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) {
        return -EALREADY;
    }

    err = bt_mesh_scan_enable();

    if(err) {
        BT_WARN("Re-enabling scanning failed (err %d)", err);
        atomic_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED);
        return err;
    }

    if(bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED) {
        bt_mesh_beacon_enable();
    }

    bt_mesh_model_foreach(model_resume, NULL);
    return err;
}

int bt_mesh_init(uint8_t own_addr_type, const struct bt_mesh_prov *prov,
                 const struct bt_mesh_comp *comp)
{
    int err;
    g_mesh_addr_type = own_addr_type;
    /* initialize SM alg ECC subsystem (it is used directly from mesh code) */
    ble_sm_alg_ecc_init();
    err = bt_mesh_comp_register(comp);

    if(err) {
        return err;
    }

#if (MYNEWT_VAL(BLE_MESH_PROXY))
    err  = ble_gatts_reset();
    bt_mesh_register_gatt();
    err = ble_gatts_start();
#endif
#if (MYNEWT_VAL(BLE_MESH_PROV))
    err = bt_mesh_prov_init(prov);

    if(err) {
        return err;
    }

#endif
#if (MYNEWT_VAL(BLE_MESH_PROXY))
    bt_mesh_proxy_init();
#endif
#if (MYNEWT_VAL(BLE_MESH_PROV))
    /* Need this to proper link.rx.buf allocation */
    bt_mesh_prov_reset_link();
#endif
    bt_mesh_net_init();
    bt_mesh_trans_init();
    bt_mesh_beacon_init();
    bt_mesh_adv_init();
    bt_mesh_settings_init();
    ble_gap_event_listener_register(&mesh_event_listener,
                                    bt_mesh_gap_event, NULL);
    return 0;
}


void bt_mesh_reset(void)
{
    if(!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) {
        return;
    }

    bt_mesh.iv_index = 0U;
    bt_mesh.seq = 0U;
    memset(bt_mesh.flags, 0, sizeof(bt_mesh.flags));
    k_delayed_work_cancel(&bt_mesh.ivu_timer);
    bt_mesh_cfg_reset(true);
    bt_mesh_rx_reset();
    bt_mesh_tx_reset();

    if((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
        bt_mesh_lpn_disable(true);
    }

    if((MYNEWT_VAL(BLE_MESH_FRIEND))) {
        bt_mesh_friend_clear_net_idx(BT_MESH_KEY_ANY);
    }

    if((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
        bt_mesh_proxy_gatt_disable();
    }

    if((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
        bt_mesh_proxy_prov_enable();
    }

    if(IS_ENABLED(CONFIG_BT_SETTINGS)) {
        bt_mesh_clear_net();
    }

    memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key));
    bt_mesh_scan_disable();
    bt_mesh_beacon_disable();
    bt_mesh_comp_deinit();
    bt_mesh_comp_unprovision();

    if(IS_ENABLED(CONFIG_BT_MESH_PROV)) {
        bt_mesh_prov_reset();
    }
}

int bt_mesh_deinit(void)
{
    int err;
    bt_mesh_scan_disable();
    bt_mesh_beacon_disable();
    bt_mesh.iv_index = 0U;
    bt_mesh.seq = 0U;
    memset(bt_mesh.flags, 0, sizeof(bt_mesh.flags));
    k_delayed_work_cancel(&bt_mesh.ivu_timer);
    bt_mesh_cfg_reset(false);

    if((MYNEWT_VAL(BLE_MESH_FRIEND))) {
        bt_mesh_friend_clear_net_idx(BT_MESH_KEY_ANY);
    }

    if((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
        bt_mesh_proxy_gatt_disable();
    }

    BT_DBG("bt_mesh_net_deinit");
    bt_mesh_net_deinit();
    BT_DBG("bt_mesh_trans_deinit");
    bt_mesh_trans_deinit();
    BT_DBG("bt_mesh_beacon_deinit");
    bt_mesh_beacon_deinit();
    bt_mesh_adv_deinit();
    bt_mesh_settings_deinit();
    BT_DBG("bt_mesh_proxy_deinit");
#if (MYNEWT_VAL(BLE_MESH_PROXY))
    bt_mesh_proxy_deinit();
#endif
#if (MYNEWT_VAL(BLE_MESH_PROV))
    BT_DBG("bt_mesh_prov_deinit");
    err = bt_mesh_prov_deinit();

    if(err) {
        BT_WARN("bt_mesh_prov_deinit failed");
    }

#endif
#if (MYNEWT_VAL(BLE_MESH_PROXY))
    err  = ble_gatts_reset();

    if(err) {
        BT_WARN("ble_gatts_reset failed");
    }

#endif
    BT_DBG("bt_mesh_comp_unregister");
    err = bt_mesh_comp_unregister();

    if(err) {
        BT_WARN("bt_mesh_comp_unregister failed");
    }

#if (MYNEWT_VAL(BLE_MESH_PROV))
    /* Need this to proper link.rx.buf allocation */
    bt_mesh_prov_reset_link_deinit();
#endif
    memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key));
    ble_gap_event_listener_unregister(&mesh_event_listener);

    return err;
}
void bt_mesh_role_set(bool node_role)
{
    if(node_role) {
        atomic_set_bit(bt_mesh.flags, BT_MESH_NODE);
    } else {
        atomic_set_bit(bt_mesh.flags, BT_MESH_PROVISIONER);
    }

    if(IS_ENABLED(CONFIG_BT_SETTINGS)) {
        bt_mesh_store_role();
    }
}
int bt_mesh_provision_init(void)
{
    struct bt_mesh_subnet *sub;
    uint8_t net_key[16] = {0};
    bt_mesh_comp_provision(1);
    sub = &bt_mesh.sub[0];

    if(bt_rand(net_key, 16)) {
        BT_WARN("Provisioner generate primary net key failed");
        return -1;
    }

    if(bt_mesh_net_keys_create(&sub->keys[0], net_key)) {
        BT_ERR("Provisioner create net-related keys failed");
        return -EIO;
    }

    sub->kr_phase = 0;
    sub->net_idx = 0x0000;
    sub->node_id = BT_MESH_NODE_IDENTITY_NOT_SUPPORTED;
    bt_mesh.ivu_duration = BT_MESH_IVU_MIN_HOURS;
    BT_INFO("NetKeyIndex 0x%03x, NID 0x%02x", sub->net_idx, sub->keys[0].nid);
    BT_INFO("NetKey %s", bt_hex(sub->keys[0].net, 16));
    atomic_set_bit(bt_mesh.flags, BT_MESH_VALID);
    bt_mesh_role_set(false);

    if(IS_ENABLED(CONFIG_BT_SETTINGS)) {
        BT_DBG("Storing network information persistently");
        bt_mesh_store_subnet(&bt_mesh.sub[0]);
        bt_mesh_store_iv(true);
    }

    return 0;
}
#endif
