/**
 * 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.
 *
 */
#include "app_timer.h"
#include "nrf_atfifo.h"
#include "nrf_sortlist.h"
#include "nrf_delay.h"
#if APP_TIMER_WITH_PROFILER
#include "app_util_platform.h"
#endif
#if APP_TIMER_CONFIG_USE_SCHEDULER
#include "app_scheduler.h"
#endif
#include <stddef.h>
#define NRF_LOG_MODULE_NAME APP_TIMER_LOG_NAME
#if APP_TIMER_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       APP_TIMER_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR  APP_TIMER_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR APP_TIMER_CONFIG_DEBUG_COLOR
#else //APP_TIMER_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL       0
#endif //APP_TIMER_CONFIG_LOG_ENABLED
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

#include "drv_rtc.h"

/**
 * Maximum possible relative value is limited by safe window to detect cases when requested
 * compare event has already occured.
 */
#define APP_TIMER_SAFE_WINDOW APP_TIMER_TICKS(APP_TIMER_SAFE_WINDOW_MS)

#define APP_TIMER_RTC_MAX_VALUE   (DRV_RTC_MAX_CNT - APP_TIMER_SAFE_WINDOW)

static drv_rtc_t m_rtc_inst = DRV_RTC_INSTANCE(1);

#if APP_TIMER_WITH_PROFILER
static uint8_t m_max_user_op_queue_utilization;     /**< Maximum observed timer user operations queue utilization. */
static uint8_t m_current_user_op_queue_utilization; /**< Currently observed timer user operations queue utilization. */
#endif /* APP_TIMER_WITH_PROFILER */

/**
 * @brief Timer requests types.
 */
typedef enum
{
    TIMER_REQ_START,
    TIMER_REQ_STOP,
    TIMER_REQ_STOP_ALL
} app_timer_req_type_t;

/**
 * @brief Operation request structure.
 */
typedef struct
{
    app_timer_req_type_t type;    /**< Request type. */
    app_timer_t *        p_timer; /**< Timer instance. */
} timer_req_t;

static app_timer_t * volatile mp_active_timer; /**< Timer currently handled by RTC driver. */
static bool                   m_global_active; /**< Flag used to globally disable all timers. */
static uint64_t m_base_counter;
static uint64_t m_stamp64;

/* Request FIFO instance. */
NRF_ATFIFO_DEF(m_req_fifo, timer_req_t, APP_TIMER_CONFIG_OP_QUEUE_SIZE);

/* Sortlist instance. */
static bool compare_func(nrf_sortlist_item_t * p_item0, nrf_sortlist_item_t *p_item1);
NRF_SORTLIST_DEF(m_app_timer_sortlist, compare_func); /**< Sortlist used for storing queued timers. */

/**
 * @brief Return current 64 bit timestamp
 */
static uint64_t get_now(void)
{
    uint64_t now = m_base_counter + drv_rtc_counter_get(&m_rtc_inst);

    /* it is possible that base was not updated and overflow occured, in that case 'now' will be
     * 24bit value behind. Additional timestamp updated on every 24 bit period is used to detect
     * that case. Apart from that 'now' should never be behind previously read timestamp.
     */
    if (now < m_stamp64) {
        now += (DRV_RTC_MAX_CNT + 1);
    }

    return now;
}
/**
 * @brief Function used for comparing items in sorted list.
 */
static inline bool compare_func(nrf_sortlist_item_t * p_item0, nrf_sortlist_item_t *p_item1)
{
    app_timer_t * p0 = CONTAINER_OF(p_item0, app_timer_t, list_item);
    app_timer_t * p1 = CONTAINER_OF(p_item1, app_timer_t, list_item);

    uint64_t p0_end = p0->end_val;
    uint64_t p1_end = p1->end_val;
    return (p0_end <= p1_end) ? true : false;
}

