/**
 * Copyright (c) 2018 - 2020, Nordic Semiconductor ASA
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Nordic
 *    Semiconductor ASA integrated circuit in a product or a software update for
 *    such product, must reproduce the above copyright notice, this list of
 *    conditions and the following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Nordic Semiconductor ASA integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
/** @file
 *
 * @defgroup zigbee_examples_light_switch main.c
 * @{
 * @ingroup zigbee_examples
 * @brief Dimmer switch for HA profile implementation.
 */

#include "zboss_api.h"
#include "zb_mem_config_custom.h"
#include "zb_error_handler.h"
#include "zigbee_helpers.h"

#include "app_timer.h"
#include "bsp.h"
#include "boards.h"

#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define IEEE_CHANNEL_MASK                   (1l << ZIGBEE_CHANNEL)              /**< Scan only one, predefined channel to find the coordinator. */
#define MATCH_DESC_REQ_START_DELAY          (2 * ZB_TIME_ONE_SECOND)            /**< Delay between the light switch startup and light bulb finding procedure. */
#define MATCH_DESC_REQ_TIMEOUT              (5 * ZB_TIME_ONE_SECOND)            /**< Timeout for finding procedure. */
#define MATCH_DESC_REQ_ROLE                 ZB_NWK_BROADCAST_RX_ON_WHEN_IDLE    /**< Find only non-sleepy device. */
#define ERASE_PERSISTENT_CONFIG             ZB_TRUE                            /**< Do not erase NVRAM to save the network parameters after device reboot or power-off. NOTE: If this option is set to ZB_TRUE then do full device erase for all network devices before running other samples. */
#define ZIGBEE_NETWORK_STATE_LED            BSP_BOARD_LED_2                     /**< LED indicating that light switch successfully joind Zigbee network. */
#define BULB_FOUND_LED                      BSP_BOARD_LED_1                     /**< LED indicating that light witch found a light bulb to control. */

#if !defined ZB_ED_ROLE
#error Define ZB_ED_ROLE to compile light switch (End Device) source code.
#endif


typedef struct light_switch_bulb_params_s
{
  zb_uint8_t  endpoint;
  zb_uint16_t short_addr;
} light_switch_bulb_params_t;

typedef struct light_switch_ctx_s
{
  light_switch_bulb_params_t bulb_params;
} light_switch_ctx_t;


static zb_void_t find_light_bulb_timeout(zb_bufid_t bufid);

static light_switch_ctx_t m_device_ctx;
static zb_uint8_t         m_attr_zcl_version   = ZB_ZCL_VERSION;
static zb_uint8_t         m_attr_power_source  = ZB_ZCL_BASIC_POWER_SOURCE_UNKNOWN;
static zb_uint16_t        m_attr_identify_time = 0;

/* Declare attribute list for Basic cluster. */
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST(basic_attr_list, &m_attr_zcl_version, &m_attr_power_source);

/* Declare attribute list for Identify cluster. */
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(identify_attr_list, &m_attr_identify_time);

/* Declare application's device context (list of registered endpoints) for Dimmer Switch device. */
#define TUNNELING_ENDPOINT_ID 11
#define TUNNELING_PROFILE_ID ZB_AF_HA_PROFILE_ID
#define TUNNELING_DEVICE_ID 0x508 // Remote Communications Device
#define TUNNELING_PROTOCOL_ID 200
#define TUNNELING_MANUF_CODE 3910
zb_zcl_cluster_desc_t tunneling_clusters[]={
        ZB_ZCL_CLUSTER_DESC(ZB_ZCL_CLUSTER_ID_TUNNELING,
                0, NULL,
                ZB_ZCL_CLUSTER_CLIENT_ROLE,
                TUNNELING_MANUF_CODE)
};
zb_af_simple_desc_1_1_t simple_desc_tunnel={
        TUNNELING_ENDPOINT_ID,
        TUNNELING_PROFILE_ID,
        TUNNELING_DEVICE_ID,
        1, 0,
        0, 1,
        { ZB_ZCL_CLUSTER_ID_TUNNELING }
};
ZB_AF_DECLARE_ENDPOINT_DESC(tunneling_ep, TUNNELING_ENDPOINT_ID,
        TUNNELING_PROFILE_ID, 0, NULL,
        ZB_ZCL_ARRAY_SIZE(tunneling_clusters, zb_zcl_cluster_desc_t), tunneling_clusters,
        &simple_desc_tunnel,
        0, NULL, 0, NULL);
