#include "compiler.h"
#include "common.h"
#include "io.h"
#include "console.h"
#include "esp.h"
#include "boardinfo_fpga.h"
#include "config.h"

struct esplink_head __esplink_head esplink_head;
static volatile __esplink struct esplink_timesync tsync;
static volatile __esplink struct esplink_ota ota;

volatile __esplink char board_info_esp[BOARDINFO_SIZE];

IRQHANDLER(esp,0)
{
    uint32_t irqstatus = ESP_CPU_IRQ;
    ESP_CPU_IRQ_CLR = irqstatus;

    if (irqstatus & (1 << EL_DIRQ_UNDERRUN)) {
	con_printf("esp: ESP link memory underrun!!\n");
	ESP_SPI_IRQ = (1 << EL_UIRQ_READY); /* Block writes, reinitialize! */
	return;
    }
    if (irqstatus & (1 << EL_DIRQ_TIME)) {
	if (tsync.set.update) {
	    SYSCLOCK_TICK_HOLD = tsync.set.tick;
	    SYSCLOCK_DATETIME  = tsync.set.td;
	    tsync.set.update = 0;
	    do_write_rtc = true;
	} else {
	    tsync.get.td   = SYSCLOCK_DATETIME;
	    tsync.get.tick = SYSCLOCK_TICK_HOLD;
	    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_TIME;
	}
    }
    if (irqstatus & (1 << EL_DIRQ_BOARDINFO))
	do_update_boardinfo = true;

    if (irqstatus & (1 << EL_DIRQ_HELLO)) {
	con_printf("esp: Got hello, sending ready...\n");
	/* Hello, are you there? Yes, I'm here, and you can write data now */
	ESP_SPI_IRQ_SET = (1 << EL_UIRQ_READY)|(1 << EL_UIRQ_WREN);
    }

    if (irqstatus & (1 << EL_DIRQ_CONFIG))
	do_update_config = true;

    /*
     * Check to see if we got hello after an OTA process was completed
     * or aborted; ESP will send EL_DIRQ_DONE to wake us up.
     */
    if (!(ESP_SPI_IRQ & (1 << EL_UIRQ_OTA))) {
	ota.data = NULL;
	ota.len  = 0;
    }
}

void esp_ota(const void *data, size_t len)
{
    mask_irq(ESP_IRQ);

    ota.data = data;
    ota.len  = len;
    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_OTA;

    unmask_irq(ESP_IRQ);

    while (ota.data)
	waitfor(ESP_IRQ);
}

#define RINGBUF_BUF_SIZE 4096	/* Must be a power of 2 */

static __esplink uint8_t rb_buf[EL_RB_COUNT][2][RINGBUF_BUF_SIZE];

static __esplink struct esplink_ringbuf_desc       rb_desc[EL_RB_COUNT];
static volatile __esplink struct esplink_ptrs_dstr rb_dstr[EL_RB_COUNT];
static volatile __esplink struct esplink_ptrs_ustr rb_ustr[EL_RB_COUNT];

static void esplink_rb_init(void)
{
    unsigned int i;

    for (i = 0; i < EL_RB_COUNT; i++) {
	rb_desc[i].dstr.start = rb_buf[i][0];
	rb_desc[i].dstr.size  = RINGBUF_BUF_SIZE;
	rb_desc[i].ustr.start = rb_buf[i][1];
	rb_desc[i].ustr.size  = RINGBUF_BUF_SIZE;
    }

    esplink_head.rb.desc       = rb_desc;
    esplink_head.rb.dstr       = (void *)rb_dstr;
    esplink_head.rb.ustr       = (void *)rb_ustr;
    esplink_head.rb.count      = EL_RB_COUNT;
}

/*
 * Read and write from ring buffers. These are atomic only; on failure
 * returns the amount of data/space available, but does NOT advance
 * any pointers nor copy any data.
 */
size_t esp_rb_read(enum esplink_ringbuf_user ring, void *data, size_t len)
{
    const size_t size          = rb_desc[ring].dstr.size;
    const size_t sizemask      = size - 1;
    const uint8_t * const base = rb_desc[ring].dstr.start;
    size_t head                = rb_ustr[ring].head;
    size_t tail                = rb_dstr[ring].tail;
    size_t avail               = (head - tail) & sizemask;
    uint8_t *p                 = data;
    size_t xlen                = len;

    if (!len)
	return 0;

    if (avail < xlen)
	return avail;

    if (tail + xlen > size) {
	size_t left = size - tail;
	memcpy(p, base + tail, left);
	p += left;
	xlen -= left;
	tail = 0;
    }
    memcpy(p, base + tail, xlen);
    tail = (tail + xlen) & sizemask;

    rb_dstr[ring].tail = tail;
    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_RINGBUF;

    return len;
}

