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

#include <generated/csr.h>

#include "mailbox.h"
#include "messages.h"

#include "clock.h"
#include "log.h"
#include "kloader.h"
#include "exceptions.h"
#include "flash_storage.h"
#include "rtiocrg.h"
#include "session.h"

#define BUFFER_IN_SIZE (1024*1024)
#define BUFFER_OUT_SIZE (1024*1024)

static int buffer_in_index;
/* The 9th byte (right after the header) of buffer_in must be aligned 
 * to a 32-bit boundary for elf_loader to work.
 */
static struct {
    char padding[3];
    char data[BUFFER_IN_SIZE];
} __attribute__((packed)) _buffer_in __attribute__((aligned(4)));
#define buffer_in _buffer_in.data
static int buffer_out_index_data;
static int buffer_out_index_mem;
static char buffer_out[BUFFER_OUT_SIZE];

static int get_in_packet_len(void)
{
    int r;

    memcpy(&r, &buffer_in[4], 4);
    return r;
}

static int get_out_packet_len(void)
{
    int r;

    memcpy(&r, &buffer_out[4], 4);
    return r;
}

static void submit_output(int len)
{
    memset(&buffer_out[0], 0x5a, 4);
    memcpy(&buffer_out[4], &len, 4);
    buffer_out_index_data = 0;
    buffer_out_index_mem = 0;
}

static int user_kernel_state;

enum {
    USER_KERNEL_NONE = 0,
    USER_KERNEL_LOADED,
    USER_KERNEL_RUNNING,
    USER_KERNEL_WAIT_RPC /* < must come after _RUNNING */
};

void session_start(void)
{
    buffer_in_index = 0;
    memset(&buffer_out[4], 0, 4);
    kloader_stop();
    user_kernel_state = USER_KERNEL_NONE;
    now = -1;
}

void session_end(void)
{
    kloader_stop();
    now = -1;
    kloader_start_idle_kernel();
}

/* host to device */
enum {
    REMOTEMSG_TYPE_LOG_REQUEST = 1,
    REMOTEMSG_TYPE_IDENT_REQUEST,
    REMOTEMSG_TYPE_SWITCH_CLOCK,
    
    REMOTEMSG_TYPE_LOAD_OBJECT,
    REMOTEMSG_TYPE_RUN_KERNEL,

    REMOTEMSG_TYPE_RPC_REPLY,

    REMOTEMSG_TYPE_FLASH_READ_REQUEST,
    REMOTEMSG_TYPE_FLASH_WRITE_REQUEST,
    REMOTEMSG_TYPE_FLASH_ERASE_REQUEST,
    REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST
};

/* device to host */
enum {
    REMOTEMSG_TYPE_LOG_REPLY = 1,
    REMOTEMSG_TYPE_IDENT_REPLY,
    REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED,
    REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED,

    REMOTEMSG_TYPE_LOAD_COMPLETED,
    REMOTEMSG_TYPE_LOAD_FAILED,

    REMOTEMSG_TYPE_KERNEL_FINISHED,
    REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED,
    REMOTEMSG_TYPE_KERNEL_EXCEPTION,

    REMOTEMSG_TYPE_RPC_REQUEST,

    REMOTEMSG_TYPE_FLASH_READ_REPLY,
    REMOTEMSG_TYPE_FLASH_OK_REPLY,
    REMOTEMSG_TYPE_FLASH_ERROR_REPLY
};

static int check_flash_storage_key_len(char *key, unsigned int key_len)
{
    if(key_len == get_in_packet_len() - 8) {
        log("Invalid key: not a null-terminated string");
        buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY;
        submit_output(9);
        return 0;
    }
    return 1;
}