#if APP_TIMER_CONFIG_USE_SCHEDULER
static void scheduled_timeout_handler(void * p_event_data, uint16_t event_size)
{
    ASSERT(event_size == sizeof(app_timer_event_t));
    app_timer_event_t const * p_timer_event = (app_timer_event_t *)p_event_data;

    p_timer_event->timeout_handler(p_timer_event->p_context);
}
#endif

/**
 * @brief Function called on timer expiration
 * If end value is not reached it is assumed that it was partial expiration and time is put back
 * into the list. Otherwise function calls user handler if timer was not stopped before. If timer
 * is in repeated mode then timer is rescheduled.
 *
 * @param p_timer Timer instance.
 *
 * @return True if reevaluation of sortlist needed (becasue it was updated).
 */
static bool timer_expire(app_timer_t * p_timer)
{
    ASSERT(p_timer->handler);
    bool ret = false;

    if ((m_global_active == true) && (p_timer != NULL) && (p_timer->active))
    {
        if (get_now() >= p_timer->end_val) {
            /* timer expired */
            if (p_timer->repeat_period == 0)
            {
                p_timer->active = false;
            }
    #if APP_TIMER_CONFIG_USE_SCHEDULER
            app_timer_event_t timer_event;

            timer_event.timeout_handler = p_timer->handler;
            timer_event.p_context       = p_timer->p_context;
            uint32_t err_code = app_sched_event_put(&timer_event,
                                                    sizeof(timer_event),
                                                    scheduled_timeout_handler);
            APP_ERROR_CHECK(err_code);
    #else
            NRF_LOG_DEBUG("Timer expired (context: %d)", (uint32_t)p_timer->p_context)
            p_timer->handler(p_timer->p_context);
    #endif
            /* check active flag as it may have been stopped in the user handler */
            if ((p_timer->repeat_period) && (p_timer->active))
            {
                p_timer->end_val += p_timer->repeat_period;
                nrf_sortlist_add(&m_app_timer_sortlist, &p_timer->list_item);
                ret = true;
            }
        }
        else
        {
            nrf_sortlist_add(&m_app_timer_sortlist, &p_timer->list_item);
            ret = true;
        }
    }
    return ret;
}

/**
 * @brief Function is configuring RTC driver to trigger timeout interrupt for given timer.
 *
 * It is possible that RTC driver will indicate that timeout already occured. In that case timer
 * expires and function indicates that RTC was not configured.
 *
 * @param          p_timer Timer instance.
 * @param [in,out] p_rerun Flag indicating that sortlist reevaluation is required.
 *
 * @return True if RTC was successfully configured, false if timer already expired and RTC was not
 *         configured.
 *
 */
static bool rtc_schedule(app_timer_t * p_timer, bool * p_rerun)
{
    ret_code_t ret = NRF_ERROR_TIMEOUT;
    *p_rerun = false;
    int64_t remaining = (int64_t)(p_timer->end_val - get_now());

    if (remaining > 0) {
        uint32_t cc_val = ((uint32_t)remaining > APP_TIMER_RTC_MAX_VALUE) ?
                (app_timer_cnt_get() + APP_TIMER_RTC_MAX_VALUE) : p_timer->end_val;

        ret = drv_rtc_windowed_compare_set(&m_rtc_inst, 0, cc_val, APP_TIMER_SAFE_WINDOW);
        NRF_LOG_DEBUG("Setting CC to 0x%08x (err: %d)", cc_val & DRV_RTC_MAX_CNT, ret);
        if (ret == NRF_SUCCESS)
        {
            return true;
        }
    }
    else
    {
        drv_rtc_compare_disable(&m_rtc_inst, 0);
    }

    if (ret == NRF_ERROR_TIMEOUT)
    {
        *p_rerun = timer_expire(p_timer);
    }
    else
    {
        NRF_LOG_ERROR("Unexpected error: %d", ret);
        ASSERT(0);
    }

    return false;
}

static inline app_timer_t * sortlist_pop(void)
{
    nrf_sortlist_item_t * p_next_item = nrf_sortlist_pop(&m_app_timer_sortlist);
    return p_next_item ? CONTAINER_OF(p_next_item, app_timer_t, list_item) : NULL;
}