ZBOSS_DECLARE_DEVICE_CTX_1_EP(dimmer_switch_ctx, tunneling_ep);

/**@brief Function for the Timer initialization.
 *
 * @details Initializes the timer module. This creates and starts application timers.
 */
static void timers_init(void)
{
    ret_code_t err_code;

    // Initialize timer module.
    err_code = app_timer_init();
    APP_ERROR_CHECK(err_code);
}

/**@brief Function for initializing the nrf log module.
 */
static void log_init(void)
{
    ret_code_t err_code = NRF_LOG_INIT(NULL);
    APP_ERROR_CHECK(err_code);

    NRF_LOG_DEFAULT_BACKENDS_INIT();
}

static void tunnel_request_cb(zb_bufid_t bufid)
{
    NRF_LOG_INFO("tunnel_request_cb: %d", bufid);
    zb_buf_free(bufid);
}

static void tunnel_request(zb_bufid_t bufid)
{
    ZB_ZCL_TUNNELING_SEND_REQUEST_TUNNEL(bufid,
            m_device_ctx.bulb_params.short_addr,
            ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
            TUNNELING_ENDPOINT_ID,
			TUNNELING_ENDPOINT_ID,
            TUNNELING_PROFILE_ID,
            ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
            tunnel_request_cb,
            TUNNELING_PROTOCOL_ID,
            TUNNELING_MANUF_CODE,
            ZB_FALSE,
            100);
    NRF_LOG_INFO("tunnel_request: %d", bufid);
}

static zb_uint16_t tunnel_id;

static void tunnel_close_cb(zb_bufid_t bufid)
{
    NRF_LOG_INFO("tunnel_close_cb: %d", bufid);
    zb_buf_free(bufid);
}

static void tunnel_close(zb_bufid_t bufid)
{
    ZB_ZCL_TUNNELING_SEND_CLOSE_TUNNEL(bufid,
            m_device_ctx.bulb_params.short_addr,
            ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
            TUNNELING_ENDPOINT_ID,
            TUNNELING_ENDPOINT_ID,
            TUNNELING_PROFILE_ID,
            ZB_ZCL_DISABLE_DEFAULT_RESPONSE,
            tunnel_close_cb,
            tunnel_id);
    NRF_LOG_INFO("tunnel_close: %d", bufid);
}

/**@brief Callback function receiving finding procedure results.
 *
 * @param[in]   bufid   Reference to Zigbee stack buffer used to pass received data.
 */
static zb_void_t find_light_bulb_cb(zb_bufid_t bufid)
{
    zb_zdo_match_desc_resp_t   * p_resp = (zb_zdo_match_desc_resp_t *) zb_buf_begin(bufid);    // Get the beginning of the response
    zb_apsde_data_indication_t * p_ind  = ZB_BUF_GET_PARAM(bufid, zb_apsde_data_indication_t); // Get the pointer to the parameters buffer, which stores APS layer response
    zb_uint8_t                 * p_match_ep;
    zb_ret_t                     zb_err_code;

    if ((p_resp->status == ZB_ZDP_STATUS_SUCCESS) && (p_resp->match_len > 0) && (m_device_ctx.bulb_params.short_addr == 0xFFFF))
    {
        /* Match EP list follows right after response header */
        p_match_ep = (zb_uint8_t *)(p_resp + 1);

        /* We are searching for exact cluster, so only 1 EP may be found */
        m_device_ctx.bulb_params.endpoint   = *p_match_ep;
        m_device_ctx.bulb_params.short_addr = p_ind->src_addr;

        NRF_LOG_INFO("Found bulb addr: %d ep: %d", m_device_ctx.bulb_params.short_addr, m_device_ctx.bulb_params.endpoint);

        zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(find_light_bulb_timeout, ZB_ALARM_ANY_PARAM);
        ZB_ERROR_CHECK(zb_err_code);

        bsp_board_led_on(BULB_FOUND_LED);
    }

    if (bufid)
    {
        zb_buf_free(bufid);
    }
}

