/**
 * \file
 * \brief RPC Bindings for AOS
 */

/*
 * Copyright (c) 2013-2016, ETH Zurich.
 * 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, Universitaetstr. 6, CH-8092 Zurich. attn: systems group.
 */

#include <aos/aos.h>
#include <aos/aos_rpc.h>
#include <aos/ump_binding.h>

#include <aos/dispatcher_arch.h>

static void noop_callback(void *arg) {
}

static errval_t do_aos_rpc(
    struct aos_rpc *rpc, uintptr_t msg_type,
    struct capref arg_cap, size_t arg_size, uintptr_t arg0, uintptr_t arg1,
    struct capref *ret_cap, size_t *ret_size, uintptr_t *ret0, uintptr_t *ret1
) {
    errval_t err;

    // Allocate slot if we expect a capability.
    // We must do this before taking the lock, because allocating a slot may
    // allocate memory and thus do a aos_rpc_get_ram_cap.
    struct capref slot;
    if (ret_cap != NULL) {
        err = slot_alloc(&slot);
        if (err_is_fail(err)) {
            return err_push(err, LIB_ERR_SLOT_ALLOC);
        }
    }

    thread_mutex_lock(&rpc->lock);

    if (ret_cap != NULL) {
        if (rpc->chan->endpoint->k.recv_cptr == 0) {
            lmp_chan_set_recv_slot(rpc->chan, slot);
        } else {
            // Freeing slots never allocates memory, so we can do this
            // while we have the lock.
            slot_free(slot);
        }
    }

    // Send call
    while (true) {
        err = lmp_chan_send4(rpc->chan, LMP_FLAG_YIELD, arg_cap, msg_type, arg_size, arg0, arg1);
        if (!err_is_fail(err)) break;
        if (!lmp_err_is_transient(err)) {
            thread_mutex_unlock(&rpc->lock);
            return err_push(err, AOS_ERR_LMP_SEND_FAILURE);
        }
        err = lmp_chan_register_send(rpc->chan, &rpc->ws, MKCLOSURE(noop_callback, NULL));
        if (err_is_fail(err)) {
            thread_mutex_unlock(&rpc->lock);
            return err;
        }
        err = event_dispatch(&rpc->ws);
        if (err_is_fail(err)) {
            thread_mutex_unlock(&rpc->lock);
            return err;
        }
    }

    // Receive return value
    struct lmp_recv_msg recv = LMP_RECV_MSG_INIT;
    while (!lmp_endpoint_can_recv(rpc->chan->endpoint)) {
        err = lmp_chan_register_recv(rpc->chan, &rpc->ws, MKCLOSURE(noop_callback, NULL));
        if (err_is_fail(err)) {
            thread_mutex_unlock(&rpc->lock);
            return err;
        }
        err = event_dispatch(&rpc->ws);
        if (err_is_fail(err)) {
            thread_mutex_unlock(&rpc->lock);
            return err;
        }
    }
    err = lmp_chan_recv(rpc->chan, &recv, ret_cap);
    assert(err_is_ok(err));

    thread_mutex_unlock(&rpc->lock);

    if (ret_size != NULL) *ret_size = recv.words[1];
    if (ret0 != NULL) *ret0 = recv.words[2];
    if (ret1 != NULL) *ret1 = recv.words[3];
    return recv.words[0];
}

errval_t
aos_rpc_nop(struct aos_rpc *rpc) {
    errval_t err;

    err = do_aos_rpc(
        rpc, RPC_MTYPE_NOP,
        NULL_CAP, 0, 0, 0,
        NULL, NULL, NULL, NULL);

    return err;
}

errval_t
aos_rpc_send_number(struct aos_rpc *rpc, uintptr_t num) {
    // Implement functionality to send a number over the channel
    // given channel and wait until the ack gets returned.
    errval_t err;

    err = do_aos_rpc(
        rpc, RPC_MTYPE_SEND_NUMBER,
        NULL_CAP, 0, num, 0,
        NULL, NULL, NULL, NULL);

    return err;
}

errval_t
aos_rpc_send_string(struct aos_rpc *rpc, const char *string) {
    // Implement functionality to send a string over the given channel
    // and wait for a response.
    errval_t err;

    size_t string_size = strlen(string) + 1;
    if (string_size > RPC_SHARED_SIZE) return AOS_ERR_RPC_ARG_TOO_BIG;

    thread_mutex_lock(&rpc->shared_mem_lock);
    memcpy(rpc->shared_mem, string, string_size);
    err = do_aos_rpc(
        rpc, RPC_MTYPE_SEND_STRING,
        NULL_CAP, string_size, 0, 0,
        NULL, NULL, NULL, NULL
    );
    thread_mutex_unlock(&rpc->shared_mem_lock);

    return err;
}

