/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#include "plat_types.h"
#include "hwtimer_list.h"
#define IGNORE_HAL_TIMER_RAW_API_CHECK
#include "hal_timer_raw.h"
#include "hal_trace.h"
#include "stdio.h"
#include "cmsis.h"

#if defined(ROM_BUILD) && !defined(SIMU) && !defined(FPGA)
#error "The user of raw timer API must be unique. Now rom is using raw timer API."
#endif

#define CHECK_ACTIVE_NULL_IN_IRQ        1

#ifndef HWTIMER_NUM
#define HWTIMER_NUM                     10
#endif

//#define HWTIMER_TEST

enum HWTIMER_STATE_T {
    HWTIMER_STATE_FREE = 0,
    HWTIMER_STATE_ALLOC,
    HWTIMER_STATE_ACTIVE,
    HWTIMER_STATE_FIRED,
    HWTIMER_STATE_CALLBACK,

    HWTIMER_STATE_QTY
};

struct HWTIMER_T {
    enum HWTIMER_STATE_T state;
    struct HWTIMER_T *next;
    uint32_t time;
    HWTIMER_CALLBACK_T callback;
    void *param;
};

struct HWTIMER_LIST_T {
    struct HWTIMER_T timer[HWTIMER_NUM];
    struct HWTIMER_T *free;
    struct HWTIMER_T *active;
    struct HWTIMER_T *fired;
};

static struct HWTIMER_LIST_T hwtimer_list;
static uint32_t err_irq_active_null = 0;
static uint32_t err_irq_early = 0;

static void hwtimer_handler(uint32_t elapsed)
{
    struct HWTIMER_T *pre;
    struct HWTIMER_T *last;
    uint32_t lock = 0;

    lock = int_lock();

    if (hwtimer_list.active == NULL) {
        err_irq_active_null++;
        TRACE(1,"HWTIMER irq when active is null: might be deleted? %u", err_irq_active_null);
#if (CHECK_ACTIVE_NULL_IN_IRQ)
        ASSERT(hal_timer_is_enabled() == 0, "HWTIMER collapsed: irq when active is null");
#endif
        goto _exit;
    }
    // Update elapsed time
    elapsed = hal_timer_get_elapsed_time();
    if (hwtimer_list.active->time > elapsed + HAL_TIMER_LOAD_DELTA) {
        err_irq_early++;
        TRACE(1,"HWTIMER irq occurred early: old active timer might be deleted? %u", err_irq_early);
        ASSERT(hal_timer_is_enabled(), "HWTIMER collapsed: irq occurred too early");
        goto _exit;
    }

    if (elapsed > hwtimer_list.active->time) {
        elapsed -= hwtimer_list.active->time;
    } else {
        elapsed = 0;
    }
    pre = hwtimer_list.active;
    // TODO: Check state here ?
    pre->state = HWTIMER_STATE_FIRED;
    last = hwtimer_list.active->next;
    while (last && last->time <= elapsed) {
        elapsed -= last->time;
        pre = last;
        // TODO: Check state here ?
        pre->state = HWTIMER_STATE_FIRED;
        last = last->next;
    }
    pre->next = NULL;
    hwtimer_list.fired = hwtimer_list.active;
    hwtimer_list.active = last;
    if (last) {
        last->time -= elapsed;
        hal_timer_start(last->time);
#if (CHECK_ACTIVE_NULL_IN_IRQ)
    } else {
        hal_timer_stop();
#endif
    }

    while (hwtimer_list.fired) {
        last = hwtimer_list.fired;
        hwtimer_list.fired = last->next;
        // TODO: Check state here ?
        last->state = HWTIMER_STATE_CALLBACK;
        last->next = NULL;
        // Now this timer can be restarted, but not stopped or freed
        if (last->callback) {
            int_unlock(lock);
            last->callback(last->param);
            lock = int_lock();
        }
        if (last->state == HWTIMER_STATE_CALLBACK) {
            last->state = HWTIMER_STATE_ALLOC;
        }
    }

_exit:
    int_unlock(lock);
}

void hwtimer_init(void)
{
    int i;

    for (i = 0; i < HWTIMER_NUM - 1; i++) {
        hwtimer_list.timer[i].state = HWTIMER_STATE_FREE;
        hwtimer_list.timer[i].next = &hwtimer_list.timer[i + 1];
    }
    hwtimer_list.timer[HWTIMER_NUM - 1].next = NULL;
    hwtimer_list.free = &hwtimer_list.timer[0];
    hwtimer_list.active = NULL;
    hwtimer_list.fired = NULL;
    hal_timer_setup(HAL_TIMER_TYPE_ONESHOT, hwtimer_handler);
}

