/* Megumin LED display firmware
 * Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "serial.h"
#include "adc.h"
#include "mac.h"
#include "led.h"

unsigned int uart_overruns = 0;
unsigned int frame_overruns = 0;
unsigned int invalid_frames = 0;

static union tx_buf_union tx_buf;
volatile union rx_buf_union rx_buf;

void serial_init() {
    USART1->CR1 = /* 8-bit -> M1, M0 clear */
        /* RTOIE clear */
          (8 << USART_CR1_DEAT_Pos) /* 8 sample cycles/1 bit DE assertion time */
        | (8 << USART_CR1_DEDT_Pos) /* 8 sample cycles/1 bit DE assertion time */
        /* OVER8 clear. Use default 16x oversampling */
        /* CMIF clear */
        | USART_CR1_MME
        /* WAKE clear */
        /* PCE, PS clear */
        | USART_CR1_RXNEIE /* Enable receive interrupt */
        /* other interrupts clear */
        | USART_CR1_TE
        | USART_CR1_RE;
    //USART1->CR2 = USART_CR2_RTOEN; /* Timeout enable */
    USART1->CR3 = USART_CR3_DEM; /* RS485 DE enable (output on RTS) */
    /* Set divider for 2MHz baud rate @50MHz system clock. */
    int usartdiv = 25;
    USART1->BRR = usartdiv;

    /* And... go! */
    USART1->CR1 |= USART_CR1_UE;

    /* Enable receive interrupt */
    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_SetPriority(USART1_IRQn, 1);
}

void tx_char(uint8_t c) {
    while (!(USART1->ISR & USART_ISR_TC));
    USART1->TDR = c;
}

void send_frame_formatted(uint8_t *buf, int len) {
    uint8_t *p=buf, *q=buf, *end=buf+len;
    do {
        while (*q && q!=end)
            q++;
        tx_char(q-p+1);
        while (*p && p!=end)
            tx_char(*p++);
        p++, q++;
    } while (p < end);
    tx_char('\0');
}

void send_status_reply(void) {
    tx_buf.desc_reply.firmware_version = FIRMWARE_VERSION;
    tx_buf.desc_reply.hardware_version = HARDWARE_VERSION;
    tx_buf.desc_reply.digit_rows = NROWS;
    tx_buf.desc_reply.digit_cols = NCOLS;
    tx_buf.desc_reply.uptime_s = sys_time_seconds;
    tx_buf.desc_reply.vcc_mv = adc_vcc_mv;
    tx_buf.desc_reply.temp_celsius = adc_temp_celsius;
    tx_buf.desc_reply.nbits = nbits;
    tx_buf.desc_reply.framerate_millifps = frame_duration_us > 0 ? 1000000000 / frame_duration_us : 0;
    tx_buf.desc_reply.uart_overruns = uart_overruns;
    tx_buf.desc_reply.frame_overruns = frame_overruns;
    tx_buf.desc_reply.invalid_frames = invalid_frames;
    send_frame_formatted(tx_buf.byte_data, sizeof(tx_buf.desc_reply));
}

/* This is the higher-level protocol handler for the serial protocol. It gets passed the number of data bytes in this
 * frame (which may be zero) and returns a pointer to the buffer where the next frame should be stored.
 */
volatile uint8_t *packet_received(int len) {
    static enum {
        PROT_ADDRESSED = 0,
        PROT_EXPECT_FRAME_SECOND_HALF = 1,
        PROT_IGNORE = 2,
    } protocol_state = PROT_IGNORE; 
    /* Use mac frames as delimiters to synchronize this protocol layer */
    trigger_comm_led();
    if (len == 0) { /* Discovery packet */
        if (sys_time < 100 && sys_time_seconds == 0) { /* Only respond during the first 100ms after boot */
            send_frame_formatted((uint8_t*)&device_mac, sizeof(device_mac));
        }

    } else if (len == 1) { /* Command packet */
        if (protocol_state == PROT_ADDRESSED) {
            switch (rx_buf.byte_data[0]) {
            case 0x01:
                GPIOA->BSRR = GPIO_BSRR_BS_4; // Debug
                //for (int i=0; i<100; i++)
                //    tick();
                send_status_reply();
                GPIOA->BSRR = GPIO_BSRR_BR_4; // Debug
                break;
            }
        } else {
            invalid_frames++;
            trigger_error_led();
        }
        protocol_state = PROT_IGNORE;

    } else if (len == 4) { /* Address packet */
        if (rx_buf.mac_data == device_mac) { /* we are addressed */
            protocol_state = PROT_ADDRESSED; /* start listening for frame buffer data */
        } else { /* we are not addressed */
            protocol_state = PROT_IGNORE; /* ignore packet */
        }

    } else if (len == sizeof(rx_buf.set_fb_rq)/2) {
        if (protocol_state == PROT_ADDRESSED) { /* First of two half-framebuffer data frames */
            protocol_state = PROT_EXPECT_FRAME_SECOND_HALF;
            /* Return second half of receive buffer */
            return rx_buf.byte_data + (sizeof(rx_buf.set_fb_rq)/2);

        } else if (protocol_state == PROT_EXPECT_FRAME_SECOND_HALF) { /* Second of two half-framebuffer data frames */
            /* Kick off buffer transfer. This triggers the main loop to copy data out of the receive buffer and paste it
             * properly formatted into the frame buffer. */
            if (fb_op == FB_WRITE) {
                fb_op = FB_FORMAT;
                trigger_id_led();
            } else {
                /* FIXME An overrun happend. What should we do? */
                frame_overruns++;
                trigger_error_led();
            }

            /* Go to "hang mode" until next zero-length packet. */
            protocol_state = PROT_IGNORE;
        }

    } else {
        /* FIXME An invalid packet has been received. What should we do? */
        invalid_frames++;
        trigger_error_led();
        protocol_state = PROT_IGNORE; /* go into "hang mode" until next zero-length packet */
    }

    /* By default, return rx_buf.byte_data . This means if an invalid protocol state is reached ("hang mode"), the next
     * frame is still written to rx_buf. This is not a problem since whatever garbage is written at that point will be
     * overwritten before the next buffer transfer. */
    return rx_buf.byte_data;
}

