/**
 * \file
 * \brief Hello world application
 */

/*
 * Copyright (c) 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, CAB F.78, Universitaetstr. 6, CH-8092 Zurich,
 * Attn: Systems Group.
 */


#include <stdio.h>
#include <string.h>

#include <aos/aos.h>
#include <aos/aos_rpc.h>
#include <aos/deferred.h>
#include <aos/ump_chan.h>

#define HELLO_CMD_CATCH "catch"
#define HELLO_CMD_SPAWN "spawn"
#define HELLO_CMD_SPAWN_REMOTE "spawn_remote"
#define HELLO_CMD_ECHOCLIENT "echoclient"

#define HELLO_CMDLINE_READ_LEN 100

#define HELLO_TEST_MESSAGE "Hello Server! Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

__attribute__((__used__))
static int null_dereference(void *ignored) {
    debug_printf("[null_dereference] Oh no! :O\n");
    // cause a page fault (slight offset from null simulates NULL struct field access)
    *((volatile char *)(NULL + 7));
    return 1;
}

__attribute__((__used__))
static void handle_payload_recv(void *arg, size_t payload_size, void *payload) {
    printf("Echo client: Received payload(%lu): %.*s\n", payload_size, payload_size, payload);
}

__attribute__((__used__))
static void handle_header_recv(void *arg, size_t header_size, void *header, size_t payload_size) {
    printf("Echo client: Received header: %u\n", *(uint8_t *)header);
    printf("Echo client: Payload is %lu bytes\n", payload_size);
    void *payload = malloc(payload_size);
    assert(payload != NULL);
    ump_recv_payload(arg, payload, handle_payload_recv, NULL);
}

__attribute__((__used__))
static void handle_send(void *arg, struct ump_send_queue_entry *entry) {
    printf("Echo client: Message sent!\n");
}

int main(int argc, char *argv[])
{
    errval_t err;
    printf("Hello, world!\n");

    // test page fault handling by doing a null dereference on a different thread
    // thread_create(null_dereference, NULL);
    // barrelfish_usleep(10000000);

    printf("argv:\n");
    for(int i = 0; i < argc; ++i) {
        printf("  %d -> %s\n", i, argv[i]);
    }

    struct aos_rpc *rpc = aos_rpc_get_init_channel();
    err = aos_rpc_send_number(rpc, 1234);
    if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to send RPC");

    err = aos_rpc_send_string(rpc, "Hello World via RPC!");
    if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to send RPC");

    // try to spawn a child using rpc
    if (argc > 1 && !strncmp(argv[1], HELLO_CMD_SPAWN, sizeof(HELLO_CMD_SPAWN))) {
        debug_printf("Waiting for 1s before showing shell because kernel getchar blocks everything!\n");
        barrelfish_usleep(1000000);

        debug_printf("# ");
        // try to read cmdline from the terminal (a very very primitive shell)
        char cmdline[HELLO_CMDLINE_READ_LEN+1];
        for(int i = 0; i < HELLO_CMDLINE_READ_LEN; ++i) {
            char c = getchar();
            if(c == '\r') {
                printf("\n");
                cmdline[i] = '\0';
                break;
            }
            if(c == 0x7f) {
                if (i > 0) {
                    printf("\b \b"); fflush(stdout);
                }
                --i;
                if(i >= 0) --i;
                continue;
            }

            cmdline[i] = c;
            printf("%c", c); fflush(stdout);
        }
        cmdline[HELLO_CMDLINE_READ_LEN] = '\0';

        printf("Got command line: %s\n", cmdline);

        // start process
        domainid_t pid;
        rpc = aos_rpc_get_process_channel();
        err = aos_rpc_process_spawn(rpc, cmdline, 0, &pid);
        if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to spawn process");
        printf("Started a new process!\n");

        // get process name
        char *name;
        err = aos_rpc_process_get_name(rpc, pid, &name);
        if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to get process name");
        printf("Process Name: %s\n", name);

        // get all PIDs
        domainid_t *pids;
        size_t pid_count;
        err = aos_rpc_process_get_all_pids(rpc, &pids, &pid_count);
        if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to get all PIDs");
        printf("PIDs:\n");
        for(size_t i = 0; i < pid_count; ++i) {
            printf(" %lu\n", pids[i]);
        }
    }

    // if we receive the catch command, RUN!
    if (argc > 1 && !strncmp(argv[1], HELLO_CMD_CATCH, sizeof(HELLO_CMD_CATCH))) {
        int iter = 0;
        while(1) {
            printf("Catch me if you can! %d\n", iter);
            // wait for 1 second
            barrelfish_usleep(1000000);
            ++iter;
        }
    }

    if (argc > 1 && !strncmp(argv[1], HELLO_CMD_SPAWN_REMOTE, sizeof(HELLO_CMD_SPAWN_REMOTE))) {
        printf("Spawning process on core 1...\n");
        domainid_t pid;
        rpc = aos_rpc_get_process_channel();
        err = aos_rpc_process_spawn(rpc, "hello world", 1, &pid);
        if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to spawn process");
        printf("Spawn succeeded, pid=%"PRIuDOMAINID"\n", pid);
    }

    if (argc > 1 && !strncmp(argv[1], HELLO_CMD_ECHOCLIENT, sizeof(HELLO_CMD_ECHOCLIENT))) {
        printf("Echo client: Connecting to echo server...\n");
        struct capref echo_cap;
        for (int attempts = 0; attempts < 50; attempts++) {
            err = aos_rpc_ump_connect(rpc, UMP_SERVER_ECHO, &echo_cap);
            if (err != LIB_ERR_UMP_NOT_REGISTERED) break;
            barrelfish_usleep(100000);
        }
        if (err_is_fail(err)) USER_PANIC_ERR(err, "Failed to connect to echo server");
        printf("Echo client: Connected to echo server.\n");

        // Create the two uni-directional channels
        struct ump_send_chan *send_chan;
        struct ump_recv_chan *recv_chan;
        err = ump_chan_init(UMP_ROLE_CLIENT, &send_chan, &recv_chan, 1, echo_cap, NULL);
        if (err_is_fail(err)) return err;

        // send a message to the server
        printf("Echo client: Sending Message\n");
        uint8_t *value_to_send = malloc(sizeof(uint8_t));
        *value_to_send = 37;
        struct ump_send_queue_entry *entry = malloc(sizeof(struct ump_send_queue_entry));
        ump_send(send_chan, entry, 1, value_to_send, sizeof(HELLO_TEST_MESSAGE), HELLO_TEST_MESSAGE, handle_send, NULL);

        // try to receive and print a message from the server
        printf("Echo client: Receiving Message\n");
        ump_recv_header(recv_chan, handle_header_recv, recv_chan);
    }

    return EXIT_SUCCESS;
}
