/* Copyright (c) 2016 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

#include "es_adv_timing.h"
#include "es_slot.h"
#include "app_timer.h"
#include "nrf_ble_es.h"
#include "es_app_config.h"
#include "es_adv_timing_resolver.h"

APP_TIMER_DEF(m_es_adv_interval_timer);                         //!< Timer for advertising the set of slots.
APP_TIMER_DEF(m_es_slot_timer);                                 //!< Timer for advertising individual slots.

static nrf_ble_escs_adv_interval_t     m_current_adv_interval;  //!< Current advertisement interval.
static es_adv_timing_callback_t        m_timing_mgr_callback;   //!< Registered callback.
static es_adv_timing_resolver_result_t m_adv_timing_result;     //!< Current advertising timing result.
static bool                            m_non_conn_adv_active;   //!< Is the beacon advertising non-conn advertisements?

/**@brief Function for invoking registered callback.  
 *
 * @param[in] p_evt Event to issue to callback.
 */
static void invoke_callback(const es_adv_timing_evt_t * p_evt)
{
    if(m_timing_mgr_callback != NULL && m_non_conn_adv_active)
    {
        m_timing_mgr_callback(p_evt);
    }
}


/**@brief Timeout handler for the advertisement slot timer. */
static void adv_slot_timeout(void * p_context)
{
    uint32_t err_code;
    uint32_t active_slot_index = (uint32_t)p_context;

    es_adv_timing_evt_t evt;

    evt.slot_no = m_adv_timing_result.timing_results[active_slot_index].slot_no;

    evt.evt_id = m_adv_timing_result.timing_results[active_slot_index].is_etlm ? \
                 ES_ADV_TIMING_EVT_ADV_ETLM : ES_ADV_TIMING_EVT_ADV_SLOT;

    // Trigger an event for the next slot if this slot is not the last to be advertised in this event.
    // Note: since we check 'm_adv_timing_result.len_timing_results > 1' we can safely cast the result of 
    //       the subtraction to a uint32.
    if (m_non_conn_adv_active && \
        m_adv_timing_result.len_timing_results > 1 && \
        active_slot_index < (uint32_t)(m_adv_timing_result.len_timing_results - 1)) 
    {
        err_code = app_timer_start( m_es_slot_timer, 
                                    APP_TIMER_TICKS(m_adv_timing_result.timing_results[active_slot_index].delay_ms, APP_TIMER_PRESCALER), 
                                    (void*)(active_slot_index + 1));
        APP_ERROR_CHECK(err_code);
    }

    invoke_callback(&evt);
}


/**@brief Timeout handler for the advertisement interval timer. */
static void adv_interval_timeout(void * p_context)
{
    if (es_slot_get_registry()->num_configured_slots > 0)
    {
        // Trigger slot timeout for advertising the first slot.
        // Note: The slot number is not the index in the slot registry, it is the index of the active slots.
        adv_slot_timeout(NULL);
    }
    
    if(m_non_conn_adv_active)
    {
        uint32_t err_code = app_timer_start(m_es_adv_interval_timer, 
                                   APP_TIMER_TICKS(m_current_adv_interval, APP_TIMER_PRESCALER), 
                                   NULL);
        APP_ERROR_CHECK(err_code);
    }
}


static void timers_init(void)
{
    ret_code_t err_code;

    err_code = app_timer_create(&m_es_adv_interval_timer,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                adv_interval_timeout);
    APP_ERROR_CHECK(err_code);

    err_code = app_timer_create(&m_es_slot_timer,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                adv_slot_timeout);
    APP_ERROR_CHECK(err_code);
}


/**@brief Function for finding and setting advertisement timing configuration. */
static void adv_timing_set(void)
{
    uint32_t err_code;
    const es_slot_reg_t * p_reg = es_slot_get_registry();

    es_adv_timing_resolver_input_t resolver_input = {
        .adv_interval             = m_current_adv_interval,
        .p_result                 = &m_adv_timing_result,
        .num_slots_configured     = p_reg->num_configured_slots,
        .p_slots_configured       = p_reg->slots_configured,
        .num_eid_slots_configured = p_reg->num_configured_eid_slots,
        .p_eid_slots_configured   = p_reg->eid_slots_configured,
        .tlm_configured           = p_reg->tlm_configured,
        .tlm_slot                 = p_reg->tlm_slot
    };

    err_code = es_adv_timing_resolve(&resolver_input);
    APP_ERROR_CHECK(err_code);
}


void es_adv_timing_start(uint16_t adv_interval)
{
    uint32_t err_code;

    const es_slot_reg_t * p_reg = es_slot_get_registry();

    m_non_conn_adv_active = true;
    
    if (p_reg->num_configured_slots > 0)
    {
        m_current_adv_interval = adv_interval;

        err_code = app_timer_start(m_es_adv_interval_timer, 
                                   APP_TIMER_TICKS(m_current_adv_interval, APP_TIMER_PRESCALER), 
                                   NULL);
        APP_ERROR_CHECK(err_code);

        adv_timing_set();
    }
}


void es_adv_timing_stop(void)
{
    m_non_conn_adv_active = false; // Stops timers from being re-fired.
}


void es_adv_timing_init(es_adv_timing_callback_t p_handler)
{
    m_non_conn_adv_active = false;
    m_timing_mgr_callback = p_handler;
    memset(&m_adv_timing_result, 0, sizeof(m_adv_timing_result));
    timers_init();
}