HWTIMER_ID hwtimer_alloc(HWTIMER_CALLBACK_T callback, void *param)
{
    struct HWTIMER_T *timer;
    uint32_t lock;

    timer = NULL;

    lock = int_lock();
    if (hwtimer_list.free != NULL) {
        timer = hwtimer_list.free;
        hwtimer_list.free = hwtimer_list.free->next;
    }
    int_unlock(lock);

    if (timer == NULL) {
        return NULL;
    }

    ASSERT(timer->state == HWTIMER_STATE_FREE, "HWTIMER-ALLOC: Invalid state: %d", timer->state);
    timer->state = HWTIMER_STATE_ALLOC;
    timer->callback = callback;
    timer->param = param;
    timer->next = NULL;

    return timer;
}

enum E_HWTIMER_T hwtimer_free(HWTIMER_ID id)
{
    enum E_HWTIMER_T ret;
    struct HWTIMER_T *timer;
    uint32_t lock;

    timer = (struct HWTIMER_T *)id;

    if (timer < &hwtimer_list.timer[0] || timer > &hwtimer_list.timer[HWTIMER_NUM - 1]) {
        return E_HWTIMER_INVAL_ID;
    }

    ret = E_HWTIMER_OK;

    lock = int_lock();

    if (timer->state != HWTIMER_STATE_ALLOC) {
        ret = E_HWTIMER_INVAL_ST;
        goto _exit;
    }
    timer->state = HWTIMER_STATE_FREE;

    timer->next = hwtimer_list.free;
    hwtimer_list.free = timer;

_exit:
    int_unlock(lock);

    return ret;
}

static enum E_HWTIMER_T __hwtimer_start_int(HWTIMER_ID id, int update, HWTIMER_CALLBACK_T callback, void *param, unsigned int ticks)
{
    enum E_HWTIMER_T ret;
    struct HWTIMER_T *timer;
    struct HWTIMER_T *pre;
    struct HWTIMER_T *next;
    uint32_t lock;
    uint32_t cur_time;

    timer = (struct HWTIMER_T *)id;

    if (timer < &hwtimer_list.timer[0] || timer > &hwtimer_list.timer[HWTIMER_NUM - 1]) {
        return E_HWTIMER_INVAL_ID;
    }

    if (ticks < HAL_TIMER_LOAD_DELTA) {
        ticks = HAL_TIMER_LOAD_DELTA;
    }

    ret = E_HWTIMER_OK;

    lock = int_lock();

    if (timer->state != HWTIMER_STATE_ALLOC && timer->state != HWTIMER_STATE_CALLBACK) {
        ret = E_HWTIMER_INVAL_ST;
        goto _exit;
    }
    timer->state = HWTIMER_STATE_ACTIVE;

    if (update) {
        timer->callback = callback;
        timer->param = param;
    }

    if (hwtimer_list.active == NULL) {
        timer->next = NULL;
        hwtimer_list.active = timer;
        hal_timer_start(ticks);
    } else {
        cur_time = hal_timer_get();
        ASSERT(cur_time <= hwtimer_list.active->time || cur_time <= HAL_TIMER_LOAD_DELTA,
            "HWTIMER-START collapsed: cur=%u active=%u",
            cur_time, hwtimer_list.active->time);
        if (cur_time > ticks) {
            hwtimer_list.active->time = cur_time - ticks;
            timer->next = hwtimer_list.active;
            hwtimer_list.active = timer;
            hal_timer_stop();
            hal_timer_start(ticks);
        } else {
            pre = hwtimer_list.active;
            next = hwtimer_list.active->next;
            ticks -= cur_time;
            while (next && next->time < ticks) {
                ticks -= next->time;
                pre = next;
                next = next->next;
            }
            if (next) {
                next->time -= ticks;
            }
            pre->next = timer;
            timer->next = next;
        }
    }
    timer->time = ticks;

_exit:
    int_unlock(lock);

    return ret;
}

enum E_HWTIMER_T hwtimer_start(HWTIMER_ID id, unsigned int ticks)
{
    return __hwtimer_start_int(id, 0, NULL, NULL, ticks);
}

enum E_HWTIMER_T hwtimer_update_then_start(HWTIMER_ID id, HWTIMER_CALLBACK_T callback, void *param, unsigned int ticks)
{
    return __hwtimer_start_int(id, 1, callback, param, ticks);
}

enum E_HWTIMER_T hwtimer_update(HWTIMER_ID id, HWTIMER_CALLBACK_T callback, void *param)
{
    enum E_HWTIMER_T ret;
    struct HWTIMER_T *timer;
    uint32_t lock;