/**@brief Function for sending ON/OFF and Level Control find request.
 *
 * @param[in]   bufid   Non-zero reference to Zigbee stack buffer that will be used to construct find request.
 */
static zb_void_t find_light_bulb(zb_bufid_t bufid)
{
    zb_zdo_match_desc_param_t * p_req;

    /* Initialize pointers inside buffer and reserve space for zb_zdo_match_desc_param_t request */
    p_req = zb_buf_initial_alloc(bufid, sizeof(zb_zdo_match_desc_param_t) + (1) * sizeof(zb_uint16_t));

    p_req->nwk_addr         = MATCH_DESC_REQ_ROLE;              // Send to devices specified by MATCH_DESC_REQ_ROLE
    p_req->addr_of_interest = MATCH_DESC_REQ_ROLE;              // Get responses from devices specified by MATCH_DESC_REQ_ROLE

    // Nur Tunnelprofil:
    p_req->profile_id       = TUNNELING_PROFILE_ID;
    p_req->num_in_clusters  = 1;
    p_req->cluster_list[0]  = ZB_ZCL_CLUSTER_ID_TUNNELING;

    /*lint -restore */
    m_device_ctx.bulb_params.short_addr = 0xFFFF; // Set 0xFFFF to reset short address in order to parse only one response.
    UNUSED_RETURN_VALUE(zb_zdo_match_desc_req(bufid, find_light_bulb_cb));
}

/**@brief Finding procedure timeout handler.
 *
 * @param[in]   bufid   Reference to Zigbee stack buffer that will be used to construct find request.
 */
static zb_void_t find_light_bulb_timeout(zb_bufid_t bufid)
{
    zb_ret_t zb_err_code;

    if (bufid)
    {
        NRF_LOG_INFO("Bulb not found, try again");
        zb_err_code = ZB_SCHEDULE_APP_ALARM(find_light_bulb, bufid, MATCH_DESC_REQ_START_DELAY);
        ZB_ERROR_CHECK(zb_err_code);
        zb_err_code = ZB_SCHEDULE_APP_ALARM(find_light_bulb_timeout, 0, MATCH_DESC_REQ_TIMEOUT);
        ZB_ERROR_CHECK(zb_err_code);
    }
    else
    {
        zb_err_code = zb_buf_get_out_delayed(find_light_bulb_timeout);
        ZB_ERROR_CHECK(zb_err_code);
    }
}

/**@brief Callback for button events.
 *
 * @param[in]   evt      Incoming event from the BSP subsystem.
 */
static void buttons_handler(bsp_event_t evt)
{
    zb_ret_t zb_err_code;

    /* Inform default signal handler about user input at the device. */
    user_input_indicate();

    if (m_device_ctx.bulb_params.short_addr == 0xFFFF)
    {
        /* No bulb found yet. */
        return;
    }

    switch(evt)
    {
        case BSP_EVENT_KEY_0:
            {
            	NRF_LOG_INFO("ON/tunnel request");
				zb_bufid_t bufid=zb_buf_get_out_func();
			    zb_err_code=ZB_SCHEDULE_APP_ALARM(tunnel_request, bufid, 3*ZB_TIME_ONE_SECOND);
                ZB_ERROR_CHECK(zb_err_code);
            }
            break;

        case BSP_EVENT_KEY_1:
            {
            	NRF_LOG_INFO("OFF/tunnel close");
                zb_bufid_t bufid=zb_buf_get_out_func();
                zb_err_code=ZB_SCHEDULE_APP_ALARM(tunnel_close, bufid, 1*ZB_TIME_ONE_SECOND);
                ZB_ERROR_CHECK(zb_err_code);
            }
            break;

        default:
            NRF_LOG_INFO("Unhandled BSP Event received: %d", evt);
            return;
    }
}

