/**@brief Function for scheduling a Timer Start operation.
 *
 * @param[in]  user_id           Id of user calling this function.
 * @param[in]  timer_id          Id of timer to start.
 * @param[in]  timeout_initial   Time (in ticks) to first timer expiry.
 * @param[in]  timeout_periodic  Time (in ticks) between periodic expiries.
 * @param[in]  p_context         General purpose pointer. Will be passed to the timeout handler when
 *                               the timer expires.
 * @return     NRF_SUCCESS on success, otherwise an error code.
 */
static uint32_t timer_start_op_schedule(timer_user_id_t user_id,
                                        timer_node_t * p_node,
                                        uint32_t        timeout_initial,
                                        uint32_t        timeout_periodic,
                                        void *          p_context)
{
    uint8_t last_index;
    
    timer_user_op_t * p_user_op = user_op_alloc(&mp_users[user_id], &last_index);
    if (p_user_op == NULL)
    {
        return NRF_ERROR_NO_MEM;
    }
    
    p_user_op->op_type                              = TIMER_USER_OP_TYPE_START;
    p_user_op->p_node                               = p_node;
    p_user_op->params.start.ticks_at_start          = rtc1_counter_get();
    p_user_op->params.start.ticks_first_interval    = timeout_initial;
    p_user_op->params.start.ticks_periodic_interval = timeout_periodic;
    p_user_op->params.start.p_context               = p_context;
    
    user_op_enque(&mp_users[user_id], last_index);    

    timer_list_handler_sched();

    return NRF_SUCCESS;
}
/**@brief Function for scheduling a Timer Stop operation.
 *
 * @param[in]  timer_id   Id of timer to stop.
 * @param[in]  op_type    Type of stop operation
 *
 * @return NRF_SUCCESS on successful scheduling a timer stop operation. NRF_ERROR_NO_MEM when there
 *         is no memory left to schedule the timer stop operation.
 */
static uint32_t timer_stop_op_schedule(timer_node_t * p_node,
                                       timer_user_op_type_t op_type)
{
    uint8_t last_index;
    uint32_t err_code = NRF_SUCCESS;

    CRITICAL_REGION_ENTER();
    timer_user_op_t * p_user_op = user_op_alloc(&last_index);
    if (p_user_op == NULL)
    {
        err_code = NRF_ERROR_NO_MEM;
    }
    else
    {
        p_user_op->op_type  = op_type;
        p_user_op->p_node = p_node;

        user_op_enque(last_index);
    }
    CRITICAL_REGION_EXIT();

    if (err_code == NRF_SUCCESS)
    {
        timer_list_handler_sched();
    }

    return err_code;
}
static uint32_t timer_start_op_schedule(timer_node_t * p_node,
                                        uint32_t        timeout_initial,
                                        uint32_t        timeout_periodic,
                                        void *          p_context)
{
    uint8_t last_index;
    uint32_t err_code = NRF_SUCCESS;

    CRITICAL_REGION_ENTER();
    timer_user_op_t * p_user_op = user_op_alloc(&last_index);
    if (p_user_op == NULL)
    {
        err_code = NRF_ERROR_NO_MEM;
    }
    else
    {

        p_user_op->op_type                              = TIMER_USER_OP_TYPE_START;
        p_user_op->p_node                               = p_node;
        p_user_op->params.start.ticks_at_start          = rtc1_counter_get();
        p_user_op->params.start.ticks_first_interval    = timeout_initial;
        p_user_op->params.start.ticks_periodic_interval = timeout_periodic;
        p_user_op->params.start.p_context               = p_context;

        user_op_enque(last_index);
    }
    CRITICAL_REGION_EXIT();

    if (err_code == NRF_SUCCESS)
    {
        timer_list_handler_sched();
    }

    return err_code;
}
/**@brief Function for scheduling a Timer Stop All operation.
 *
 * @param[in]  user_id    Id of user calling this function.
 */
static uint32_t timer_stop_all_op_schedule(timer_user_id_t user_id)
{
    timer_user_op_t * p_user_op = user_op_alloc(&mp_users[user_id]);
    if (p_user_op == NULL)
    {
        return NRF_ERROR_NO_MEM;
    }
    
    p_user_op->op_type  = TIMER_USER_OP_TYPE_STOP_ALL;
    p_user_op->timer_id = TIMER_NULL;

    timer_list_handler_sched();

    return NRF_SUCCESS;
}
/**@brief Function for scheduling a Timer Stop All operation.
 *
 * @param[in]  user_id    Id of user calling this function.
 */
