/**
 * 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.
 *
 * This timer should be used with coution 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

#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;

/**
 * Those debug variables are set but never used.
 */
static ets_timer_func_t *debug_timerfn;
static ets_timer_t *debug_timer;

/**
 * Timer queue
 */
static QueueHandle_t queue;

/**
 * Unknown stuff
 * Some counters
 */
static uint8_t queue_len = 0;
static uint8_t buf_param_index = 0;
static uint64_t _unknown_buf[4];


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;
}

/**
 * .Lfunc004
 */
static void set_alarm_value(uint32_t value)
{
    TIMER_FRC2.ALARM = value;
}

/**
 * .Lfunc005
 *
 * 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);
    }
}

/**
 * .Lfunc006
 *
 * 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) {
        value *= 1000;
    }

    if (value != 0) {
        // Why to do multiplication for values greater than 858
        // and do 'shift and add' for other?
        // What is the magic number 858 ?
        if (858 < value) {
            ticks = (value << 2) + value;  // ticks = value * 5
        } else {
            // It is the same as just multiply by 5
            // No idea why to do it this way
            ticks = (value * 5000000) / 1000000;
        }
    }

    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);
}

/**
 * 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.
 * This function is not called from the interrupt regardless of its name.
 */
void sdk_ets_timer_handler_isr()
{
    vPortEnterCritical();
    int32_t ticks = TIMER_FRC2.COUNT;
    while (timer_list) {
        if (((int32_t)timer_list->fire_ticks - ticks) < 1) {
            debug_timerfn = timer_list->callback;
            debug_timer = timer_list;

            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();
}


/**
 * .Lfunc001
 *
 * Mysterious function.
 * It seems like it keeps track of the queue size and returns 0 if
 * the queue gets longer than 5.
 * Also it seems like it returns some buffers that are used in the queue.
 * But those buffers in the queue are not used at all.
 * If anybody knows what is this all about please leave a comment.
 */
static void* IRAM func001()
{
    uint8_t *p = (uint8_t*)_unknown_buf;

    queue_len++;
    if (queue_len < 5) {
        p += (buf_param_index*8);
        if (buf_param_index + 1 < 4) {
            buf_param_index++;
        } else {
            buf_param_index = 0;
        }
        return p;
    } else {
        queue_len--;
        return 0;
    }
}

/**
 * .Lfunc002
 */
static void IRAM frc2_isr()
{
    void *p = func001();

    if (!p) {
        printf("TIMQ_NUL\n");
        return;
    }

    BaseType_t task_woken = 0;

    BaseType_t result = xQueueGenericSendFromISR(queue, p, &task_woken, 0);
    if (result != pdTRUE) {
        printf("TIMQ_FL:%d!!", (uint32_t)result);
    }
    if (task_woken) {
        vTaskSwitchContext();
    }
}

/**
 * .Lfunc003
 */
static void func003()
{
    vPortEnterCritical();
    queue_len--;
    vPortExitCritical();
}

/**
 * .Lfunc007
 *
 * Timer task
 */
static void timer_task(void* param)
{
    uint32_t *local0;
    while (true) {
        if (xQueueGenericReceive(queue, &local0, 0xffffffff, 0) == 1) {
            sdk_ets_timer_handler_isr();
            func003();
        }
    }
}

void sdk_ets_timer_init()
{
    timer_list = 0;

    _xt_isr_attach(INUM_TIMER_FRC2, frc2_isr);

    queue = xQueueGenericCreate(/*length=*/4, /*item size=*/4,
            /*queu type: base*/0);

    TaskHandle_t handle = 0;

    /* Original code calls xTaskGenericCreate:
     * xTaskGenericCreate(timer_task, "rtc_timer_task", 200, 0, 12, &handle,
     *     NULL, NULL);
     */
    xTaskCreate(timer_task, "rtc_timer_task", 200, 0, 12, &handle);
    printf("frc2_timer_task_hdl:%x, prio:%d, stack:%d\n", (uint32_t)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 /* OPEN_LIBMAIN_ETS_TIMER */