/**@brief Function for initializing LEDs and buttons.
 */
static zb_void_t leds_buttons_init(void)
{
    ret_code_t error_code;

    /* Initialize LEDs and buttons - use BSP to control them. */
    error_code = bsp_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS, buttons_handler);
    APP_ERROR_CHECK(error_code);
    /* By default the bsp_init attaches BSP_KEY_EVENTS_{0-4} to the PUSH events of the corresponding buttons. */

    bsp_board_leds_off();
}

/**@brief Zigbee stack event handler.
 *
 * @param[in]   bufid   Reference to the Zigbee stack buffer used to pass signal.
 */
void zboss_signal_handler(zb_bufid_t bufid)
{
    zb_zdo_app_signal_hdr_t      * p_sg_p = NULL;
    zb_zdo_app_signal_type_t       sig    = zb_get_app_signal(bufid, &p_sg_p);
    zb_ret_t                       status = ZB_GET_APP_SIGNAL_STATUS(bufid);
    zb_ret_t                       zb_err_code;

    /* Update network status LED */
    zigbee_led_status_update(bufid, ZIGBEE_NETWORK_STATE_LED);
    NRF_LOG_INFO("zboss_signal_handler: %i", sig);

    switch(sig)
    {
        case ZB_BDB_SIGNAL_DEVICE_REBOOT:
            /* fall-through */
        case ZB_BDB_SIGNAL_STEERING:
            /* Call default signal handler. */
            ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
            if (status == RET_OK)
            {
                /* Check the light device address */
                if (m_device_ctx.bulb_params.short_addr == 0xFFFF)
                {
                    zb_err_code = ZB_SCHEDULE_APP_ALARM(find_light_bulb, bufid, MATCH_DESC_REQ_START_DELAY);
                    ZB_ERROR_CHECK(zb_err_code);
                    zb_err_code = ZB_SCHEDULE_APP_ALARM(find_light_bulb_timeout, 0, MATCH_DESC_REQ_TIMEOUT);
                    ZB_ERROR_CHECK(zb_err_code);
                    bufid = 0; // Do not free buffer - it will be reused by find_light_bulb callback
                }
            }
            break;

        default:
            /* Call default signal handler. */
            ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
            break;
    }

    if (bufid)
    {
        zb_buf_free(bufid);
    }
}

static void tunnel_send_transfer_data_cb(zb_bufid_t bufid)
{
    NRF_LOG_INFO("tunnel_send_transfer_data_cb: %d", bufid);
    zb_buf_free(bufid);
}

static void tunnel_send_transfer_data(zb_bufid_t bufid)
{
	zb_uint8_t *data=(zb_uint8_t*)"client data";
	NRF_LOG_INFO("Transfer data: %d/%d", tunnel_id, strlen((char*)data));
	zb_ret_t zb_err_code=ZB_ZCL_TUNNELING_CLIENT_SEND_TRANSFER_DATA(bufid, TUNNELING_ENDPOINT_ID, TUNNELING_PROFILE_ID,
			ZB_ZCL_DISABLE_DEFAULT_RESPONSE, tunnel_send_transfer_data_cb,
			tunnel_id, strlen((char*)data), data);
    ZB_ERROR_CHECK(zb_err_code);
}

