#include "hal_data.h"

#include <stdbool.h>
#include <stdint.h>

#include "fifo.h"
#include "udp_cdc.h"

#define LINE_CODING_LENGTH  (0x07U)
#define READ_BUF_SIZE       (2048U)

#define APP_ERR_TRAP(a)     if(a) {__asm("BKPT #0\n");}

#define WAITFORTXCOMP       { while (!txcomplete) udp_event_poll (); }//__WFI ();

static TFIFO FIFO;

static bool b_usb_attach = false;
static usb_pcdc_linecoding_t g_line_coding;
static uint8_t g_buf[READ_BUF_SIZE];
static bool txcomplete = true;

static void udp_event_poll (void) {
    usb_status_t usb_event = USB_STATUS_NONE;
    usb_event_info_t event_info = {0};

    while (1) {
        if (R_USB_EventGet (&event_info, &usb_event) == FSP_SUCCESS) {
            switch (usb_event) {
                case USB_STATUS_NONE:
                    return;
                case USB_STATUS_CONFIGURED:
                    if (R_USB_Read (&g_basic0_ctrl, g_buf, READ_BUF_SIZE, USB_CLASS_PCDC) == FSP_SUCCESS) {

                    }
                    break;

                case USB_STATUS_READ_COMPLETE:
                    if (b_usb_attach) {
                        if (R_USB_Read (&g_basic0_ctrl, g_buf, READ_BUF_SIZE, USB_CLASS_PCDC) == FSP_SUCCESS) {
                            for (int i = 0; i < event_info.data_size; i++) FIFO_WD (FIFO.rx, g_buf[i]);
                        }
                    }
                    break;

                case USB_STATUS_WRITE_COMPLETE:
                    txcomplete = true;
                    break;

                case USB_STATUS_REQUEST :
                    if (USB_PCDC_SET_LINE_CODING == (event_info.setup.request_type & USB_BREQUEST)) {
                        R_USB_PeriControlDataGet (&g_basic0_ctrl, (uint8_t *) &g_line_coding, LINE_CODING_LENGTH );
                    } else if (USB_PCDC_GET_LINE_CODING == (event_info.setup.request_type & USB_BREQUEST)) {
                        R_USB_PeriControlDataSet (&g_basic0_ctrl, (uint8_t *) &g_line_coding, LINE_CODING_LENGTH );
                    } else if (USB_PCDC_SET_CONTROL_LINE_STATE == (event_info.setup.request_type & USB_BREQUEST)) {
                        R_USB_PeriControlStatusSet (&g_basic0_ctrl, USB_SETUP_STATUS_ACK);
                    } else {
                        /* none */
                    }

                    break;

                case USB_STATUS_DETACH:
                case USB_STATUS_SUSPEND:
                    b_usb_attach = false;
                    memset (g_buf, 0, sizeof(g_buf));
                    txcomplete = true;
                    break;
                case USB_STATUS_RESUME:
                    b_usb_attach = true;
                    txcomplete = true;
                    break;

                case USB_STATUS_REQUEST_COMPLETE:
                    txcomplete = true;
                    break;
                default:
                    break;
            }
        } else
            return;
    }
}

static void push_tx (int l) {
    if (l) {
        WAITFORTXCOMP;
        txcomplete = false;
        R_USB_Write (&g_basic0_ctrl, (uint8_t*)FIFO.tx.buf, l, USB_CLASS_PCDC);
    }
}

// TX BUFFER
uint16_t udp_tx_buff (void) {
    udp_event_poll ();
    return txcomplete ? 0 : 1;
}

// RX BUFFER
uint16_t udp_rx_buff (void) {
    udp_event_poll ();
    int s = (int)FIFO.rx.wp - (int)FIFO.rx.rp;
    if (s < 0) s = s + (int)FIFO.rx.maxlen;
    return s;
}

// TX PURGE
void udp_tx_purge (void) {
    FIFO_RESET (FIFO.tx);
    udp_event_poll ();
}

// RX PURGE
void udp_rx_purge (void) {
    FIFO_RESET (FIFO.rx);
    udp_event_poll ();
}

void udp_putc (char ch) {
    udp_event_poll ();
    FIFO.tx.buf[0] = ch;
    push_tx (1);
}

void udp_puts (const char *s) {
    while (*s) {
        udp_putc (*s);
        if (*s++ == '\n') udp_putc ('\r');
    }
}

extern void __aeabi_memcpy4 (void *dest, const void *source, size_t n);

uint16_t udp_putsb (const uint8_t *s, int len) {
    int j = len, k;

    if (len <= FIFO.tx.maxlen) {
        WAITFORTXCOMP;
        __aeabi_memcpy4 (FIFO.tx.buf, s, len);
        push_tx (len);
    } else {
        k = FIFO.tx.maxlen;
        do {
            WAITFORTXCOMP;
            __aeabi_memcpy4 (FIFO.tx.buf, s, k);
            push_tx (k);
            s += k;
            j -= FIFO.tx.maxlen;
            if (j < FIFO.tx.maxlen) k = j;
        } while (j > 0);
    }
    return len;
}

// RECEIVE 1 BYTE CHAR
char udp_getc (void) {
    uint8_t c;
    while (!udp_rx_buff()) __WFI ();
    FIFO_RD (FIFO.rx, c);
    return (char)c;
}

// RECEIVE TEXT
uint16_t udp_gets (char *s, int len) {
    int i = 0;
    char c;

    while (1) {
        c = udp_getc();
        if ((c == '\n') || (c == '\r')) {
            s[i++] = '\0';
      //      _SCI_PUTC ('\n');
            break;
        } else if ((i > 0) && (c == '\b')) {
            udp_puts ("\b \b");
            i--;
        } else if ((i < len) && (i >= 0) && (c >= ' ') && (c <= '~')) {
            s[i++] = (char)c;
            udp_putc (c);
        } else udp_putc ('\a');
    }
    return i;
}


uint32_t udp_init (uint8_t *txbuf, uint16_t txl, uint8_t *rxbuf, uint16_t rxl) {
    FIFO.tx.buf = txbuf;
    FIFO.tx.maxlen = txl;

    FIFO_INIT (FIFO.rx, rxbuf, rxl);

    return (R_USB_Open (&g_basic0_ctrl, &g_basic0_cfg) == FSP_SUCCESS) ? 1 : 0;
}

void udp_deinit (void) {
    R_USB_Stop(&g_basic0_ctrl, USB_TRANSFER_READ, USB_CLASS_PCDC);
    R_USB_Stop(&g_basic0_ctrl, USB_TRANSFER_WRITE, USB_CLASS_PCDC);
    R_USB_Close(&g_basic0_ctrl);
}
