/*
 * 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 <errno.h>
#include <unistd.h>
#include "os/mynewt.h"
#include "hal/hal_flash.h"
#include "hal/hal_system.h"
#include "testutil/testutil.h"
#include "testutil_priv.h"

/* The test task runs at a lower priority (greater number) than the default
 * task.  This allows the test task to assume events get processed as soon as
 * they are initiated.  The test code can then immediately assert the expected
 * result of event processing.
 */
#define TU_TEST_TASK_PRIO   (MYNEWT_VAL(OS_MAIN_TASK_PRIO) + 1)
#define TU_TEST_STACK_SIZE  1024

struct tu_config tu_config;

int tu_any_failed;

struct ts_testsuite_list *ts_suites;

void
tu_set_pass_cb(tu_case_report_fn_t *cb, void *cb_arg)
{
    tu_config.pass_cb = cb;
    tu_config.pass_arg = cb_arg;
}

void
tu_set_fail_cb(tu_case_report_fn_t *cb, void *cb_arg)
{
    tu_config.fail_cb = cb;
    tu_config.fail_arg = cb_arg;
}

#if MYNEWT_VAL(SELFTEST)
static void
tu_pass_cb_self(const char *msg, void *arg)
{
    printf("[pass] %s/%s %s\n", tu_config.ts_suite_name, tu_case_name, msg);
    fflush(stdout);
}

static void
tu_fail_cb_self(const char *msg, void *arg)
{
    printf("[FAIL] %s/%s %s\n", tu_config.ts_suite_name, tu_case_name, msg);
    fflush(stdout);
}
#endif

void
tu_init(void)
{
    /* Ensure this function only gets called by sysinit. */
    SYSINIT_ASSERT_ACTIVE();

#if MYNEWT_VAL(SELFTEST)
    os_init(NULL);
    tu_set_pass_cb(tu_pass_cb_self, NULL);
    tu_set_fail_cb(tu_fail_cb_self, NULL);
#endif
}

void
tu_arch_restart(void)
{
#if MYNEWT_VAL(SELFTEST)
    os_arch_os_stop();
    tu_case_abort();
#else
    hal_system_reset();
#endif
}

void
tu_restart(void)
{
    tu_case_write_pass_auto();
    tu_arch_restart();
}

static void
tu_dflt_task_handler(void *arg)
{
    while (1) {
        os_eventq_run(os_eventq_dflt_get());
    }
}

static void
tu_create_dflt_task(void)
{
    static os_stack_t stack[OS_STACK_ALIGN(OS_MAIN_STACK_SIZE)];
    static struct os_task task;

    os_task_init(&task, "tu_dflt_task", tu_dflt_task_handler, NULL,
                 OS_MAIN_TASK_PRIO, OS_WAIT_FOREVER, stack,
                 OS_STACK_ALIGN(OS_MAIN_STACK_SIZE));
}

static void
tu_test_task_handler(void *arg)
{
    os_task_func_t fn;

    fn = (os_task_func_t)arg;
    fn(NULL);

    tu_restart();
}

/**
 * Creates the "test task."  For test cases running in the OS, this is the task
 * that contains the actual test logic.
 */
static void
tu_create_test_task(const char *task_name, os_task_func_t task_handler)
{
    static os_stack_t stack[OS_STACK_ALIGN(TU_TEST_STACK_SIZE)];
    static struct os_task task;

    os_task_init(&task, task_name, tu_test_task_handler, (void *)task_handler,
                 TU_TEST_TASK_PRIO, OS_WAIT_FOREVER, stack,
                 OS_STACK_ALIGN(TU_TEST_STACK_SIZE));
}

/**
 * Creates the default task, creates the test task to run a test case in, and
 * starts the OS.
 */
void
tu_start_os(const char *test_task_name, os_task_func_t test_task_handler)
{
    tu_create_dflt_task();
    tu_create_test_task(test_task_name, test_task_handler);

    os_start();
}