static uint32_t timer_stop_all_op_schedule(timer_user_id_t user_id)
{
    uint8_t last_index;
    
    timer_user_op_t * p_user_op = user_op_alloc(&mp_users[user_id], &last_index);
    if (p_user_op == NULL)
    {
        return NRF_ERROR_NO_MEM;
    }
    
    p_user_op->op_type  = TIMER_USER_OP_TYPE_STOP_ALL;
    p_user_op->p_node = NULL;
    
    user_op_enque(&mp_users[user_id], last_index);        

    timer_list_handler_sched();

    return NRF_SUCCESS;
}
/**@brief Function for checking for expired timers.
 */
static void timer_timeouts_check(void)
{
    // Handle expired of timer 
    if (mp_timer_id_head != NULL)
    {
        timer_node_t *  p_timer;
        timer_node_t *  p_previous_timer;
        uint32_t        ticks_elapsed;
        uint32_t        ticks_expired;

        // Initialize actual elapsed ticks being consumed to 0.
        ticks_expired = 0;

        // ticks_elapsed is collected here, job will use it.
        ticks_elapsed = ticks_diff_get(rtc1_counter_get(), m_ticks_latest);

        // Auto variable containing the head of timers expiring.
        p_timer = mp_timer_id_head;

        // Expire all timers within ticks_elapsed and collect ticks_expired.
        while (p_timer != NULL)
        {
            // Do nothing if timer did not expire.
            if (ticks_elapsed < p_timer->ticks_to_expire)
            {
                break;
            }

            // Decrement ticks_elapsed and collect expired ticks.
            ticks_elapsed -= p_timer->ticks_to_expire;
            ticks_expired += p_timer->ticks_to_expire;

            // Move to next timer.
            p_previous_timer = p_timer;
            p_timer = p_timer->next;

            // Execute Task.
            timeout_handler_exec(p_previous_timer);
        }

        // Prepare to queue the ticks expired in the m_ticks_elapsed queue.
        if (m_ticks_elapsed_q_read_ind == m_ticks_elapsed_q_write_ind)
        {
            // The read index of the queue is equal to the write index. This means the new
            // value of ticks_expired should be stored at a new location in the m_ticks_elapsed
            // queue (which is implemented as a double buffer).

            // Check if there will be a queue overflow.
            if (++m_ticks_elapsed_q_write_ind == CONTEXT_QUEUE_SIZE_MAX)
            {
                // There will be a queue overflow. Hence the write index should point to the start
                // of the queue.
                m_ticks_elapsed_q_write_ind = 0;
            }
        }

        // Queue the ticks expired.
        m_ticks_elapsed[m_ticks_elapsed_q_write_ind] = ticks_expired;

        timer_list_handler_sched();
    }
}
/**@brief Function for checking for expired timers.
 */
static void timer_timeouts_check(void)
{
    // Handle expired of timer 
    if (m_timer_id_head != TIMER_NULL)
    {
        uint32_t ticks_expired;
        uint8_t  ticks_elapsed_last;

        // Initialize actual elapsed ticks being consumed to 0 
        ticks_expired = 0;

        // Queue the ticks elapsed (to make the value context safe)
        ticks_elapsed_last = m_ticks_elapsed_last + 1;
        if (ticks_elapsed_last == CONTEXT_QUEUE_SIZE_MAX)
        {
            ticks_elapsed_last = 0;
        }

        if (ticks_elapsed_last != m_ticks_elapsed_first)
        {
            app_timer_id_t timer_id;
            uint32_t       ticks_elapsed;

            // Ticks_elapsed is collected here, job will use it
            ticks_elapsed = ticks_diff_get(rtc1_counter_get(), m_ticks_latest);

            // Auto variable containing the head of timers expiring 
            timer_id = m_timer_id_head;

            // Expire all timers within ticks_elapsed and collect ticks_expired 
            while (timer_id != TIMER_NULL)
            {
                timer_node_t * p_timer;

                // Auto variable for current timer node 
                p_timer = &mp_nodes[timer_id];

                // Do nothing if timer did not expire 
                if (ticks_elapsed < p_timer->ticks_to_expire)
                {
                    break;
                }

                // Decrement ticks_elapsed and collect expired ticks 
                ticks_elapsed -= p_timer->ticks_to_expire;
                ticks_expired += p_timer->ticks_to_expire;

                // Move to next timer 
                timer_id = p_timer->next;

                // Execute Task 
                timeout_handler_exec(p_timer);
            }
        }

        // Queue the elapsed value 
        m_ticks_elapsed[m_ticks_elapsed_last] = ticks_expired;
        m_ticks_elapsed_last                  = ticks_elapsed_last;

        timer_list_handler_sched();
    }
}