size_t esp_rb_write(enum esplink_ringbuf_user ring,
		    const void *data, size_t len)
{
    const size_t size          = RINGBUF_BUF_SIZE;
    const size_t sizemask      = size - 1;
    uint8_t * const base       = rb_desc[ring].ustr.start;
    size_t tail                = rb_ustr[ring].tail;
    size_t head                = rb_dstr[ring].head;
    size_t avail               = (tail - head - 1) & sizemask;
    const uint8_t *p           = data;
    size_t xlen                = len;

    if (!len)
	return 0;

    if (avail < xlen)
	return avail;

    if (head + xlen > size) {
	size_t left = size - head;
	memcpy(base + head, p, left);
	p += left;
	xlen -= left;
	head = 0;
    }
    memcpy(base + head, p, xlen);
    head = (head + xlen) & sizemask;

    rb_dstr[ring].head = head;
    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_RINGBUF;

    return len;
}

/*
 * Faster/simpler versions for reading/writing a single byte at a time;
 * esp_rb_getc() returns -1 if the ring is empty.
 *
 * esp_rb_putc() returns -1 on failure, otherwise the number of
 * bytes still free in the ring (a nonnegative number.)
 */
__hot int esp_rb_getc(enum esplink_ringbuf_user ring)
{
    const size_t size          = RINGBUF_BUF_SIZE;
    const size_t sizemask      = size - 1;
    const uint8_t * const base = rb_desc[ring].dstr.start;
    size_t head                = rb_ustr[ring].head;
    size_t tail                = rb_dstr[ring].tail;
    int data;

    if (tail == head)
	return -1;		/* Buffer empty */

    data = base[tail];
    tail = (tail + 1) & sizemask;
    rb_dstr[ring].tail = tail;
    ESP_SPI_IRQ_SET = 1 << EL_UIRQ_RINGBUF;

    return data;
}

__hot int esp_rb_putc(enum esplink_ringbuf_user ring, uint8_t data)
{
    const size_t size          = RINGBUF_BUF_SIZE;
    const size_t sizemask      = size - 1;
    uint8_t * const base       = rb_desc[ring].ustr.start;
    size_t tail                = rb_ustr[ring].tail;
    size_t head                = rb_dstr[ring].head;
    int avail                  = (tail - head - 1) & sizemask;

    if (avail--) {
	base[head] = data;
	head = (head + 1) & sizemask;
	rb_dstr[ring].head = head;
	ESP_SPI_IRQ_SET = 1 << EL_UIRQ_RINGBUF;
    }

    return avail;
}

__hot enum ringbuf_status esp_rb_status(enum esplink_ringbuf_user ring)
{
    const size_t size          = RINGBUF_BUF_SIZE;
    const size_t sizemask      = size - 1;
    const size_t utail         = rb_ustr[ring].tail;
    const size_t dhead         = rb_ustr[ring].head;
    const size_t uhead         = rb_dstr[ring].head;
    const size_t dtail         = rb_dstr[ring].tail;
    enum ringbuf_status status = RB_CONNECTED;

    if (dhead != dtail)
	status |= RB_RXDATA;
    if ((utail - uhead - 1) & sizemask)
	status |= RB_TXFREE;

    return status;
}

void esp_init(void)
{
    static char __dram_data esp_signature[] = "Hej tomtebuggar slå i glasen!";

    ESP_CPU_IRQ                = 0;
    ESP_SPI_IRQ                = 0;

    memset(&esplink_head, 0, sizeof esplink_head);

    esplink_head.hlen          = sizeof esplink_head;
    esplink_head.board.cfg     = SYS_BOARDCFG;
    memcpy(esplink_head.signature, esp_signature, sizeof esp_signature);

    esplink_head.board_info    = &board_info_raw;
    esplink_head.tsync         = &tsync;
    esplink_head.ota           = &ota;
    esplink_head.cfg.buf       = config_buf;
    esplink_head.cfg.buflen    = sizeof config_buf;

    esplink_rb_init();

    esplink_head.magic         = ESPLINK_HEAD_MAGIC;
    ESP_SPI_IRQ                = (1 << EL_UIRQ_READY);

    unmask_irq(ESP_IRQ);
}