static inline app_timer_t * sortlist_peek(void)
{
    nrf_sortlist_item_t const * p_next_item = nrf_sortlist_peek(&m_app_timer_sortlist);
    return p_next_item ? CONTAINER_OF(p_next_item, app_timer_t, list_item) : NULL;
}

/**
 * @brief Function for deactivating all timers which are in the sorted list (active timers).
 */
static void sorted_list_stop_all(void)
{
    app_timer_t * p_next;
    do
    {
        p_next = sortlist_pop();
        if (p_next)
        {
            p_next->active = false;
        }
    } while (p_next);
}

/**
 * @brief Function for handling RTC counter overflow.
 *
 * Increment base counter used to calculate 64 bit timestamp.
 */
static void on_overflow_evt(void)
{
    NRF_LOG_DEBUG("Overflow EVT");
    m_base_counter += (DRV_RTC_MAX_CNT + 1);
}

/**
 * #brief Function for handling RTC compare event - active timer expiration.
 */
static void on_compare_evt(drv_rtc_t const * const  p_instance)
{
    if (mp_active_timer)
    {
        /* If assert fails it suggests that safe window should be increased. */
        ASSERT(app_timer_cnt_diff_compute(drv_rtc_counter_get(p_instance),
                                          drv_rtc_compare_get(p_instance, 0)) < APP_TIMER_SAFE_WINDOW);

        NRF_LOG_INST_DEBUG(mp_active_timer->p_log, "Compare EVT");
        UNUSED_RETURN_VALUE(timer_expire(mp_active_timer));
        mp_active_timer = NULL;
    }
    else
    {
        NRF_LOG_WARNING("Compare event but no active timer (already stopped?)");
    }
}

/**
 * @brief Channel 1 is triggered in the middle of 24 bit period to updated control timestamp in
 * place where there is no risk of overflow.
 */
static void on_compare1_evt(drv_rtc_t const * const  p_instance)
{
    m_stamp64 = get_now();
}

/**
 * @brief Function updates RTC.
 *
 * Function is called at the end of RTC interrupt when all new user request and/or timer expiration
 * occured. It configures RTC if there is any pending timer, reconfigures if the are timers with
 * shorted timeout than active one or stops RTC if there is no active timers.
 */
static void rtc_update(drv_rtc_t const * const  p_instance)
{
    while(1)
    {
        app_timer_t * p_next = sortlist_peek();
        bool rtc_reconf = false;
        if (p_next) //Candidate for active timer
        {
            if (mp_active_timer == NULL)
            {
                //There is no active timer so candidate will become active timer.
                rtc_reconf = true;
            }
            else if (p_next->end_val < mp_active_timer->end_val)
            {
                //Candidate has shorter timeout than current active timer. Candidate will replace active timer.
                //Active timer is put back into sorted list.
                rtc_reconf = true;
                if (mp_active_timer->active)
                {
                    NRF_LOG_INST_DEBUG(mp_active_timer->p_log, "Timer preempted.");
                    nrf_sortlist_add(&m_app_timer_sortlist, &mp_active_timer->list_item);
                }
            }

            if (rtc_reconf)
            {
                bool rerun;
                p_next = sortlist_pop();
                NRF_LOG_INST_DEBUG(p_next->p_log, "Activating timer (CC:%d/%08x).", p_next->end_val, p_next->end_val);
                if (rtc_schedule(p_next, &rerun))
                {
                    if (!APP_TIMER_KEEPS_RTC_ACTIVE && (mp_active_timer == NULL))
                    {
                        drv_rtc_start(p_instance);
                    }
                    mp_active_timer = p_next;

                    if (rerun == false)
                    {
                        //RTC was successfully updated and sortlist was not updated. Function can be terminated.
                        break;
                    }
                }
                else
                {
                    //If RTC driver indicated that timeout already occured a new candidate will be taken from sorted list.
                    NRF_LOG_INST_DEBUG(p_next->p_log,"Timer expired before scheduled to RTC.");
                    mp_active_timer = NULL;
                }
            }
            else
            {
                //RTC will not be updated. Function can terminate.
                break;
            }
        }
        else //No candidate for active timer.
        {
            if (!APP_TIMER_KEEPS_RTC_ACTIVE && (mp_active_timer == NULL))
            {
                drv_rtc_stop(p_instance);
            }
            break;
        }
    }
}