errval_t
aos_rpc_get_ram_cap(struct aos_rpc *rpc, size_t bytes, size_t alignment,
                    struct capref *ret_cap, size_t *ret_bytes) {
    // Implement functionality to request a RAM capability over the
    // given channel and wait until it is delivered.

    errval_t err;

    err = do_aos_rpc(
        rpc, RPC_MTYPE_GET_RAM_CAP,
        NULL_CAP, 0, bytes, alignment,
        ret_cap, NULL, ret_bytes, NULL
    );

    return err;
}


errval_t
aos_rpc_serial_getchar(struct aos_rpc *rpc, char *retc) {
    // Implement functionality to request a character from
    // the serial driver.
    errval_t err;
    uintptr_t retval;

    err = do_aos_rpc(
        rpc, RPC_MTYPE_SERIAL_GETCHAR,
        NULL_CAP, 0, 0, 0,
        NULL, NULL, &retval, NULL);

    *retc = retval;
    return err;
}


errval_t
aos_rpc_serial_putchar(struct aos_rpc *rpc, char c) {
    // Implement functionality to send a character to the
    // serial port.
    errval_t err;

    err = do_aos_rpc(
        rpc, RPC_MTYPE_SERIAL_PUTCHAR,
        NULL_CAP, 0, c, 0,
        NULL, NULL, NULL, NULL
    );

    return err;
}

// putchar for terminal printing is too slow so we write buffers instead
// the corresponding interface does not know about errors, just partial writes
size_t
aos_rpc_serial_write(struct aos_rpc *rpc, const char *buf, size_t buf_len) {
    errval_t err;

    // split the buffer into chunks for sending
    size_t total_bytes = 0;
    while(buf_len > 0) {
        size_t chunk_len = MIN(buf_len, RPC_SHARED_SIZE);
        size_t written_bytes;

        thread_mutex_lock(&rpc->shared_mem_lock);
        memcpy(rpc->shared_mem, buf, chunk_len);
        err = do_aos_rpc(
            rpc, RPC_MTYPE_SERIAL_WRITE,
            NULL_CAP, chunk_len, 0, 0,
            NULL, NULL, &written_bytes, NULL
        );
        thread_mutex_unlock(&rpc->shared_mem_lock);

        if (err_is_fail(err)) {
            DEBUG_ERR(err, "Failed to write the full buffer");
            return total_bytes;
        }
        assert(written_bytes <= chunk_len);

        total_bytes += written_bytes;

        // if the receiver was not able to write all we have given, return for now
        if (written_bytes < chunk_len) {
            return total_bytes;
        }

        buf += chunk_len;
        buf_len -= chunk_len;
    }

    return total_bytes;
}

// getchar for terminal reading is too slow so we read buffers instead
// the corresponding interface does not know about errors, just partial reads
size_t
aos_rpc_serial_read(struct aos_rpc *rpc, char *buf, size_t buf_len) {
    errval_t err;

    // split the buffer into chunks for reading
    size_t total_bytes = 0;
    while(buf_len > 0) {
        size_t chunk_len = MIN(buf_len, RPC_SHARED_SIZE);
        size_t read_bytes;

        thread_mutex_lock(&rpc->shared_mem_lock);
        err = do_aos_rpc(
            rpc, RPC_MTYPE_SERIAL_READ,
            NULL_CAP, 0, chunk_len, 0,
            NULL, &read_bytes, NULL, NULL
        );
        if (err_is_fail(err)) {
            DEBUG_ERR(err, "Failed to read the full buffer");
            thread_mutex_unlock(&rpc->shared_mem_lock);
            return total_bytes;
        }
        assert(read_bytes <= chunk_len);

        memcpy(buf, rpc->shared_mem, read_bytes);
        thread_mutex_unlock(&rpc->shared_mem_lock);

        total_bytes += read_bytes;

        // if the receiver was not able to write all we have given, return for now
        if (read_bytes < chunk_len) {
            return total_bytes;
        }

        buf += chunk_len;
        buf_len -= chunk_len;
    }

    return total_bytes;
}

errval_t
aos_rpc_process_spawn(struct aos_rpc *rpc, char *cmdline,
                      coreid_t core, domainid_t *newpid) {
    // implement spawn new process rpc
    size_t cmdline_size = strlen(cmdline) + 1;
    if (cmdline_size > RPC_SHARED_SIZE) return AOS_ERR_RPC_ARG_TOO_BIG;

    thread_mutex_lock(&rpc->shared_mem_lock);
    memcpy(rpc->shared_mem, cmdline, cmdline_size);
    uintptr_t retval;
    errval_t err = do_aos_rpc(
        rpc, RPC_MTYPE_PROCESS_SPAWN,
        NULL_CAP, cmdline_size, core, 0,
        NULL, NULL, &retval, NULL
    );
    thread_mutex_unlock(&rpc->shared_mem_lock);

    *newpid = retval;
    return err;
}