static int process_input(void)
{
    switch(buffer_in[8]) {
        case REMOTEMSG_TYPE_LOG_REQUEST:
#if (LOG_BUFFER_SIZE + 9) > BUFFER_OUT_SIZE
#error Output buffer cannot hold the log buffer
#endif
            buffer_out[8] = REMOTEMSG_TYPE_LOG_REPLY;
            log_get(&buffer_out[9]);
            submit_output(9 + LOG_BUFFER_SIZE);
            break;
        case REMOTEMSG_TYPE_IDENT_REQUEST:
            buffer_out[8] = REMOTEMSG_TYPE_IDENT_REPLY;
            buffer_out[9] = 'A';
            buffer_out[10] = 'R';
            buffer_out[11] = 'O';
            buffer_out[12] = 'R';
            submit_output(13);
            break;
        case REMOTEMSG_TYPE_SWITCH_CLOCK:
            if(user_kernel_state >= USER_KERNEL_RUNNING) {
                log("Attempted to switch RTIO clock while kernel running");
                buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED;
                submit_output(9);
                break;
            }
            if(rtiocrg_switch_clock(buffer_in[9]))
                buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED;
            else
                buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED;
            submit_output(9);
            break;
        case REMOTEMSG_TYPE_LOAD_OBJECT:
            if(user_kernel_state >= USER_KERNEL_RUNNING) {
                log("Attempted to load new kernel while already running");
                buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED;
                submit_output(9);
                break;    
            }
            if(kloader_load(&buffer_in[9], get_in_packet_len() - 8)) {
                buffer_out[8] = REMOTEMSG_TYPE_LOAD_COMPLETED;
                user_kernel_state = USER_KERNEL_LOADED;
            } else
                buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED;
            submit_output(9);
            break;
        case REMOTEMSG_TYPE_RUN_KERNEL: {
            kernel_function k;

            if(user_kernel_state != USER_KERNEL_LOADED) {
                log("Attempted to run kernel while not in the LOADED state");
                buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
                submit_output(9);
                break;
            }

            if((buffer_in_index + 1) > BUFFER_OUT_SIZE) {
                log("Kernel name too long");
                buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
                submit_output(9);
                break;
            }
            buffer_in[buffer_in_index] = 0;

            k = kloader_find((char *)&buffer_in[9]);
            if(k == NULL) {
                log("Failed to find kernel entry point '%s' in object", &buffer_in[9]);
                buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED;
                submit_output(9);
                break;
            }

            watchdog_init();
            kloader_start_user_kernel(k);
            user_kernel_state = USER_KERNEL_RUNNING;
            break;
        }
        case REMOTEMSG_TYPE_RPC_REPLY: {
            struct msg_rpc_reply reply;

            if(user_kernel_state != USER_KERNEL_WAIT_RPC) {
                log("Unsolicited RPC reply");
                return 0;
            }

            reply.type = MESSAGE_TYPE_RPC_REPLY;
            memcpy(&reply.eid, &buffer_in[9], 4);
            memcpy(&reply.retval, &buffer_in[13], 4);
            mailbox_send_and_wait(&reply);
            user_kernel_state = USER_KERNEL_RUNNING;
            break;
        }
        case REMOTEMSG_TYPE_FLASH_READ_REQUEST: {
#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9
#error Output buffer cannot hold the flash storage data
#elif SPIFLASH_SECTOR_SIZE - 4 > BUFFER_IN_SIZE - 9
#error Input buffer cannot hold the flash storage data
#endif
            unsigned int ret, in_packet_len;
            char *key;

            in_packet_len = get_in_packet_len();
            key = &buffer_in[9];
            buffer_in[in_packet_len] = '\0';

            buffer_out[8] = REMOTEMSG_TYPE_FLASH_READ_REPLY;
            ret = fs_read(key, &buffer_out[9], sizeof(buffer_out) - 9, NULL);
            submit_output(9 + ret);
            break;
        }
        case REMOTEMSG_TYPE_FLASH_WRITE_REQUEST: {
            char *key, *value;
            unsigned int key_len, value_len, in_packet_len;
            int ret;

            in_packet_len = get_in_packet_len();
            key = &buffer_in[9];
            key_len = strnlen(key, in_packet_len - 9) + 1;
            if(!check_flash_storage_key_len(key, key_len))
                break;

            value_len = in_packet_len - key_len - 9;
            value = key + key_len;
            ret = fs_write(key, value, value_len);

            if(ret)
                buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
            else
                buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY;
            submit_output(9);
            break;
        }
        case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: {
            fs_erase();
            buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
            submit_output(9);
            break;
        }
        case REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST: {
            char *key;
            unsigned int in_packet_len;

            in_packet_len = get_in_packet_len();
            key = &buffer_in[9];
            buffer_in[in_packet_len] = '\0';

            fs_remove(key);
            buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY;
            submit_output(9);
            break;
        }
        default:
            return 0;
    }
    return 1;
}