    timer = (struct HWTIMER_T *)id;

    if (timer < &hwtimer_list.timer[0] || timer > &hwtimer_list.timer[HWTIMER_NUM - 1]) {
        return E_HWTIMER_INVAL_ID;
    }

    ret = E_HWTIMER_OK;

    lock = int_lock();

    if (timer->state == HWTIMER_STATE_ALLOC || timer->state == HWTIMER_STATE_CALLBACK) {
        timer->callback = callback;
        timer->param = param;
    } else {
        ret = E_HWTIMER_INVAL_ST;
    }

    int_unlock(lock);

    return ret;
}

enum E_HWTIMER_T hwtimer_stop(HWTIMER_ID id)
{
    enum E_HWTIMER_T ret;
    struct HWTIMER_T *timer;
    struct HWTIMER_T *pre;
    struct HWTIMER_T *next;
    uint32_t cur_time;
    uint32_t elapsed_time;
    uint32_t lock;

    timer = (struct HWTIMER_T *)id;

    if (timer < &hwtimer_list.timer[0] || timer > &hwtimer_list.timer[HWTIMER_NUM - 1]) {
        return E_HWTIMER_INVAL_ID;
    }

    ret = E_HWTIMER_OK;

    lock = int_lock();

    if (timer->state == HWTIMER_STATE_ALLOC) {
        // Already stopped
        goto _exit;
    } else if (timer->state == HWTIMER_STATE_ACTIVE) {
        // Active timer
        if (hwtimer_list.active == timer) {
            cur_time = hal_timer_get();
            ASSERT(cur_time <= hwtimer_list.active->time || cur_time <= HAL_TIMER_LOAD_DELTA,
                "HWTIMER-STOP collapsed: cur=%u active=%u",
                cur_time, hwtimer_list.active->time);
            hal_timer_stop();
            next = hwtimer_list.active->next;
            if (next) {
                if (cur_time == 0) {
                    elapsed_time = hal_timer_get_elapsed_time();
                    ASSERT(elapsed_time + HAL_TIMER_LOAD_DELTA >= hwtimer_list.active->time,
                        "HWTIMER-STOP collapsed: elapsed=%u active=%u",
                        elapsed_time, hwtimer_list.active->time);
                    if (elapsed_time > hwtimer_list.active->time) {
                        elapsed_time -= hwtimer_list.active->time;
                    } else {
                        elapsed_time = 0;
                    }
                    pre = next;
                    while (pre && pre->time <= elapsed_time) {
                        elapsed_time -= pre->time;
                        pre->time = 0;
                        pre = pre->next;
                    }
                    if (pre) {
                        pre->time -= elapsed_time;
                    }
                } else {
                    next->time += cur_time;
                }
                hal_timer_start(next->time);
            }
            hwtimer_list.active = next;
        } else if (hwtimer_list.active) {
            pre = hwtimer_list.active;
            next = hwtimer_list.active->next;
            while (next && next != timer) {
                pre = next;
                next = next->next;
            }
            if (next == timer) {
                pre->next = next->next;
                if (next->next) {
                    next->next->time += timer->time;
                }
            } else {
                ret = E_HWTIMER_NOT_FOUND;
            }
        } else {
            ret = E_HWTIMER_NOT_FOUND;
        }
        ASSERT(ret == E_HWTIMER_OK, "HWTIMER-STOP collapsed: active timer 0x%08x not in list 0x%08x",
            (uint32_t)timer, (uint32_t)hwtimer_list.active);
    } else if (timer->state == HWTIMER_STATE_FIRED) {
        // Fired timer
        if (hwtimer_list.fired == timer) {
            // The timer handler is preempted
            hwtimer_list.fired = hwtimer_list.fired->next;
        } else if (hwtimer_list.fired) {
            pre = hwtimer_list.fired;
            next = hwtimer_list.fired->next;
            while (next && next != timer) {
                pre = next;
                next = next->next;
            }
            if (next == timer) {
                pre->next = next->next;
            } else {
                ret = E_HWTIMER_NOT_FOUND;
            }
        } else {
            ret = E_HWTIMER_NOT_FOUND;
        }
        ASSERT(ret == E_HWTIMER_OK, "HWTIMER-STOP collapsed: fired timer 0x%08x not in list 0x%08x",
            (uint32_t)timer, (uint32_t)hwtimer_list.fired);
    } else if (timer->state == HWTIMER_STATE_CALLBACK) {
        // The timer handler is preempted and timer is being handled
        ret = E_HWTIMER_IN_CALLBACK;
    } else {
        // Invalid state
        ret = E_HWTIMER_INVAL_ST;
    }