static zb_uint8_t zb_tunnel_endpoint_handler(zb_bufid_t bufid)
{
	zb_zcl_parsed_hdr_t * header = ZB_BUF_GET_PARAM(bufid, zb_zcl_parsed_hdr_t);
    NRF_LOG_INFO("zb_tunnel_endpoint_handler: %i", header->cmd_id);

	switch(header->cmd_id)
	{
		case ZB_ZCL_TUNNELING_SRV_CMD_REQUEST_TUNNEL_RESPONSE:
		{
			zb_zcl_tunneling_request_tunnel_response_t tun;
			zb_zcl_parse_status_t status;

			ZB_ZCL_TUNNELING_GET_REQUEST_TUNNEL_RESPONSE(&tun, bufid, status);
			NRF_LOG_INFO("Tunneling request response: %d/%d/%d", tun.tunnel_id, tun.tunnel_status, status);
			if (status==ZB_ZCL_PARSE_STATUS_SUCCESS &&
				tun.tunnel_status==ZB_ZCL_TUNNELING_STATUS_SUCCESS)
			{
				tunnel_id=tun.tunnel_id;
				zb_bufid_t buf=zb_buf_get_out_func();
				zb_ret_t zb_err_code=ZB_SCHEDULE_APP_ALARM(tunnel_send_transfer_data, buf, 2*ZB_TIME_ONE_SECOND);
                ZB_ERROR_CHECK(zb_err_code);
			}
			zb_buf_free(bufid);
			return ZB_TRUE;
		}
        case ZB_ZCL_TUNNELING_SRV_CMD_TUNNEL_CLOSURE_NOTIFICATION:
        {
            NRF_LOG_INFO("Tunnel closed/timeout");
            break;
        }
        case ZB_ZCL_TUNNELING_SRV_CMD_TRANSFER_DATA:
        {
            zb_zcl_tunneling_transfer_data_payload_t data;
            zb_zcl_parse_status_t status;

            ZB_ZCL_TUNNELING_GET_TRANSFER_DATA(&data, bufid, status);
            NRF_LOG_INFO("Transfer data: %d/%d", data.data_size, status);
            if (status==ZB_ZCL_PARSE_STATUS_SUCCESS)
            {
                NRF_LOG_INFO("%s", data.tun_data);
            }
            zb_buf_free(bufid);
            return ZB_TRUE;
        }
	}

    return ZB_FALSE;
}

static void init_setunnel()
{
    ZB_ZCL_CLUSTER_ID_TUNNELING_CLIENT_ROLE_INIT();
    ZB_AF_SET_ENDPOINT_HANDLER(TUNNELING_ENDPOINT_ID, zb_tunnel_endpoint_handler);
}

/**@brief Function for application main entry.
 */
int main(void)
{
    zb_ret_t       zb_err_code;
    zb_ieee_addr_t ieee_addr;

    /* Initialize timers, loging system and GPIOs. */
    timers_init();
    log_init();
    leds_buttons_init();

    /* Set Zigbee stack logging level and traffic dump subsystem. */
    ZB_SET_TRACE_LEVEL(ZIGBEE_TRACE_LEVEL);
    ZB_SET_TRACE_MASK(ZIGBEE_TRACE_MASK);
    ZB_SET_TRAF_DUMP_OFF();

    /* Initialize Zigbee stack. */
    ZB_INIT("Tunnel client");

    /* Set device address to the value read from FICR registers. */
    zb_osif_get_ieee_eui64(ieee_addr);
    zb_set_long_address(ieee_addr);

    zb_set_network_ed_role(IEEE_CHANNEL_MASK);
    zigbee_erase_persistent_storage(ERASE_PERSISTENT_CONFIG);

    zb_set_ed_timeout(ED_AGING_TIMEOUT_64MIN);
    zb_set_keepalive_timeout(ZB_MILLISECONDS_TO_BEACON_INTERVAL(3000));

    /* Initialize application context structure. */
    UNUSED_RETURN_VALUE(ZB_MEMSET(&m_device_ctx, 0, sizeof(light_switch_ctx_t)));

    /* Set default bulb short_addr. */
    m_device_ctx.bulb_params.short_addr = 0xFFFF;

    /* Register dimmer switch device context (endpoints). */
    ZB_AF_REGISTER_DEVICE_CTX(&dimmer_switch_ctx);

    init_setunnel();

    /** Start Zigbee Stack. */
    zb_err_code = zboss_start_no_autostart();
    ZB_ERROR_CHECK(zb_err_code);

    while(1)
    {
        zboss_main_loop_iteration();
        UNUSED_RETURN_VALUE(NRF_LOG_PROCESS());
    }
}


/**
 * @}
 */
