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

#include "log.h"
#include "analyzer.h"


#ifdef CSR_RTIO_ANALYZER_BASE

struct analyzer_header {
    unsigned int sent_bytes;
    unsigned long long int total_byte_count;
    unsigned char overflow_occured;
    unsigned char log_channel;
    unsigned char dds_channel;
    unsigned char dds_onehot_sel;
} __attribute__((packed));


#define ANALYZER_BUFFER_SIZE (512*1024)

static struct analyzer_header analyzer_header;
static char analyzer_buffer[ANALYZER_BUFFER_SIZE] __attribute__((aligned(64)));

static void arm(void)
{
    rtio_analyzer_message_encoder_overflow_reset_write(1);
    rtio_analyzer_dma_base_address_write((unsigned int)analyzer_buffer);
    rtio_analyzer_dma_last_address_write((unsigned int)analyzer_buffer + ANALYZER_BUFFER_SIZE - 1);
    rtio_analyzer_dma_reset_write(1);
    rtio_analyzer_dma_enable_write(1);
}

static void disarm(void)
{
    rtio_analyzer_dma_enable_write(0);
    while(rtio_analyzer_dma_busy_read());
    flush_cpu_dcache();
    flush_l2_cache();
}

void analyzer_init(void)
{
    arm();
}

enum {
    SEND_STATE_HEADER,
    SEND_STATE_POST_POINTER, /* send from pointer to end of buffer */
    SEND_STATE_PRE_POINTER, /* send from start of buffer to pointer-1 */
    SEND_STATE_TERMINATE
};

static int send_state;
static int pointer;
static int wraparound;
static int offset_consumed;
static int offset_sent;

void analyzer_start(void)
{
    disarm();

    analyzer_header.total_byte_count = rtio_analyzer_dma_byte_count_read();
    pointer = analyzer_header.total_byte_count % ANALYZER_BUFFER_SIZE;
    wraparound = analyzer_header.total_byte_count >= ANALYZER_BUFFER_SIZE;

    if(wraparound)
        analyzer_header.sent_bytes = ANALYZER_BUFFER_SIZE;
    else
        analyzer_header.sent_bytes = analyzer_header.total_byte_count;

    analyzer_header.overflow_occured = rtio_analyzer_message_encoder_overflow_read();
    analyzer_header.log_channel = CONFIG_RTIO_LOG_CHANNEL;
    analyzer_header.dds_channel = CONFIG_RTIO_DDS_CHANNEL;
#ifdef CONFIG_DDS_ONEHOT_SEL
    analyzer_header.dds_onehot_sel = 1;
#else
    analyzer_header.dds_onehot_sel = 0;
#endif

    offset_consumed = 0;
    offset_sent = 0;
    send_state = SEND_STATE_HEADER;
}

void analyzer_end(void)
{
    arm();
}

int analyzer_input(void *data, int length)
{
    log("no input should be received by analyzer, dropping connection");
    return -1;
}

void analyzer_poll(void **data, int *length)
{
    switch(send_state) {
        case SEND_STATE_HEADER:
            *length = sizeof(struct analyzer_header) - offset_consumed;
            *data = (char *)&analyzer_header + offset_consumed;
            break;
        case SEND_STATE_POST_POINTER:
            *length = ANALYZER_BUFFER_SIZE - pointer - offset_consumed;
            *data = analyzer_buffer + pointer + offset_consumed;
            break;
        case SEND_STATE_PRE_POINTER:
            *length = pointer - offset_consumed;
            *data = analyzer_buffer + offset_consumed;
            break;
        case SEND_STATE_TERMINATE:
            *length = -1;
            break;
        default:
            *length = 0;
            break;
    }
}

void analyzer_ack_consumed(int length)
{
    offset_consumed += length;
}

void analyzer_ack_sent(int length)
{
    offset_sent += length;
    switch(send_state) {
        case SEND_STATE_HEADER:
            if(offset_sent >= sizeof(struct analyzer_header)) {
                offset_consumed = 0;
                offset_sent = 0;
                if(wraparound)
                    send_state = SEND_STATE_POST_POINTER;
                else {
                    if(pointer)
                        send_state = SEND_STATE_PRE_POINTER;
                    else
                        send_state = SEND_STATE_TERMINATE;
                }
            }
            break;
        case SEND_STATE_POST_POINTER:
            if(pointer + offset_consumed >= ANALYZER_BUFFER_SIZE) {
                offset_consumed = 0;
                offset_sent = 0;
                send_state = SEND_STATE_PRE_POINTER;
            }
            break;
        case SEND_STATE_PRE_POINTER:
            if(offset_sent >= pointer)
                send_state = SEND_STATE_TERMINATE;
            break;
    }
}

#endif
