/*
 * Copyright (c) 2014-2018, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "nsconfig.h"
#include "ns_types.h"
#include "ns_trace.h"
#include "eventOS_event.h"
#include "eventOS_callback_timer.h"
#include "NWK_INTERFACE/Include/protocol.h"
#include "NWK_INTERFACE/Include/protocol_timer.h"
#include "platform/arm_hal_interrupt.h"

#define TRACE_GROUP "ctim"

#define PROTOCOL_TIMER_INTERVAL 1000    // 50us units, so we use 50ms

NS_LARGE protocol_timer_t protocol_timer[PROTOCOL_TIMER_MAX];
int8_t protocol_timer_id = -1;
bool protocol_tick_handle_busy = false;
static uint16_t  protocol_tick_update = 0;
int protocol_timer_init(void)
{
    uint8_t i;
    protocol_timer_id = eventOS_callback_timer_register(protocol_timer_interrupt);
    for (i = 0; i < PROTOCOL_TIMER_MAX; i++) {
        protocol_timer[i].ticks = 0;
        protocol_timer[i].time_drifts = 0;
    }
    if (protocol_timer_id >= 0) {
        eventOS_callback_timer_start(protocol_timer_id, PROTOCOL_TIMER_INTERVAL);
    }
    return protocol_timer_id;
}

// time is in milliseconds
void protocol_timer_start(protocol_timer_id_t id, void (*passed_fptr)(uint16_t), uint32_t time)
{
    //Check Overflow
    if (passed_fptr) {
        if (time > 0x12FFED) {
            time = 0xffff;
        }
        if (time >= 100) {
            time /= (1000 / 20);
            //time++;
        } else {
            time = 1;
        }
        platform_enter_critical();
        protocol_timer[id].ticks = (uint16_t) time;

        protocol_timer[id].orderedTime = (uint16_t) time;
        if (time > 1 && protocol_timer[id].time_drifts >= 50) {
            protocol_timer[id].ticks--;
            protocol_timer[id].time_drifts -= 50;
        }
        protocol_timer[id].fptr = passed_fptr;
        platform_exit_critical();
    } else {
        tr_debug("Do Not use Null pointer for fptr!!!\n");
    }
}

void protocol_timer_stop(protocol_timer_id_t id)
{
    platform_enter_critical();
    protocol_timer[id].ticks = 0;
    protocol_timer[id].orderedTime = 0;
    platform_exit_critical();
}



void protocol_timer_sleep_balance(uint32_t time_in_ms)
{
    uint8_t i;
    uint16_t ticks_module;
    uint16_t time_in50ms_ticks;
    uint16_t tick_update, tempTimer;

    ticks_module = (time_in_ms % 50);
    time_in50ms_ticks = (time_in_ms / 50);
    for (i = 0; i < PROTOCOL_TIMER_MAX; i++) {
        if (protocol_timer[i].ticks) {

            tick_update = time_in50ms_ticks;
            protocol_timer[i].time_drifts += ticks_module;

            if (protocol_timer[i].time_drifts >= 50) {
                protocol_timer[i].time_drifts -= 50;
            }

            if (protocol_timer[i].ticks <= tick_update) {
                tempTimer = (tick_update - protocol_timer[i].ticks);
                tick_update = 1;
                if (tempTimer >= protocol_timer[i].orderedTime) {
                    tick_update += (tempTimer / protocol_timer[i].orderedTime);
                    //time drift
                    protocol_timer[i].time_drifts += ((tempTimer % protocol_timer[i].orderedTime) * 50);
                }

                protocol_timer[i].ticks = 0;
                protocol_timer[i].orderedTime = 0;
                protocol_timer[i].fptr(tick_update);

            } else {
                protocol_timer[i].ticks -= tick_update;
            }
        }
    }

}

void protocol_timer_event_lock_free(void)
{
    platform_enter_critical();
    protocol_tick_handle_busy = false;
    platform_exit_critical();
}


void protocol_timer_cb(uint16_t ticks)
{
    uint8_t i;
    uint16_t tick_update, tempTimer;
    for (i = 0; i < PROTOCOL_TIMER_MAX; i++) {
        if (protocol_timer[i].ticks) {
            if (protocol_timer[i].ticks <= ticks) {
                tempTimer = (ticks - protocol_timer[i].ticks);

                tick_update = 1;
                if (protocol_timer[i].time_drifts >= 50) {
                    tempTimer++;
                    protocol_timer[i].time_drifts -= 50;
                }

                if (tempTimer >= protocol_timer[i].orderedTime) {
                    tick_update += (tempTimer / protocol_timer[i].orderedTime);
                    protocol_timer[i].time_drifts += ((tempTimer % protocol_timer[i].orderedTime) * 50);
                }

                protocol_timer[i].ticks = 0;
                protocol_timer[i].orderedTime = 0;
                protocol_timer[i].fptr(tick_update);

            } else {
                protocol_timer[i].ticks -= ticks;
            }
        }
    }
}

void protocol_timer_interrupt(int8_t timer_id, uint16_t slots)
{
    (void)timer_id;
    (void)slots;
    eventOS_callback_timer_start(protocol_timer_id, PROTOCOL_TIMER_INTERVAL);
    protocol_tick_update++;

    if (!protocol_tick_handle_busy) {
        /* This static stuff gets initialised once */
        static arm_event_storage_t event = {
            .data = {
                .data_ptr = NULL,
                .event_type = ARM_IN_PROTOCOL_TIMER_EVENT,
                .event_id = 0,
                .priority = ARM_LIB_HIGH_PRIORITY_EVENT
            }
        };

        /* Dynamic stuff */
        event.data.receiver = event.data.sender = protocol_read_tasklet_id();
        event.data.event_data = protocol_tick_update;
        protocol_tick_update = 0;

        /* Use user-allocated variant to avoid memory allocation failure */
        eventOS_event_send_user_allocated(&event);
        protocol_tick_handle_busy = true;
    }
}
