/**
 * \file
 * \brief Barrelfish library initialization.
 */

/*
 * Copyright (c) 2007-2019, ETH Zurich.
 * Copyright (c) 2014, HP Labs.
 * All rights reserved.
 *
 * This file is distributed under the terms in the attached LICENSE file.
 * If you do not find this file, copies can be found by writing to:
 * ETH Zurich D-INFK, CAB F.78, Universitaetstr. 6, CH-8092 Zurich,
 * Attn: Systems Group.
 */

#include <stdio.h>

#include <aos/aos.h>
#include <aos/dispatch.h>
#include <aos/curdispatcher_arch.h>
#include <aos/dispatcher_arch.h>
#include <barrelfish_kpi/dispatcher_shared.h>
#include <aos/morecore.h>
#include <aos/paging.h>
#include <aos/systime.h>
#include <barrelfish_kpi/domain_params.h>

#include "threads_priv.h"
#include "init.h"
#include <aos/aos_rpc.h>

/// Are we the init domain (and thus need to take some special paths)?
static bool init_domain;
static bool init_chan_initialized;

extern size_t (*_libc_terminal_read_func)(char *, size_t);
extern size_t (*_libc_terminal_write_func)(const char *, size_t);
extern void (*_libc_exit_func)(int);
extern void (*_libc_assert_func)(const char *, const char *, const char *, int);

void libc_exit(int);

__weak_reference(libc_exit, _exit);
void libc_exit(int status)
{
    debug_printf("libc exit NYI!\n");
    thread_exit(status);
    // If we're not dead by now, we wait
    while (1) {}
}

static void libc_assert(const char *expression, const char *file,
                        const char *function, int line)
{
    char buf[512];
    size_t len;

    /* Formatting as per suggestion in C99 spec 7.2.1.1 */
    len = snprintf(buf, sizeof(buf), "Assertion failed on core %d in %.*s: %s,"
                   " function %s, file %s, line %d.\n",
                   disp_get_core_id(), DISP_NAME_LEN,
                   disp_name(), expression, function, file, line);
    sys_print(buf, len < sizeof(buf) ? len : sizeof(buf));
}

// __attribute__((__used__))
// static size_t syscall_terminal_write(const char *buf, size_t len)
// {
//     if(len) {
//         errval_t err = sys_print(buf, len);
//         if (err_is_fail(err)) {
//             return 0;
//         }
//     }
//     return len;
// }

// __attribute__((__used__))
// static size_t dummy_terminal_read(char *buf, size_t len)
// {
//     debug_printf("Terminal read NYI!\n");
//     return 0;
// }

static void handle_init_recv(void *arg)
{
    errval_t err;
    struct lmp_chan *init_chan = (struct lmp_chan *)arg;

    struct lmp_recv_msg msg = LMP_RECV_MSG_INIT;

    err = lmp_chan_recv(init_chan, &msg, NULL);
    assert(err_is_ok(err));

    if (msg.words[0] == SYS_ERR_OK) {
        debug_printf("Received child init OK\n");
        init_chan_initialized = true;
    } else {
        USER_PANIC("Child LMP initialization failed.");
    }
}

static size_t aos_terminal_write(const char *buf, size_t len)
{
    if(len == 0) return 0;

    struct aos_rpc *rpc = aos_rpc_get_serial_channel();
    if (rpc == NULL) {
        debug_printf("[aos_terminal_write] RPC NOT YET INITIALIZED: %.*s\n", len, buf);
        USER_PANIC("Make sure to initialize aos_rpc before barrelfish_libc_glue_init is called.");
    }

    return aos_rpc_serial_write(rpc, buf, len);
}

static size_t aos_terminal_read(char *buf, size_t len)
{
    if(len == 0) return 0;

    struct aos_rpc *rpc = aos_rpc_get_serial_channel();
    if (rpc == NULL) {
        debug_printf("[aos_terminal_read] RPC NOT YET INITIALIZED: %.*s\n", len, buf);
        USER_PANIC("Make sure to initialize aos_rpc before barrelfish_libc_glue_init is called.");
    }

    return aos_rpc_serial_read(rpc, buf, len);
}

