/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 <assert.h>
#include <string.h>
#include "os/mynewt.h"
#include "os_priv.h"

uint8_t g_task_id;

struct os_task_stailq g_os_task_list;

static void
_clear_stack(os_stack_t *stack_bottom, int size)
{
    int i;

    for (i = 0; i < size; i++) {
        stack_bottom[i] = OS_STACK_PATTERN;
    }
}

static inline uint8_t
os_task_next_id(void)
{
    uint8_t rc;
    os_sr_t sr;

    OS_ENTER_CRITICAL(sr);
    rc = g_task_id;
    g_task_id++;
    OS_EXIT_CRITICAL(sr);

    return (rc);
}

uint8_t
os_task_count(void)
{
    return (g_task_id);
}

int
os_task_init(struct os_task *t, const char *name, os_task_func_t func,
        void *arg, uint8_t prio, os_time_t sanity_itvl,
        os_stack_t *stack_bottom, uint16_t stack_size)
{
    struct os_sanity_check *sc;
    int rc;
    struct os_task *task;

    memset(t, 0, sizeof(*t));

    t->t_func = func;
    t->t_arg = arg;

    t->t_taskid = os_task_next_id();
    t->t_prio = prio;

    t->t_state = OS_TASK_READY;
    t->t_name = name;
    t->t_next_wakeup = 0;

    rc = os_sanity_check_init(&t->t_sanity_check);
    if (rc != OS_OK) {
        goto err;
    }

    if (sanity_itvl != OS_WAIT_FOREVER) {
        sc = (struct os_sanity_check *) &t->t_sanity_check;
        sc->sc_checkin_itvl = sanity_itvl;

        rc = os_sanity_check_register(sc);
        if (rc != OS_OK) {
            goto err;
        }
    }

    _clear_stack(stack_bottom, stack_size);
    t->t_stackbottom = stack_bottom;
    t->t_stacksize = stack_size;
    t->t_stackptr = os_arch_task_stack_init(t, os_task_stacktop_get(t),
                                            t->t_stacksize);

    STAILQ_FOREACH(task, &g_os_task_list, t_os_task_list) {
        assert(t->t_prio != task->t_prio);
    }

    /* insert this task into the task list */
    STAILQ_INSERT_TAIL(&g_os_task_list, t, t_os_task_list);

    /* insert this task into the scheduler list */
    rc = os_sched_insert(t);
    if (rc != OS_OK) {
        goto err;
    }

    os_trace_task_create(t);
    os_trace_task_info(t);

    /* Allow a preemption in case the new task has a higher priority than the
     * current one.
     */
    if (os_started()) {
        os_sched(NULL);
    }

    return (0);
err:
    return (rc);
}

os_stack_t *
os_task_stacktop_get(struct os_task *t)
{
    return &t->t_stackbottom[t->t_stacksize];
}

int
os_task_remove(struct os_task *t)
{
    int rc;
    os_sr_t sr;

    /*
     * Can't suspend yourself
     */
    if (t == os_sched_get_current_task()) {
        return OS_INVALID_PARM;
    }

    /*
     * If state is not READY or SLEEP, assume task has not been initialized
     */
    if (t->t_state != OS_TASK_READY && t->t_state != OS_TASK_SLEEP)
    {
        return OS_NOT_STARTED;
    }

    /*
     * Disallow suspending tasks which are waiting on a lock
     */
    if (t->t_flags & (OS_TASK_FLAG_SEM_WAIT | OS_TASK_FLAG_MUTEX_WAIT |
                                               OS_TASK_FLAG_EVQ_WAIT)) {
        return OS_EBUSY;
    }

    /*
     * Disallowing suspending tasks which are holding a mutex
     */
    if (t->t_lockcnt) {
        return OS_EBUSY;
    }

    OS_ENTER_CRITICAL(sr);
    rc = os_sched_remove(t);
    OS_EXIT_CRITICAL(sr);
    return rc;
}

void
os_task_info_get(const struct os_task *task, struct os_task_info *oti)
{
    os_stack_t *bottom;
    os_stack_t *top;

    oti->oti_prio = task->t_prio;
    oti->oti_taskid = task->t_taskid;
    oti->oti_state = task->t_state;

    bottom = task->t_stackbottom;
    top = bottom + task->t_stacksize;
    while (bottom < top) {
        if (*bottom != OS_STACK_PATTERN) {
            break;
        }
        ++bottom;
    }

    oti->oti_stkusage = (uint16_t) (top - bottom);
    oti->oti_stksize = task->t_stacksize;
    oti->oti_cswcnt = task->t_ctx_sw_cnt;
    oti->oti_runtime = task->t_run_time;
    oti->oti_last_checkin = task->t_sanity_check.sc_checkin_last;
    oti->oti_next_checkin = task->t_sanity_check.sc_checkin_last +
                            task->t_sanity_check.sc_checkin_itvl;
    oti->oti_name[0] = '\0';
    strncat(oti->oti_name, task->t_name, sizeof(oti->oti_name) - 1);
}

struct os_task *
os_task_info_get_next(const struct os_task *prev, struct os_task_info *oti)
{
    struct os_task *next;

    if (prev != NULL) {
        next = STAILQ_NEXT(prev, t_os_task_list);
    } else {
        next = STAILQ_FIRST(&g_os_task_list);
    }

    if (next) {
        os_task_info_get(next, oti);
    }

    return next;
}