/**
 * @brief Function for processing user requests.
 *
 * Function is called only in the context of RTC interrupt.
 */
static void timer_req_process(drv_rtc_t const * const  p_instance)
{
    nrf_atfifo_item_get_t fifo_ctx;
    timer_req_t *         p_req = nrf_atfifo_item_get(m_req_fifo, &fifo_ctx);

    while (p_req)
    {
        switch (p_req->type)
        {
            case TIMER_REQ_START:
                if (!p_req->p_timer->active)
                {
                    p_req->p_timer->active = true;
                    nrf_sortlist_add(&m_app_timer_sortlist, &(p_req->p_timer->list_item));
                    NRF_LOG_INST_DEBUG(p_req->p_timer->p_log,"Start request (expiring at %d/0x%08x).",
                                                  p_req->p_timer->end_val, p_req->p_timer->end_val);
                }
                break;
            case TIMER_REQ_STOP:
                if (p_req->p_timer == mp_active_timer)
                {
                    mp_active_timer = NULL;
                }
                else
                {
                    bool found = nrf_sortlist_remove(&m_app_timer_sortlist, &(p_req->p_timer->list_item));
                    if (!found)
                    {
                         NRF_LOG_INFO("Timer not found on sortlist (stopping expired timer).");
                    }
                }
                NRF_LOG_INST_DEBUG(p_req->p_timer->p_log,"Stop request.");
                break;
            case TIMER_REQ_STOP_ALL:
                sorted_list_stop_all();
                m_global_active = true;
                NRF_LOG_INFO("Stop all request.");
                break;
            default:
                break;
        }
#if APP_TIMER_WITH_PROFILER
        CRITICAL_REGION_ENTER();
#endif
        UNUSED_RETURN_VALUE(nrf_atfifo_item_free(m_req_fifo, &fifo_ctx));
#if APP_TIMER_WITH_PROFILER
        if (m_max_user_op_queue_utilization < m_current_user_op_queue_utilization)
        {
            m_max_user_op_queue_utilization = m_current_user_op_queue_utilization;
        }
        --m_current_user_op_queue_utilization;
        CRITICAL_REGION_EXIT();
#endif /* APP_TIMER_WITH_PROFILER */
        p_req = nrf_atfifo_item_get(m_req_fifo, &fifo_ctx);
    }
}

static void rtc_irq(drv_rtc_t const * const  p_instance)
{
    if (drv_rtc_overflow_pending(p_instance))
    {
        on_overflow_evt();
    }
    if (drv_rtc_compare_pending(p_instance, 0))
    {
        on_compare_evt(p_instance);
    }
    if (drv_rtc_compare_pending(p_instance, 1))
    {
        on_compare1_evt(p_instance);
    }

    timer_req_process(p_instance);
    rtc_update(p_instance);
}

/**
 * @brief Function for triggering processing user requests.
 *
 * @note All user requests are processed in a single context - RTC interrupt.
 */
static inline void timer_request_proc_trigger(void)
{
    drv_rtc_irq_trigger(&m_rtc_inst);
}

/**
 * @brief Function for putting user request into the request queue
 */
