/**
 * Recreated Espressif libmain ets_timer.o contents.
 *
 * Copyright (C) 2015 Espressif Systems. Derived from MIT Licensed SDK libraries.
 * BSD Licensed as described in the file LICENSE
 *
 * Copyright (c) 2016 sheinz (https://github.com/sheinz)
 *
 * This module seems to be adapted from NONOS SDK by Espressif to fit into
 * RTOS SDK. Function sdk_ets_timer_handler_isr is no longer an ISR handler
 * but still holds its name. Espressif just added a task that receives events
 * from the real FRC2 timer ISR handler and calls former ISR handler.
 * So, timer callbacks are called from the task context rather than an interrupt.
 *
 * Modifications from the original reverse engineered version:
 *  - FreeRTOS queue is replaced with Task notifications.
 *  - Removed unknown queue length monitoring and parameters allocation.
 *  - Removed unused debug variables
 *  - xTaskGenericCreate is replaced with xTaskCreate
 *  - simplified time to ticks conversion (simply multiply by 5)
 *
 * This timer should be used with caution together with other tasks. As the
 * timer callback is executed within timer task context, access to data that
 * other tasks accessing should be protected.
 */
#include "open_esplibs.h"

#if OPEN_LIBMAIN_ETS_TIMER

#if 0

#include "etstimer.h"
#include "espressif/osapi.h"

void sdk_ets_timer_setfn(ETSTimer *timer, ETSTimerFunc *func, void *parg) {
    sdk_os_timer_setfn(timer, func, parg);
}

void sdk_ets_timer_arm(ETSTimer *timer, uint32_t value, bool repeat_flag) {
    sdk_os_timer_arm(timer, value, repeat_flag);
}

void sdk_ets_timer_arm_ms_us(ETSTimer *timer, uint32_t value,
                             bool repeat_flag, bool value_in_ms) {
    sdk_os_timer_arm(timer, value * 1000 + value_in_ms, repeat_flag);
}

void sdk_ets_timer_disarm(ETSTimer *timer) {
    sdk_os_timer_disarm(timer);
}

void sdk_ets_timer_init() {
}

#else

#include "open_esplibs.h"
#include <stdint.h>
#include <stdbool.h>
#include <esp/timer_regs.h>
#include <FreeRTOS.h>
#include <timers.h>
#include <queue.h>
#include <stdio.h>

typedef void ets_timer_func_t(void *);

/**
 * This structure is used for both timers: ets_timer.c and timer.c
 */
typedef struct ets_timer_st {
    struct ets_timer_st  *next;
    TimerHandle_t timer_handle;  // not used in ets_timer.c
    uint32_t fire_ticks;         // FRC2 timer value when timer should fire
    uint32_t period_ticks;       // timer value in FRC2 ticks for rpeating timers
    ets_timer_func_t *callback;
    bool repeat;                 // not used in ets_timer.c
    void *timer_arg;
} ets_timer_t;

/**
 * Special values of ets_timer_t::next field
 */
#define ETS_TIMER_NOT_ARMED (ets_timer_t*)(0xffffffff)
#define ETS_TIMER_LIST_END  (ets_timer_t*)(0)

/**
 * Linked list of timers
 */
static ets_timer_t* timer_list = 0;

static TaskHandle_t task_handle = NULL;

void sdk_ets_timer_setfn(ets_timer_t *timer, ets_timer_func_t *func, void *parg)
{
    timer->callback = func;
    timer->timer_arg = parg;
    timer->fire_ticks = 0;
    timer->period_ticks = 0;
    timer->next = ETS_TIMER_NOT_ARMED;
}

static inline void set_alarm_value(uint32_t value)
{
    TIMER_FRC2.ALARM = value;
}

/**
 * Set timer alarm and make sure the alarm is set in the future
 * and will not be missed by the timer.
 */
static void set_alarm(uint32_t ticks)
{
    uint32_t curr_time = TIMER_FRC2.COUNT;
    int32_t delta = (int32_t)ticks - curr_time;
    if ((delta - 40) < 1) {
        if (delta < 1) {
            set_alarm_value(curr_time + 40);
        } else {
            set_alarm_value(ticks + 44);
        }
    } else {
        set_alarm_value(ticks);
    }
}

/**
 *
 * Pending timer list example:
 *
 * | Timer:      | T0 | T1 | T2 | T3 |
 * |-------------|----|----|----|----|
 * | fire_ticks: | 10 | 20 | 30 | 40 |
 * | next:       | T1 | T2 | T3 | 0  |
 *
 *
 * For example we need to add a timer that should fire at 25 ticks:
 *
 * | Timer:      | T0 | T1  | new | T2 | T3 |
 * |-------------|----|-----|-----|----|----|
 * | fire_ticks: | 10 | 20  | 25  | 30 | 40 |
 * | next:       | T1 | new | T2  | T3 | 0  |
 *
 * We squeeze the timer into the list so the list will always remain sorted
 *
 * Note: if add the same timer twice the system halts
*/
static void add_pending_timer(uint32_t ticks, ets_timer_t *timer)
{
    ets_timer_t *prev = 0;
    ets_timer_t *curr = timer_list;
    while (curr) {
        if (((int32_t)ticks - (int32_t)curr->fire_ticks) < 1) {
            // found a timer that should fire later
            // so our timer should fire earlier
            break;
        }
        prev = curr;
        curr = curr->next;
    }

    timer->next = curr;
    timer->fire_ticks = ticks;

    if (prev != 0) {
        prev->next = timer;
    } else {
        // Our timer is the first in the line to fire
        timer_list = timer;
        set_alarm(ticks);
    }

    // This situation might happen if adding the same timer twice
    if (timer == timer->next) {
        // This seems like an error: %s is used for line number
        // In the recent SDK Espressif fixed the format to "%s %u\n"
        printf("%s %s \n", "ets_timer.c", (char*)209);
        while (1);
    }
}