    if (ret == E_HWTIMER_OK) {
        timer->state = HWTIMER_STATE_ALLOC;
        timer->next = NULL;
    }

_exit:
    int_unlock(lock);

    return ret;
}

#ifdef HWTIMER_TEST

int hwtimer_get_index(HWTIMER_ID id)
{
    struct HWTIMER_T *timer;
    int i;

    timer = (struct HWTIMER_T *)id;

    if (timer < &hwtimer_list.timer[0] || timer > &hwtimer_list.timer[HWTIMER_NUM - 1]) {
        return -1;
    }

    for (i = 0; i < HWTIMER_NUM; i++) {
        if (timer == &hwtimer_list.timer[i]) {
            return i;
        }
    }

    return -1;
}

void hwtimer_dump(void)
{
    int i;
    int idx;
    bool checked[HWTIMER_NUM];
    char buf[100], *pos;
    const char *end = buf + sizeof(buf);
    struct HWTIMER_T *timer;
    uint32_t lock;
    enum HWTIMER_STATE_T state;

    for (i = 0; i < HWTIMER_NUM; i++) {
        checked[i] = false;
    }

    TRACE(0,"------\nHWTIMER LIST DUMP");
    lock = int_lock();
    for (i = 0; i < 3; i++) {
        pos = buf;
        if (i == 0) {
            pos += snprintf(pos, end - pos, "ACTIVE: ");
            timer = hwtimer_list.active;
            state = HWTIMER_STATE_ACTIVE;
        } else if (i == 1) {
            pos += snprintf(pos, end - pos, "FIRED : ");
            timer = hwtimer_list.fired;
            state = HWTIMER_STATE_FIRED;
        } else {
            pos += snprintf(pos, end - pos, "FREE  : ");
            timer = hwtimer_list.free;
            state = HWTIMER_STATE_FREE;
        }
        while (timer) {
            idx = hwtimer_get_index(timer);
            if (idx == -1) {
                pos += snprintf(pos, end - pos, "<NA: %p>", timer);
                break;
            } else if (checked[idx]) {
                pos += snprintf(pos, end - pos, "<DUP: %d>", idx);
                break;
            } else if (timer->state != state) {
                pos += snprintf(pos, end - pos, "<ST-%d: %d> ", timer->state, idx);
            } else if (state == HWTIMER_STATE_ACTIVE) {
                pos += snprintf(pos, end - pos, "%d-%u ", idx, timer->time);
            } else {
                pos += snprintf(pos, end - pos, "%d ", idx);
            }
            checked[idx] = true;
            timer = timer->next;
        }
        TRACE(buf);
    }
    int_unlock(lock);
    pos = buf;
    pos += snprintf(pos, end - pos, "ALLOC : ");
    for (i = 0; i < HWTIMER_NUM; i++) {
        if (!checked[i]) {
            if (hwtimer_list.timer[i].state == HWTIMER_STATE_ALLOC) {
                pos += snprintf(pos, end - pos, "%d ", i);
            } else {
                pos += snprintf(pos, end - pos, "<ST-%d: %d> ", hwtimer_list.timer[i].state, i);
            }
        }
    }
    pos += snprintf(pos, end - pos, "\n------");
    TRACE(buf);
}

#define HWTIMER_TEST_NUM            (HWTIMER_NUM + 2)

static HWTIMER_ID test_id[HWTIMER_TEST_NUM];

static void timer_stop_test(int id)
{
    int ret;

    ret = hwtimer_stop(test_id[id]);
    TRACE(3,"[%u] Stop %d / %d", hal_sys_timer_get(), id, ret);
    hwtimer_dump();
}

static void timer_callback(void *param)
{
    int id;

    id = (int)param;

    TRACE(2,"[%u] TIMER-CALLBACK: %d", hal_sys_timer_get(), id);

    if (id == 3) {
        timer_stop_test(3);
        timer_stop_test(5);
        timer_stop_test(7);
    }
}

void hwtimer_test(void)
{
    int i;
    int ret;
    uint32_t lock;

    hwtimer_init();

    for (i = 0; i < HWTIMER_TEST_NUM; i++) {
        test_id[i] = hwtimer_alloc(timer_callback, (void *)i);
        ret = hwtimer_start(test_id[i], (i + 1) * 10);
        TRACE(4,"[%u] START-TIMER: %u / %p / %d", hal_sys_timer_get(), i, test_id[i], ret);
    }

    hwtimer_dump();

    lock = int_lock();
    hal_sys_timer_delay(55);
    timer_stop_test(0);
    timer_stop_test(2);
    int_unlock(lock);

    hal_sys_timer_delay(300);
    hwtimer_dump();
}

#endif