/* Returns -1 in case of irrecoverable error
 * (the session must be dropped and session_end called)
 */
int session_input(void *data, int len)
{
    unsigned char *_data = data;
    int consumed;

    consumed = 0;
    while(len > 0) {
        /* Make sure the output buffer is available for any reply
         * we might need to send. */
        if(get_out_packet_len() != 0)
            return consumed;

        if(buffer_in_index < 4) {
            /* synchronizing */
            if(_data[consumed] == 0x5a)
                buffer_in[buffer_in_index++] = 0x5a;
            else
                buffer_in_index = 0;
            consumed++; len--;
        } else if(buffer_in_index < 8) {
            /* receiving length */
            buffer_in[buffer_in_index++] = _data[consumed];
            consumed++; len--;
            if((buffer_in_index == 8) && (get_in_packet_len() == 0))
                /* zero-length packet = session reset */
                return -2;
        } else {
            /* receiving payload */
            int packet_len;
            int count;

            packet_len = get_in_packet_len();
            if(packet_len > BUFFER_IN_SIZE)
                return -1;
            count = packet_len - buffer_in_index;
            if(count > len)
                count = len;
            memcpy(&buffer_in[buffer_in_index], &_data[consumed], count);
            buffer_in_index += count;

            if(buffer_in_index == packet_len) {
                if(!process_input())
                    return -1;
                buffer_in_index = 0;
            }

            consumed += count; len -= count;
        }
    }
    return consumed;
}

static int add_base_rpc_value(char base_type, void *value, char *buffer_out, int available_space)
{
    switch(base_type) {
        case 'n':
            return 0;
        case 'b':
            if(available_space < 1)
                return -1;
            if(*(char *)value)
                buffer_out[0] = 1;
            else
                buffer_out[0] = 0;
            return 1;
        case 'i':
            if(available_space < 4)
                return -1;
            memcpy(buffer_out, value, 4);
            return 4;
        case 'I':
        case 'f':
            if(available_space < 8)
                return -1;
            memcpy(buffer_out, value, 8);
            return 8;
        case 'F':
            if(available_space < 16)
                return -1;
            memcpy(buffer_out, value, 16);
            return 16;
        default:
            return -1;
    }
}

static int add_rpc_value(int bi, int type_tag, void *value)
{
    char base_type;
    int obi, r;

    obi = bi;
    base_type = type_tag;

    if((bi + 1) > BUFFER_OUT_SIZE)
        return -1;
    buffer_out[bi++] = base_type;

    if(base_type == 'l') {
        char elt_type;
        int len;
        int i, p;

        elt_type = type_tag >> 8;
        if((bi + 1) > BUFFER_OUT_SIZE)
            return -1;
        buffer_out[bi++] = elt_type;

        len = *(int *)value;
        if((bi + 4) > BUFFER_OUT_SIZE)
            return -1;
        memcpy(&buffer_out[bi], &len, 4);
        bi += 4;

        p = 4;
        for(i=0;i<len;i++) {
            r = add_base_rpc_value(elt_type, (char *)value + p,
                                   &buffer_out[bi], BUFFER_OUT_SIZE - bi);
            if(r < 0)
                return r;
            bi += r;
            p += r;
        }
    } else {
        r = add_base_rpc_value(base_type, value,
                               &buffer_out[bi], BUFFER_OUT_SIZE - bi);
        if(r < 0)
            return r;
        bi += r;
    }

    return bi - obi;
}