/**
 * In the Espressif SDK 0.9.9 if try to arm already armed timer the system halts
 * with error message. In the later SDK version Espressif changed the behavior.
 * If the timer was previously armed it is disarmed and then armed without errors.
 * This version recreates behavior of SDK 0.9.9
 */
void sdk_ets_timer_arm_ms_us(ets_timer_t *timer, uint32_t value,
        bool repeat_flag, bool value_in_ms)
{
    uint32_t ticks = 0;

    if (timer->next != ETS_TIMER_NOT_ARMED) {
        // The error message doesn't tell what is wrong
        printf("arm new %x %x\n", (uint32_t)timer, (uint32_t)timer->next);
        while(1);  // halt
    }

    if (value_in_ms) {
        ticks = value * 5000;
    } else {
        ticks = value * 5;
    }

    if (repeat_flag) {
        timer->period_ticks = ticks;
    }
    vPortEnterCritical();
    add_pending_timer(TIMER_FRC2.COUNT + ticks, timer);
    vPortExitCritical();
}

void sdk_ets_timer_arm(ets_timer_t *timer, uint32_t milliseconds,
        bool repeat_flag)
{
    sdk_ets_timer_arm_ms_us(timer, milliseconds, repeat_flag,
            /*value in ms=*/true);
}

void sdk_ets_timer_arm_us(ets_timer_t *timer, uint32_t useconds,
        bool repeat_flag)
{
    sdk_ets_timer_arm_ms_us(timer, useconds, repeat_flag,
            /*value in ms=*/false);
}

/**
 * Function removes a timer from the pending timers list.
 */
void sdk_ets_timer_disarm(ets_timer_t *timer)
{
    vPortEnterCritical();
    ets_timer_t *curr = timer_list;
    ets_timer_t *prev = 0;
    while (curr) {
        if (curr == timer) {
            if (prev) {
                prev->next = curr->next;
            } else {
                timer_list = curr->next;
            }
            break;
        }
        prev = curr;
        curr = curr->next;
    }
    vPortExitCritical();
    timer->next = ETS_TIMER_NOT_ARMED;
    timer->period_ticks = 0;
}

/**
 * Check the list of pending timers for expired ones and process them.
 */
static inline void process_pending_timers()
{
    vPortEnterCritical();
    int32_t ticks = TIMER_FRC2.COUNT;
    while (timer_list) {
        if (((int32_t)timer_list->fire_ticks - ticks) < 1) {
            ets_timer_t *timer = timer_list;
            timer_list = timer->next;
            timer->next = ETS_TIMER_NOT_ARMED;

            vPortExitCritical();
            timer->callback(timer->timer_arg);
            vPortEnterCritical();

            if (timer->next == ETS_TIMER_NOT_ARMED) {
                if (timer->period_ticks) {
                    timer->fire_ticks = timer->fire_ticks + timer->period_ticks;
                    add_pending_timer(timer->fire_ticks, timer);
                }
            }
            ticks = TIMER_FRC2.COUNT;
        } else {
            if (timer_list) {
                set_alarm(timer_list->fire_ticks);
            }
            break;
        }
    }
    vPortExitCritical();
}

/**
 * .Lfunc002
 */
static void IRAM frc2_isr(void *arg)
{
    BaseType_t task_woken = 0;

    BaseType_t result = xTaskNotifyFromISR(task_handle, 0, eNoAction, &task_woken);
    if (result != pdTRUE) {
        printf("TIMQ_FL:%d!!", (uint32_t)result);
    }

    portEND_SWITCHING_ISR(task_woken);
}

/**
 * .Lfunc007
 *
 * Timer task
 */
static void timer_task(void* param)
{
    while (true) {
        if (xTaskNotifyWait(0, 0, NULL, portMAX_DELAY) == pdTRUE) {
            process_pending_timers();
        }
    }
}

void sdk_ets_timer_init()
{
    timer_list = 0;

    _xt_isr_attach(INUM_TIMER_FRC2, frc2_isr, NULL);

    /* Original code calls xTaskGenericCreate:
     * xTaskGenericCreate(task_handle, "rtc_timer_task", 200, 0, 12, &handle,
     *     NULL, NULL);
     */
    xTaskCreate(timer_task, "rtc_timer_task", 200, 0, 12, &task_handle);
    printf("frc2_timer_task_hdl:%p, prio:%d, stack:%d\n", task_handle, 12, 200);

    TIMER_FRC2.ALARM = 0;
    TIMER_FRC2.CTRL =  VAL2FIELD(TIMER_CTRL_CLKDIV, TIMER_CLKDIV_16)
        | TIMER_CTRL_RUN;
    TIMER_FRC2.LOAD = 0;

    DPORT.INT_ENABLE |= DPORT_INT_ENABLE_TIMER1;

   _xt_isr_unmask(BIT(INUM_TIMER_FRC2));
}

#endif

#endif /* OPEN_LIBMAIN_ETS_TIMER */
