/*
 * 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 <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "os/mynewt.h"
#include "console/console.h"
#include "testutil/testutil.h"
#include "taskpool/taskpool.h"

#include "runtest/runtest.h"
#include "runtest_priv.h"

#if MYNEWT_VAL(RUNTEST_CLI)
#include "shell/shell.h"
struct shell_cmd runtest_cmd_struct;
#endif

#if MYNEWT_VAL(RUNTEST_LOG)
#include "cbmem/cbmem.h"
#include "modlog/modlog.h"
static uint8_t runtest_cbmem_buf[MYNEWT_VAL(RUNTEST_LOG_SIZE)];
static struct cbmem runtest_cbmem;
static struct log runtest_log;
#endif

static void runtest_evt_fn(struct os_event *ev);

static int runtest_total_tests;
static int runtest_total_fails;
static struct ts_suite *runtest_current_ts;
static char runtest_token[MYNEWT_VAL(RUNTEST_MAX_TOKEN_LEN)];
static char runtest_test_name[MYNEWT_VAL(RUNTEST_MAX_TEST_NAME_LEN)];
static bool runtest_busy;

static struct os_eventq *runtest_evq;
static struct os_mutex runtest_mtx;
static struct os_event run_test_event = {
    .ev_cb = runtest_evt_fn,
};

#define RUNTEST_BUILD_ID 

/**
 * Retrieves the event queue used by the runtest package.
 */
struct os_eventq *
runtest_evq_get(void)
{
    return runtest_evq;
}

void
runtest_evq_set(struct os_eventq *evq)
{
    runtest_evq = evq;
}

static void
runtest_lock(void)
{
    int rc;

    rc = os_mutex_pend(&runtest_mtx, OS_TIMEOUT_NEVER);
    assert(rc == 0);
}

static void
runtest_unlock(void)
{
    int rc;

    rc = os_mutex_release(&runtest_mtx);
    assert(rc == 0);
}

int
runtest_total_fails_get(void)
{
    return runtest_total_fails;
}

#if MYNEWT_VAL(RUNTEST_LOG)
static void
runtest_log_result(const char *msg, bool passed)
{
    /* Must log valid json with a strlen less than MODLOG_MAX_PRINTF_LEN */
    char buf[MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN)];
    char *n;
    int n_len;
    char *s;
    int s_len;
    char *m;
    int m_len;
    int len;

    /* str length of {"k":"","n":"","s":"","m":"","r":1}<token> plus three
     * null-terminators.
     */
    len = 38 + strlen(runtest_token);

    /* How much of the test name can we log? */
    n_len = strlen(tu_case_name);
    if (len + n_len >= MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN)) {
        n_len = MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN) - len - 1;
    }
    len += n_len;
    n = buf;
    strncpy(n, tu_case_name, n_len);
    n[n_len] = '\0';

    /* How much of the suite name can we log? */
    s_len = strlen(runtest_current_ts->ts_name);
    if (len + s_len >= MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN)) {
        s_len = MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN) - len - 1;
    }
    len += s_len;
    s = n + n_len + 2;
    strncpy(s, runtest_current_ts->ts_name, s_len);
    s[s_len] = '\0';

    /* How much of the message can we log? */
    m_len = strlen(msg);
    if (len + m_len >= MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN)) {
        m_len = MYNEWT_VAL(MODLOG_MAX_PRINTF_LEN) - len - 1;
    }
    m = s + s_len + 2;
    strncpy(m, msg, m_len);
    m[m_len] = '\0';

    /* XXX Hack to allow the idle task to run and update the timestamp. */
    os_time_delay(1);

    runtest_total_tests++;
    if (!passed) {
        runtest_total_fails++;
    }

    TEST_LOG_INFO(
        "{\"k\":\"%s\",\"n\":\"%s\",\"s\":\"%s\",\"m\":\"%s\",\"r\":%d}",
        runtest_token, n, s, m, passed);
}

static void
runtest_pass(const char *msg, void *arg)
{
    runtest_log_result(msg, true);
}

static void
runtest_fail(const char *msg, void *arg)
{
    runtest_log_result(msg, false);
}
#endif

