#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/usart.h>
#include <libopencmsis/core_cm3.h>
#include <string.h>

#include "crypto_aead.h"
#include "api.h"
#include "uartp.h"

void uart_wbyte(uint8_t x);
uint8_t uart_rbyte(void);
static void loop(void);
static void my_assert(bool b);

#define MAX_BYTES 172
#define CMDBUF_LEN 200
static uint8_t cmdbuf[CMDBUF_LEN];

uint8_t npub[CRYPTO_NPUBBYTES];
uint8_t nsec[CRYPTO_NSECBYTES];
uint8_t k[CRYPTO_KEYBYTES];
uint8_t ad[MAX_BYTES];
uint8_t m[MAX_BYTES];
uint8_t c[MAX_BYTES];
unsigned long long int adlen = 0;
unsigned long long int mlen = 0;
unsigned long long int clen = 0;

void uart_wbyte(uint8_t x) {
    usart_send_blocking(USART1, x);
}

uint8_t uart_rbyte() {
    uint16_t r = usart_recv_blocking(USART1);
    return (uint8_t) (0xff & r);
}

static void my_assert(bool b) {
    if (b)
        return;
    for(;;);
}


static void clock_setup(void)
{
    rcc_clock_setup_in_hse_8mhz_out_72mhz();

    /* Enable GPIOA clock. */
    rcc_periph_clock_enable(RCC_GPIOA);

    /* Enable clocks for GPIO port A (for GPIO_USART1_TX) and USART1. */
    rcc_periph_clock_enable(RCC_USART1);
}

static void usart_setup(void)
{
    /* Setup GPIO pin GPIO_USART1_TX. */
    gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ,
              GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO_USART1_TX);

    /* Setup UART parameters. */
    usart_set_baudrate(USART1, 115200);
    usart_set_databits(USART1, 8);
    usart_set_stopbits(USART1, USART_STOPBITS_1);
    usart_set_mode(USART1, USART_MODE_TX_RX);
    usart_set_parity(USART1, USART_PARITY_NONE);
    usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);

    /* Finally enable the USART. */
    usart_enable(USART1);
}

static void gpio_setup(void)
{
    /* Set GPIO (in GPIO port A) to 'output push-pull'. */
    gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ,
              GPIO_CNF_OUTPUT_PUSHPULL, GPIO7);
}

int main(void)
{
    int i;

    clock_setup();
    gpio_setup();
    usart_setup();

    memset(npub, 0, CRYPTO_NPUBBYTES);
    memset(nsec, 0, CRYPTO_NSECBYTES);
    memset(k, 0, CRYPTO_KEYBYTES);
    memset(ad, 0, MAX_BYTES);
    memset(m, 0, MAX_BYTES);
    memset(c, 0, MAX_BYTES);

    for (i = 0; i < 1000000; i++)    /* Wait a bit. */
        __asm__("nop");

    for (i=0; i<13; i++) {
        usart_send_blocking(USART1, "Hello, World!"[i]);
    }

    while (1) {
        loop();
    }

    return 0;
}

static void loop() {
    int res;
    uint16_t len = uartp_recv(cmdbuf, CMDBUF_LEN - 1);
    uint8_t action = cmdbuf[0];
    if (len == 0 || len > CMDBUF_LEN - 1)
        return;

    uint16_t l = len - 1;
    uint16_t rl = 0;
    uint8_t *var = cmdbuf+1;
    switch (action) {
        case 'm': my_assert(l <= MAX_BYTES);        memcpy(m,    var, l); mlen = l; break;
        case 'c': my_assert(l <= MAX_BYTES);        memcpy(c,    var, l); clen = l; break;
        case 'a': my_assert(l <= MAX_BYTES);        memcpy(ad,   var, l); adlen = l; break;
        case 'k': my_assert(l == CRYPTO_KEYBYTES);  memcpy(k,    var, l); break;
        case 'p': my_assert(l == CRYPTO_NPUBBYTES); memcpy(npub, var, l); break;
        case 's': my_assert(l == CRYPTO_NSECBYTES); memcpy(nsec, var, l); break;
        case 'e':
            __disable_irq();
            __asm__("nop");
            gpio_clear(GPIOA, GPIO7);
            res = crypto_aead_encrypt(c, &clen, m, mlen, ad, adlen, nsec, npub, k);
            gpio_set(GPIOA, GPIO7);
            __asm__("nop");
            __enable_irq();
            break;
        case 'd':
            __disable_irq();
            __asm__("nop");
            gpio_clear(GPIOA, GPIO7);
            res = crypto_aead_decrypt(m, &mlen, nsec, c, clen, ad, adlen, npub, k);
            gpio_set(GPIOA, GPIO7);
            __asm__("nop");
            __enable_irq();
            break;
        case'M': var = m;    rl = mlen; break;
        case'C': var = c;    rl = clen; break;
        case'A': var = ad;   rl = adlen; break;
        case'K': var = k;    rl = CRYPTO_KEYBYTES; break;
        case'P': var = npub; rl = CRYPTO_NPUBBYTES; break;
        case'S': var = nsec; rl = CRYPTO_NSECBYTES; break;
        case'R': var = (uint8_t *) &res; rl = sizeof(res); break;
        default:
            my_assert(false);
    }
    cmdbuf[0] = action;
    memcpy(cmdbuf+1, var, rl);
    uartp_send(cmdbuf, rl+1);
}