static int send_rpc_request(int rpc_num, va_list args)
{
    int r;
    int bi = 8;
    int type_tag;
    void *v;

    buffer_out[bi++] = REMOTEMSG_TYPE_RPC_REQUEST;

    memcpy(&buffer_out[bi], &rpc_num, 4);
    bi += 4;

    while((type_tag = va_arg(args, int))) {
        if(type_tag == 'n')
            v = NULL;
        else {
            v = va_arg(args, void *);
            if(!kloader_validate_kpointer(v))
                return 0;
        }
        r = add_rpc_value(bi, type_tag, v);
        if(r < 0)
            return 0;
        bi += r;
    }
    if((bi + 1) > BUFFER_OUT_SIZE)
        return 0;
    buffer_out[bi++] = 0;

    submit_output(bi);
    return 1;
}

/* assumes output buffer is empty when called */
static int process_kmsg(struct msg_base *umsg)
{
    if(!kloader_validate_kpointer(umsg))
        return 0;
    if(kloader_is_essential_kmsg(umsg->type))
        return 1; /* handled elsewhere */
    if(user_kernel_state != USER_KERNEL_RUNNING) {
        log("Received unexpected message from kernel CPU while not in running state");
        return 0;
    }

    switch(umsg->type) {
        case MESSAGE_TYPE_FINISHED:
            buffer_out[8] = REMOTEMSG_TYPE_KERNEL_FINISHED;
            submit_output(9);

            kloader_stop();
            user_kernel_state = USER_KERNEL_LOADED;
            mailbox_acknowledge();
            break;
        case MESSAGE_TYPE_EXCEPTION: {
            struct msg_exception *msg = (struct msg_exception *)umsg;

            buffer_out[8] = REMOTEMSG_TYPE_KERNEL_EXCEPTION;
            memcpy(&buffer_out[9], &msg->eid, 4);
            memcpy(&buffer_out[13], msg->eparams, 3*8);
            submit_output(9+4+3*8);

            kloader_stop();
            user_kernel_state = USER_KERNEL_LOADED;
            mailbox_acknowledge();
            break;
        }
        case MESSAGE_TYPE_WATCHDOG_SET_REQUEST: {
            struct msg_watchdog_set_request *msg = (struct msg_watchdog_set_request *)umsg;
            struct msg_watchdog_set_reply reply;

            reply.type = MESSAGE_TYPE_WATCHDOG_SET_REPLY;
            reply.id = watchdog_set(msg->ms);
            mailbox_send_and_wait(&reply);
            break;
        }
        case MESSAGE_TYPE_WATCHDOG_CLEAR: {
            struct msg_watchdog_clear *msg = (struct msg_watchdog_clear *)umsg;

            watchdog_clear(msg->id);
            mailbox_acknowledge();
            break;
        }
        case MESSAGE_TYPE_RPC_REQUEST: {
            struct msg_rpc_request *msg = (struct msg_rpc_request *)umsg;

            if(!send_rpc_request(msg->rpc_num, msg->args))
                return 0;
            user_kernel_state = USER_KERNEL_WAIT_RPC;
            mailbox_acknowledge();
            break;
        }
        default: {
            log("Received invalid message type from kernel CPU");
            return 0;
        }
    }
    return 1;
}

/* len is set to -1 in case of irrecoverable error
 * (the session must be dropped and session_end called)
 */
void session_poll(void **data, int *len)
{
    int l;

    if(user_kernel_state == USER_KERNEL_RUNNING) {
        if(watchdog_expired()) {
            log("Watchdog expired");
            *len = -1;
            return;
        }
        if(!rtiocrg_check()) {
            log("RTIO clock failure");
            *len = -1;
            return;
        }
    }

    l = get_out_packet_len();

    /* If the output buffer is available, 
     * check if the kernel CPU has something to transmit.
     */
    if(l == 0) {
        struct msg_base *umsg;

        umsg = mailbox_receive();
        if(umsg) {
            if(!process_kmsg(umsg)) {
                *len = -1;
                return;
            }
        }
        l = get_out_packet_len();
    }

    if(l > 0) {
        *len = l - buffer_out_index_data;
        *data = &buffer_out[buffer_out_index_data];
    } else
        *len = 0;
}

void session_ack_data(int len)
{
    buffer_out_index_data += len;
}

void session_ack_mem(int len)
{
    buffer_out_index_mem += len;
    if(buffer_out_index_mem >= get_out_packet_len())
        memset(&buffer_out[4], 0, 4);
}