static struct ts_suite *
runtest_find_test(const char *suite_name)
{
    struct ts_suite *cur;

    SLIST_FOREACH(cur, &g_ts_suites, ts_next) {
        if (strcmp(cur->ts_name, suite_name) == 0) {
            return cur;
        }
    }

    return NULL;
}

/* XXX Deprecated.  Remove after next release. */
struct os_task *
runtest_init_task(os_task_func_t task_handler, uint8_t prio)
{
    return taskpool_alloc_assert(task_handler, prio);
}

static void
runtest_test_complete(void)
{
#if MYNEWT_VAL(RUNTEST_LOG)
    if (runtest_total_tests > 0) {
        TEST_LOG_INFO("%s Done", runtest_token);
        TEST_LOG_INFO(
                    "%s TESTBENCH TEST %s - Tests run:%d pass:%d fail:%d %s",
                    RUNTEST_PREFIX,
                    runtest_total_fails ? "FAILED" : "PASSED",
                    runtest_total_tests,
                    runtest_total_tests - runtest_total_fails,
                    runtest_total_fails,
                    runtest_token);
    }
#endif
    runtest_busy = false;
}

static void
runtest_evt_fn(struct os_event *ev)
{
    int runtest_all;

    runtest_total_tests = 0;
    runtest_total_fails = 0;

    if (ev != NULL) {
        runtest_all = runtest_test_name[0] == '\0' ||
                      strcmp(runtest_test_name, "all") == 0;

        if (runtest_all) {
            SLIST_FOREACH(runtest_current_ts, &g_ts_suites, ts_next) {
                runtest_current_ts->ts_test();
            }
        } else {
            runtest_current_ts = runtest_find_test(runtest_test_name);
            if (runtest_current_ts != NULL) {
                runtest_current_ts->ts_test();
            }
        }
    }
    runtest_test_complete();
}

int
runtest_run(const char *test_name, const char *token)
{
    int rc;

    /* Only allow one test at a time. */
    runtest_lock();

    if (runtest_busy) {
        rc = SYS_EAGAIN;
    } else if (test_name[0] != '\0'             &&
               strcmp(test_name, "all") != 0    &&
               runtest_find_test(test_name) == NULL) {
        rc = SYS_ENOENT;
    } else {
        strncpy(runtest_test_name, test_name, sizeof runtest_test_name);
        strncpy(runtest_token, token, sizeof runtest_token);

        runtest_busy = true;
        os_eventq_put(runtest_evq_get(), &run_test_event);
        rc = 0;
    }

    runtest_unlock();

    return rc;
}

/*
 * Package init routine to register mgmt "run" commands
 */
void
runtest_init(void)
{
    int rc;

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

#if MYNEWT_VAL(RUNTEST_LOG)
    rc = cbmem_init(&runtest_cbmem, runtest_cbmem_buf,
                    MYNEWT_VAL(RUNTEST_LOG_SIZE));
    SYSINIT_PANIC_ASSERT(rc == 0);

    /* Create a cbmem log and map the "test" module to it. */
    rc = log_register("testlog", &runtest_log, &log_cbmem_handler,
                      &runtest_cbmem, LOG_SYSLEVEL);
    SYSINIT_PANIC_ASSERT(rc == 0);

    rc = modlog_register(LOG_MODULE_TEST, &runtest_log, LOG_LEVEL_DEBUG, NULL);
    assert(rc == 0);
#endif

#if MYNEWT_VAL(RUNTEST_CLI)
    shell_cmd_register(&runtest_cmd_struct);
#endif

#if MYNEWT_VAL(RUNTEST_MGMT)
    rc = runtest_mgmt_register_group();
    SYSINIT_PANIC_ASSERT(rc == 0);
#endif

    rc = os_mutex_init(&runtest_mtx);
    SYSINIT_PANIC_ASSERT(rc == 0);

    tu_config.ts_system_assert = 0;
#if MYNEWT_VAL(RUNTEST_LOG)
    tu_set_pass_cb(runtest_pass, NULL);
    tu_set_fail_cb(runtest_fail, NULL);
#endif

    /* Use the main event queue by default. */
    runtest_evq_set(os_eventq_dflt_get());
}