static ret_code_t timer_req_schedule(app_timer_req_type_t type, app_timer_t * p_timer)
{
    nrf_atfifo_item_put_t fifo_ctx;
    timer_req_t * p_req;
#if APP_TIMER_WITH_PROFILER
    CRITICAL_REGION_ENTER();
#endif
    p_req = nrf_atfifo_item_alloc(m_req_fifo, &fifo_ctx);
#if APP_TIMER_WITH_PROFILER
    if (p_req)
    {
        ++m_current_user_op_queue_utilization;
    }
    CRITICAL_REGION_EXIT();
#endif /* APP_TIMER_WITH_PROFILER */
    if (p_req)
    {
        p_req->type    = type;
        p_req->p_timer = p_timer;
        if (nrf_atfifo_item_put(m_req_fifo, &fifo_ctx))
        {
            timer_request_proc_trigger();
        }
        else
        {
            NRF_LOG_WARNING("Scheduling interrupted another scheduling.");
        }
        return NRF_SUCCESS;
    }
    else
    {
        return NRF_ERROR_NO_MEM;
    }
}

ret_code_t app_timer_init(void)
{
    ret_code_t err_code;
    drv_rtc_config_t config = {
        .prescaler          = APP_TIMER_CONFIG_RTC_FREQUENCY,
        .interrupt_priority = APP_TIMER_CONFIG_IRQ_PRIORITY
    };

    err_code = NRF_ATFIFO_INIT(m_req_fifo);
    if (err_code != NRFX_SUCCESS)
    {
        return err_code;
    }

    err_code = drv_rtc_init(&m_rtc_inst, &config, rtc_irq);
    if (err_code != NRFX_SUCCESS)
    {
        return err_code;
    }
    drv_rtc_overflow_enable(&m_rtc_inst, true);
    drv_rtc_compare_set(&m_rtc_inst, 1, DRV_RTC_MAX_CNT >> 1, true);
    if (APP_TIMER_KEEPS_RTC_ACTIVE)
    {
        drv_rtc_start(&m_rtc_inst);
    }

    m_global_active = true;
    return err_code;
}

ret_code_t app_timer_create(app_timer_id_t const *      p_timer_id,
                            app_timer_mode_t            mode,
                            app_timer_timeout_handler_t timeout_handler)
{
    ASSERT(p_timer_id);
    ASSERT(timeout_handler);

    if (timeout_handler == NULL)
    {
        return NRF_ERROR_INVALID_PARAM;
    }

    app_timer_t * p_t = (app_timer_t *) *p_timer_id;
    p_t->handler = timeout_handler;
    p_t->repeat_period = (mode == APP_TIMER_MODE_REPEATED) ? 1 : 0;
    return NRF_SUCCESS;
}

ret_code_t app_timer_start(app_timer_t * p_timer, uint32_t timeout_ticks, void * p_context)
{
    ASSERT(p_timer);
    app_timer_t * p_t = (app_timer_t *) p_timer;

    if (p_t->active)
    {
        return NRF_SUCCESS;
    }

    p_t->p_context = p_context;
    p_t->end_val = get_now() + timeout_ticks;

    if (p_t->repeat_period)
    {
        p_t->repeat_period = timeout_ticks;
    }

    return timer_req_schedule(TIMER_REQ_START, p_t);
}


ret_code_t app_timer_stop(app_timer_t * p_timer)
{
    ASSERT(p_timer);
    app_timer_t * p_t = (app_timer_t *) p_timer;
    p_t->active = false;

    return timer_req_schedule(TIMER_REQ_STOP, p_t);
}

ret_code_t app_timer_stop_all(void)
{
    //block timer globally
    m_global_active = false;

    return timer_req_schedule(TIMER_REQ_STOP_ALL, NULL);
}

#if APP_TIMER_WITH_PROFILER
uint8_t app_timer_op_queue_utilization_get(void)
{
    return m_max_user_op_queue_utilization;
}
#endif /* APP_TIMER_WITH_PROFILER */

uint32_t app_timer_cnt_diff_compute(uint32_t   ticks_to,
                                    uint32_t   ticks_from)
{
    return ((ticks_to - ticks_from) & RTC_COUNTER_COUNTER_Msk);
}

uint32_t app_timer_cnt_get(void)
{
    return drv_rtc_counter_get(&m_rtc_inst);
}

void app_timer_pause(void)
{
    drv_rtc_stop(&m_rtc_inst);
}

void app_timer_resume(void)
{
    drv_rtc_start(&m_rtc_inst);
}
