/**
 * Arduino + Energia Wrapper for BTstack
 */

#if !defined(ARDUINO)
#error "Not compiling for Arduino/Energia"
#endif

#include <Arduino.h>
#ifdef ENERGIA
#include <Energia.h>
#endif
#include <SPI.h>

#include "hal_uart_dma.h"

#define HAVE_SHUTDOWN

#ifdef ENERGIA

// CMM 9301 Configuration for TI Launchpad
#define PIN_SPI_SCK      7
#define PIN_CS       8
#define PIN_SHUTDOWN 11
#define PIN_IRQ_DATA 13
#define PIN_SPI_MISO     14
#define PIN_SPI_MOSI     15
#else // ARDUINO

// CMM 9301 Configuration for Arduino
#define PIN_IRQ_DATA 2
#define PIN_CS 4
#define PIN_SHUTDOWN 5

// -- SPI defines for Arduino Mega
#ifndef PIN_SPI_MISO
#define PIN_SPI_MISO 50
#endif
#ifndef PIN_SPI_MOSI
#define PIN_SPI_MOSI 51
#endif
#ifndef PIN_SPI_SCK
#define PIN_SPI_SCK  52
#endif

#endif

// rx state
static uint16_t  bytes_to_read = 0;
static uint8_t * rx_buffer_ptr = 0;

// tx state
static uint16_t  bytes_to_write = 0;
static uint8_t * tx_buffer_ptr = 0;

// handlers
static void dummy_handler(void){};
static void (*rx_done_handler)(void) = dummy_handler;
static void (*tx_done_handler)(void) = dummy_handler;

static void bt_setup(void){
    pinMode(PIN_CS, OUTPUT);
    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_SPI_SCK, OUTPUT);
    pinMode(PIN_SHUTDOWN, OUTPUT);
    pinMode(PIN_IRQ_DATA, INPUT);

    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_SHUTDOWN, HIGH);

    // SPI settings are reset in SPI.begin() - calls hang on Arduino Zero, too.
    // SPI.setBitOrder(MSBFIRST);
    // SPI.setDataMode(SPI_MODE0);
    // SPI.end();
}

#ifdef HAVE_SHUTDOWN
static void bt_power_cycle(void){

    // power cycle. set CPU outputs to input to not power EM9301 via IOs
    // pinMode(PIN_SPI_MOSI, INPUT);
    // pinMode(PIN_CS, INPUT);
    pinMode(PIN_CS, OUTPUT);
    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_SPI_SCK, OUTPUT);
    pinMode(PIN_SHUTDOWN, OUTPUT);
    digitalWrite(PIN_CS, LOW);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_SPI_SCK, LOW);
    digitalWrite(PIN_SHUTDOWN, HIGH);
    delay(500);

    pinMode(PIN_SPI_MOSI, OUTPUT);
    pinMode(PIN_CS, OUTPUT);
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SHUTDOWN, LOW);
    delay(1000);
}
#endif

#ifndef HAVE_SHUTDOWN
static void bt_send_illegal(void){
    digitalWrite(PIN_SPI_MOSI, HIGH);
    digitalWrite(PIN_CS, LOW);
    printf("Illegal start\n");
    SPI.begin(); 
    int i;
    for (i=0;i<255;i++){
        SPI.transfer(0xff);
        printf(".");
    }    
    SPI.end(); 
    printf("\nIllegal stop\n");
    digitalWrite(PIN_CS, HIGH);
}

static void bt_flush_input(void){
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, LOW);
    SPI.begin(); 
    while (digitalRead(PIN_IRQ_DATA) == HIGH){
        SPI.transfer(0x00);
    }
    SPI.end(); 
    digitalWrite(PIN_CS, HIGH);
}

static void bt_send_reset(void){
      digitalWrite(PIN_SPI_MOSI, HIGH);
      digitalWrite(PIN_CS, LOW);
      SPI.begin(); 
      SPI.transfer(0x01);
      SPI.transfer(0x03);
      SPI.transfer(0x0c);
      SPI.transfer(0x00);
      SPI.end(); 
      digitalWrite(PIN_CS, HIGH);
}
#endif


static void bt_try_send(void){
    
    if (!bytes_to_write) return;

    // activate module
    pinMode(PIN_SPI_MOSI, OUTPUT);
    digitalWrite(PIN_SPI_MOSI, HIGH);
    digitalWrite(PIN_CS, LOW);

    // module ready
    int tx_done = 0;
    if (digitalRead(PIN_SPI_MISO) == HIGH){
        // printf("Sending: ");

        SPI.begin(); 
        while (bytes_to_write){
            // printf("%02x ", *tx_buffer_ptr);
            SPI.transfer(*tx_buffer_ptr);
            tx_buffer_ptr++;
            bytes_to_write--;
        }
        SPI.end();
        // printf(".\n");
        tx_done = 1;
    } 

    // deactivate module
    digitalWrite(PIN_CS, HIGH);
    digitalWrite(PIN_SPI_MOSI, LOW);
    pinMode(PIN_SPI_MOSI, OUTPUT);

    // notify upper layer
    if (tx_done) {
        (*tx_done_handler)();
    }
}

static int bt_try_read(void){

    // check if data available and buffer is ready
    if (digitalRead(PIN_IRQ_DATA) == LOW) return 0;
    if (bytes_to_read == 0) return 0;

    int num_bytes_read = 0;

    // printf("Reading (%u): ", bytes_to_read);

    // activate module
    digitalWrite(PIN_SPI_MOSI, LOW);
    digitalWrite(PIN_CS, LOW);
    SPI.begin(); 
    do {
        uint8_t byte_read = SPI.transfer(0x00);
        // printf("%02x ", byte_read); 
        *rx_buffer_ptr = byte_read;
        rx_buffer_ptr++;
        bytes_to_read--;
        num_bytes_read++;
    } while (bytes_to_read > 0);
    SPI.end(); 
    digitalWrite(PIN_CS, HIGH);

    // printf("\n"); 

    // notify upper layer
    (*rx_done_handler)();

    return num_bytes_read;
}

extern "C" void hal_uart_dma_init(void){
    bt_setup();

#ifdef HAVE_SHUTDOWN
    bt_power_cycle(); 
#else
    // bring EM9301 into defined state
    bt_send_illegal();
    bt_send_reset();
    bt_flush_input();
#endif
}

extern "C" void hal_uart_dma_set_block_received( void (*block_handler)(void)){
    rx_done_handler = block_handler;
}
extern "C" void hal_uart_dma_set_block_sent( void (*block_handler)(void)){
    tx_done_handler = block_handler;
}

extern "C" void hal_uart_dma_set_csr_irq_handler( void (*csr_irq_handler)(void)){
    // only used for eHCILL
}

extern "C" int  hal_uart_dma_set_baud(uint32_t baud){
	return 0;
}

extern "C" void hal_uart_dma_send_block(const uint8_t *buffer, uint16_t length){
    // printf("send_block, bytes %u\n", length);
    tx_buffer_ptr = (uint8_t *) buffer;
    bytes_to_write = length;
}

extern "C" void hal_uart_dma_receive_block(uint8_t *buffer, uint16_t length){
    rx_buffer_ptr = buffer;
    bytes_to_read = length;
}

extern "C" void hal_uart_dma_set_sleep(uint8_t sleep){
    // not needed for SPI (doesn't need internal clock to work)
}

extern "C" void hal_uart_dma_process(void){
    int num_bytes_read = bt_try_read();
    if (num_bytes_read == 0){
        bt_try_send();
    }
}

extern "C" uint32_t hal_time_ms(void){
    return millis();
}