#include <string.h>
#include <generated/csr.h>

#include "log.h"
#include "flash_storage.h"
#include "mailbox.h"
#include "messages.h"
#include "elf_loader.h"
#include "services.h"
#include "kloader.h"

static struct symbol symtab[128];
static int _symtab_count;
static char _symtab_strings[128*16];
static char *_symtab_strptr;

static void symtab_init(void)
{
    memset(symtab, 0, sizeof(symtab));
    _symtab_count = 0;
    _symtab_strptr = _symtab_strings;
}

static int symtab_add(const char *name, void *target)
{
    if(_symtab_count >= sizeof(symtab)/sizeof(symtab[0])) {
        log("Too many provided symbols in object");
        symtab_init();
        return 0;
    }
    symtab[_symtab_count].name = _symtab_strptr;
    symtab[_symtab_count].target = target;
    _symtab_count++;

    while(1) {
        if(_symtab_strptr >= &_symtab_strings[sizeof(_symtab_strings)]) {
            log("Provided symbol string table overflow");
            symtab_init();
            return 0;
        }
        *_symtab_strptr = *name;
        _symtab_strptr++;
        if(*name == 0)
            break;
        name++;
    }

    return 1;
}

int kloader_load(void *buffer, int length)
{
    if(!kernel_cpu_reset_read()) {
        log("BUG: attempted to load while kernel CPU running");
        return 0;
    }
    symtab_init();
    return load_elf(
        resolve_service_symbol, symtab_add,
        buffer, length, (void *)KERNELCPU_PAYLOAD_ADDRESS, 4*1024*1024);
}

kernel_function kloader_find(const char *name)
{
    return find_symbol(symtab, name);
}

extern char _binary_ksupport_bin_start;
extern char _binary_ksupport_bin_end;

static void start_kernel_cpu(void *addr)
{
    memcpy((void *)KERNELCPU_EXEC_ADDRESS, &_binary_ksupport_bin_start,
        &_binary_ksupport_bin_end - &_binary_ksupport_bin_start);
    mailbox_acknowledge();
    mailbox_send(addr);
    kernel_cpu_reset_write(0);
}

void kloader_start_bridge(void)
{
    start_kernel_cpu(NULL);
}

void kloader_start_user_kernel(kernel_function k)
{
    if(!kernel_cpu_reset_read()) {
        log("BUG: attempted to start kernel CPU while already running (user kernel)");
        return;
    }
    start_kernel_cpu((void *)k);
}

void kloader_start_idle_kernel(void)
{
    char buffer[32*1024];
    int len;
    kernel_function k;

    if(!kernel_cpu_reset_read()) {
        log("BUG: attempted to start kernel CPU while already running (idle kernel)");
        return;
    }
#if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE)
    len = fs_read("idle_kernel", buffer, sizeof(buffer), NULL);
    if(len <= 0)
        return;
    if(!kloader_load(buffer, len)) {
        log("Failed to load ELF binary for idle kernel");
        return;
    }
    k = kloader_find("run");
    if(!k) {
        log("Failed to find entry point for ELF kernel");
        return;
    }
    start_kernel_cpu((void *)k);
#endif
}

void kloader_stop(void)
{
    kernel_cpu_reset_write(1);
    mailbox_acknowledge();
}

int kloader_validate_kpointer(void *p)
{
    unsigned int v = (unsigned int)p;
    if((v < 0x40400000) || (v > (0x4fffffff - 1024*1024))) {
        log("Received invalid pointer from kernel CPU: 0x%08x", v);
        return 0;
    }
    return 1;
}

int kloader_is_essential_kmsg(int msgtype)
{
    switch(msgtype) {
        case MESSAGE_TYPE_NOW_INIT_REQUEST:
        case MESSAGE_TYPE_NOW_SAVE:
        case MESSAGE_TYPE_LOG:
            return 1;
        default:
            return 0;
    }
}

long long int now;

void kloader_service_essential_kmsg(void)
{
    struct msg_base *umsg;

    umsg = mailbox_receive();
    if(umsg) {
        if(!kloader_validate_kpointer(umsg))
            return;
        switch(umsg->type) {
            case MESSAGE_TYPE_NOW_INIT_REQUEST: {
                struct msg_now_init_reply reply;

                reply.type = MESSAGE_TYPE_NOW_INIT_REPLY;
                reply.now = now;
                mailbox_send_and_wait(&reply);
                break;
            }
            case MESSAGE_TYPE_NOW_SAVE: {
                struct msg_now_save *msg = (struct msg_now_save *)umsg;

                now = msg->now;
                mailbox_acknowledge();
                break;
            }
            case MESSAGE_TYPE_LOG: {
                struct msg_log *msg = (struct msg_log *)umsg;

                log_va(msg->fmt, msg->args);
                mailbox_acknowledge();
                break;
            }
            default:
                /* handled elsewhere */
                break;
        }
    }
}