/* Set libc function pointers */
void barrelfish_libc_glue_init(void)
{
    // XXX: FIXME: Check whether we can use the proper kernel serial, and
    // what we need for that
    // TODO: change these to use the user-space serial driver if possible
    // TODO: set these functions
    // MARKER SHELL: init should also print through the UART driver
    if(init_domain) {
        _libc_terminal_read_func = dummy_terminal_read;
        _libc_terminal_write_func = dummy_terminal_write;
    } else {
        _libc_terminal_read_func = aos_terminal_read;
        _libc_terminal_write_func = aos_terminal_write;
    }
    _libc_exit_func = libc_exit;
    _libc_assert_func = libc_assert;
    /* morecore func is setup by morecore_init() */

    // XXX: set a static buffer for stdout
    // this avoids an implicit call to malloc() on the first printf
    static char buf[BUFSIZ];
    setvbuf(stdout, buf, _IOLBF, sizeof(buf));
}


/** \brief Initialise libbarrelfish.
 *
 * This runs on a thread in every domain, after the dispatcher is setup but
 * before main() runs.
 */
errval_t barrelfish_init_onthread(struct spawn_domain_params *params)
{
    errval_t err;

    // do we have an environment?
    if (params != NULL && params->envp[0] != NULL) {
        extern char **environ;
        environ = params->envp;
    }

    // Init default waitset for this dispatcher
    struct waitset *default_ws = get_default_waitset();
    waitset_init(default_ws);

    // Initialize ram_alloc state
    ram_alloc_init();
    /* All domains use smallcn to initialize */
    err = ram_alloc_set(ram_alloc_fixed);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_RAM_ALLOC_SET);
    }

    err = paging_init_params(params);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_VSPACE_INIT);
    }

    err = slot_alloc_init();
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_SLOT_ALLOC_INIT);
    }

    err = morecore_init(BASE_PAGE_SIZE);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_MORECORE_INIT);
    }

    lmp_endpoint_init();

    // HINT: Use init_domain to check if we are the init domain.
    // early exit for the init process
    if(init_domain) {
        return SYS_ERR_OK;
    }

    // TODO MILESTONE 3: register ourselves with init
    /* allocate lmp channel structure */
    static struct lmp_chan init_chan;
    lmp_chan_init(&init_chan);
    /* create local endpoint */
    err = endpoint_create(DEFAULT_LMP_BUF_WORDS, &init_chan.local_cap, &init_chan.endpoint);
    if (err_is_fail(err)) {
        return err_push(err, LIB_ERR_ENDPOINT_CREATE);
    }
    /* set remote endpoint to init's endpoint */
    init_chan.remote_cap = cap_initep;
    /* set receive handler */
    init_chan_initialized = false;
    err = lmp_chan_register_recv(&init_chan, get_default_waitset(), MKCLOSURE(handle_init_recv, &init_chan));
    /* send local ep to init */
    err = lmp_chan_send1(&init_chan, LMP_FLAG_YIELD | LMP_FLAG_SYNC, init_chan.local_cap, RPC_MTYPE_CHILD_ENDPOINT);
    if (err_is_fail(err)) {
        // should never fail since init listens before invoking the dispatcher
        return err_push(err, LIB_ERR_LMP_CHAN_SEND);
    }
    /* wait for init to acknowledge receiving the endpoint */
    while(!init_chan_initialized) {
        err = event_dispatch(get_default_waitset());
        if (err_is_fail(err)) {
            return err_push(err, LIB_ERR_EVENT_DISPATCH);
        }
    }
    /* initialize init RPC client with lmp channel */
    static struct aos_rpc init_rpc;
    err = aos_rpc_init(&init_rpc, &init_chan, params->rpc_shared_memory);
    if (err_is_fail(err)){
        return err_push(err, ERR_NOTIMP);
    }
    /* set init RPC client in our program state */
    set_init_rpc(&init_rpc);

    /* TODO MILESTONE 3: now we should have a channel with init set up and can
     * use it for the ram allocator */
    //afeer: script page 135, tell domain to use our memserver.
    err = ram_alloc_set(NULL);

    // right now we don't have the nameservice & don't need the terminal
    // and domain spanning, so we return here
    return SYS_ERR_OK;
}


/**
 *  \brief Initialise libbarrelfish, while disabled.
 *
 * This runs on the dispatcher's stack, while disabled, before the dispatcher is
 * setup. We can't call anything that needs to be enabled (ie. cap invocations)
 * or uses threads. This is called from crt0.
 */
void barrelfish_init_disabled(dispatcher_handle_t handle, bool init_dom_arg);
void barrelfish_init_disabled(dispatcher_handle_t handle, bool init_dom_arg)
{
    init_domain = init_dom_arg;
    disp_init_disabled(handle);
    thread_init_disabled(handle, init_dom_arg);
}