errval_t
aos_rpc_process_get_name(struct aos_rpc *rpc, domainid_t pid, char **name) {
    // implement name lookup for process given a process id
    size_t name_len;
    errval_t err;

    thread_mutex_lock(&rpc->shared_mem_lock);
    err = do_aos_rpc(
        rpc, RPC_MTYPE_PROCESS_GET_NAME,
        NULL_CAP, 0, pid, 0,
        NULL, &name_len, NULL, NULL
    );
    if (err_is_fail(err)) {
        thread_mutex_unlock(&rpc->shared_mem_lock);
        return err;
    }

    // malloc may use RPC but not the shared memory, so this is fine.
    *name = malloc(name_len);
    if (*name == NULL) {
        thread_mutex_unlock(&rpc->shared_mem_lock);
        return LIB_ERR_MALLOC_FAIL;
    }

    memcpy(*name, rpc->shared_mem, name_len);
    thread_mutex_unlock(&rpc->shared_mem_lock);

    assert(name_len != 0 && (*name)[name_len - 1] == '\0');
    return err;
}


errval_t
aos_rpc_process_get_all_pids(struct aos_rpc *rpc, domainid_t **pids,
                             size_t *pid_count) {
    // implement process id discovery
    size_t ret_len;
    errval_t err;

    thread_mutex_lock(&rpc->shared_mem_lock);
    err = do_aos_rpc(
        rpc, RPC_MTYPE_PROCESS_GET_ALL_PIDS,
        NULL_CAP, 0, 0, 0,
        NULL, &ret_len, NULL, NULL
    );
    if (err_is_fail(err)) {
        thread_mutex_unlock(&rpc->shared_mem_lock);
        return err;
    }

    *pid_count = ret_len / sizeof(**pids);
    *pids = malloc(ret_len);
    if (*pids == NULL) {
        thread_mutex_unlock(&rpc->shared_mem_lock);
        return LIB_ERR_MALLOC_FAIL;
    }

    memcpy(*pids, rpc->shared_mem, ret_len);

    thread_mutex_unlock(&rpc->shared_mem_lock);

    return err;
}


errval_t aos_rpc_ump_register(struct aos_rpc *rpc, enum ump_server_id server_id, struct capref accept_ep_cap, struct capref *reply_ep_cap) {
    return do_aos_rpc(
        rpc, RPC_MTYPE_UMP_REGISTER,
        accept_ep_cap, 0, server_id, 0,
        reply_ep_cap, NULL, NULL, NULL
    );
}

errval_t aos_rpc_ump_connect(struct aos_rpc *rpc, enum ump_server_id server_id, struct capref *ret_cap) {
    return do_aos_rpc(
        rpc, RPC_MTYPE_UMP_CONNECT,
        NULL_CAP, 0, server_id, 0,
        ret_cap, NULL, NULL, NULL
    );
}


// We are allowed to change the signature for this one
errval_t aos_rpc_init(struct aos_rpc *rpc, struct lmp_chan *chan, void *shared_mem)
{
    rpc->chan = chan;
    rpc->shared_mem = shared_mem;
    waitset_init(&rpc->ws);
    thread_mutex_init(&rpc->lock);
    thread_mutex_init(&rpc->shared_mem_lock);
    return SYS_ERR_OK;
}

/**
 * \brief Returns the RPC channel to init.
 */
struct aos_rpc *aos_rpc_get_init_channel(void)
{
    // Return channel to talk to init process
    return get_init_rpc();
}

/**
 * \brief Returns the channel to the memory server
 */
struct aos_rpc *aos_rpc_get_memory_channel(void)
{
    // Return channel to talk to memory server process (or whoever
    // implements memory server functionality)
    return get_init_rpc();
}

/**
 * \brief Returns the channel to the process manager
 */
struct aos_rpc *aos_rpc_get_process_channel(void)
{
    // Return channel to talk to process server process (or whoever
    // implements process server functionality)
    return get_init_rpc();
}

/**
 * \brief Returns the channel to the serial console
 */
struct aos_rpc *aos_rpc_get_serial_channel(void)
{
    // Return channel to talk to serial driver/terminal process (whoever
    // implements print/read functionality)
    return get_init_rpc();
}