void USART1_IRQHandler(void) {
    /* Since a large amount of data will be shoved down this UART interface we need a more reliable and more efficient
     * way of framing than just waiting between transmissions.
     *
     * This code uses "Consistent Overhead Byte Stuffing" (COBS). For details, see its Wikipedia page[0] or the proper
     * scientific paper[1] published on it. Roughly, it works like this:
     *
     * * A frame is at most 254 bytes in length.
     * * The null byte 0x00 acts as a frame delimiter. There is no null bytes inside frames.
     * * Every frame starts with an "overhead" byte indicating the number of non-null payload bytes until the next null
     *   byte in the payload, **plus one**. This means this byte can never be zero.
     * * Every null byte in the payload is replaced by *its* distance to *its* next null byte as above.
     *
     * This means, at any point the receiver can efficiently be synchronized on the next frame boundary by simply
     * waiting for a null byte. After that, only a simple state machine is necessary to strip the overhead byte and a
     * counter to then count skip intervals.
     *
     * Here is Wikipedia's table of example values:
     *
     *    Unencoded data          Encoded with COBS
     *    00                      01 01 00
     *    00 00                   01 01 01 00
     *    11 22 00 33             03 11 22 02 33 00
     *    11 22 33 44             05 11 22 33 44 00
     *    11 00 00 00             02 11 01 01 01 00
     *    01 02 ...FE             FF 01 02 ...FE 00
     *
     * [0] https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
     * [1] Cheshire, Stuart; Baker, Mary (1999). "Consistent Overhead Byte Stuffing"
     *     IEEE/ACM Transactions on Networking. doi:10.1109/90.769765
     *     http://www.stuartcheshire.org/papers/COBSforToN.pdf
     */

    /* This pointer stores where we write data. The higher-level protocol logic decides on a frame-by-frame-basis where
     * the next frame's data will be stored. */
    static volatile uint8_t *writep = rx_buf.byte_data;
    /* Index inside the current frame payload */
    static int rxpos = 0;
    /* COBS state machine. This implementation might be a little too complicated, but it works well enough and I find it
     * reasonably easy to understand. */
    static enum {
        COBS_WAIT_SYNC = 0,  /* Synchronize with frame */
        COBS_WAIT_START = 1, /* Await overhead byte */
        COBS_RUNNING = 2     /* Process payload */
    } cobs_state = 0;
    /* COBS skip counter. During payload processing this contains the remaining non-null payload bytes */
    static int cobs_count = 0;

    if (USART1->ISR & USART_ISR_ORE) { /* Overrun handling */
        uart_overruns++;
        trigger_error_led();
        /* Reset and re-synchronize. Retry next frame. */
        rxpos = 0;
        cobs_state = COBS_WAIT_SYNC;
        /* Clear interrupt flag */
        USART1->ICR = USART_ICR_ORECF;

    } else { /* Data received */
        uint8_t data = USART1->RDR; /* This automatically acknowledges the IRQ */

        if (data == 0x00) { /* End-of-packet */
            /* Process higher protocol layers on this packet. */
            writep = packet_received(rxpos);

            /* Reset for next packet. */
            cobs_state = COBS_WAIT_START;
            rxpos = 0;

        } else { /* non-null byte */
            if (cobs_state == COBS_WAIT_SYNC) { /* Wait for null byte */
                /* ignore data */

            } else if (cobs_state == COBS_WAIT_START) { /* Overhead byte */
                cobs_count = data;
                cobs_state = COBS_RUNNING;

            } else { /* Payload byte */
                if (--cobs_count == 0) { /* Skip byte */
                    cobs_count = data;
                    data = 0;
                }

                /* Write processed payload byte to current receive buffer */
                writep[rxpos++] = data;
            }
        }
    }
}

