/* mbed Microcontroller Library
 * Copyright (c) 2006-2013 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 *  ------- BLE Peripheral/Server UART function --------------------------------
 *          communicate with BLE_UART_Client program
 *      --- Tested on Switch Science mbed TY51822r3 ---
 *
 *      http://www.page.sannet.ne.jp/kenjia/index.html
 *      https://os.mbed.com/users/kenjiArai/
 *
 *      Started:  March     7th, 2016
 *      Revised:  June     13th, 2016
 *      Revised:  Feburary 10th, 2018   Not set mac addr but use device name
 *      Revised:  Feburary 11th, 2018   use mbed-os5.7.4 with CircularBuffer
 *      Revised:  April    14th, 2018   modification only for TYBLE16
 *      Revised:  November 25th, 2018   Change os2 to os5
 *
 *  Original program:
 *      BLE_LoopbackUART
 *      https://developer.mbed.org/teams/Bluetooth-Low-Energy/
 *                                  code/BLE_LoopbackUART/
 *  Reference program:
 *      BLE_Peripheral_test by noboru koshinaka
 *      https://os.mbed.com/users/noboruk/code/BLE_Peripheral_test/
 *  Tested Client Device:
 *      BLE_Uart_Client
 *      https://os.mbed.com/users/kenjiArai/code/BLE_Uart_Client/
 */

//#define EXAMPLE_8_UART_SERVER
#ifdef EXAMPLE_8_UART_SERVER

//  Include --------------------------------------------------------------------
#include "mbed.h"
#include "BLE.h"
#include "UARTService.h"
#include "nRF51_Vdd.h"
#include "nRF51_WakeUp.h"
#include "CircularBuffer.h"

//  Definition -----------------------------------------------------------------
//#define     USE_MAC           // if you use mac address, please define it

#define     NUM_ONCE            20
#define     BFSIZE              (NUM_ONCE+4)

// Please refer nRF51_WakeUP library
#define     GOTO_SLEEP_MODE     0
#if GOTO_SLEEP_MODE
#warning "Make sure!! -> You need to connected P0_21(LED1) and P0_0"
#endif

//#define    USE_DEBUG_MODE
#ifdef USE_DEBUG_MODE
#define DEBUG(...) { printf(__VA_ARGS__); }
#else
#define DEBUG(...)
#endif

//  Object ---------------------------------------------------------------------
BLE&            ble_uart = BLE::Instance();
DigitalOut      connectedLED(LED1);
//InterruptIn     wake_up_sw(P0_1);
//nRF51_WakeUp    wakeup(P0_21, P0_0);
nRF51_Vdd       vdd(3.0f, 2.2f);
RawSerial       pc(USBTX, USBRX, 115200);
//Serial        pc(P0_3, P0_1, 115200);     // for another board
UARTService     *uartServicePtr;
Ticker          ticker;
CircularBuffer<char, 1536> ser_bf;
Thread          tsk;
Mutex           bletx_mutex;

//  ROM / Constant data --------------------------------------------------------
#warning "You need to confirm your device name."
const static char DEVICE_NAME[] = "UART_PJL";

//  RAM ------------------------------------------------------------------------
Gap::Address_t  my_mac;
uint8_t         tx_buf[BFSIZE];
uint8_t         tx_len                  = 0;
uint8_t         rx_buf[BFSIZE];
volatile bool   trigger_transmit        = false;
volatile bool   trigger_receive         = false;
volatile uint8_t command_continue       = 0;
uint16_t        time_out_cntr           = 3600;
volatile bool   time_out                = false;
//uint32_t        sleep_time              = 30;   // unit:second
volatile bool   rx_isr_busy             = false;

//  Function prototypes --------------------------------------------------------
//      BLE
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *);
void onDataWritten_action(const GattWriteCallbackParams *);
//      Tasks
void pc_ser_rx(void);
void data_from_ble(void);
void Update_Values(void);
//      Application related
void command(uint8_t *cmd);
void action_tx_help(void);
void action_tx_vdd(void);
void action_tx_temperature(void);
//void action_tx_wait_time(uint8_t *);
void action_tx_quit(void);
static int  xatoi (char **, int32_t *);
void adjust_line(uint8_t *);
//      Interrupt related
//void interrupt_by_sw(void);
void serialRxCallback(void);
void periodicCallback(void);

//------------------------------------------------------------------------------
//  Control Program
//------------------------------------------------------------------------------
int main(void)
{
    connectedLED = 0;
    pc.attach(&serialRxCallback, Serial::RxIrq);
    ticker.attach(periodicCallback, 1);
    tsk.start(pc_ser_rx);
    // clear terminal output
    for (int k = 0; k < 3; k++) {
        pc.printf("\r\n");
    }
    // opening message
    pc.printf("UART Communication / Server(Peripheral) side\r\n");
    pc.printf("  need Client module (run BLE_Uart_Client program)\r\n");
    // Interrupt by switch
//    wake_up_sw.fall(&interrupt_by_sw);
    ble_uart.init();
    Gap::AddressType_t my_mac_type;
    ble_uart.gap().getAddress(&my_mac_type, my_mac);
    DEBUG(
        "  my_MAC %02x:%02x:%02x:%02x:%02x:%02x (%s)\r\n",
        my_mac[5], my_mac[4], my_mac[3], my_mac[2], my_mac[1], my_mac[0],
        (my_mac_type == Gap::ADDR_TYPE_PUBLIC) ? "public" : "random"
    );
    pc.printf(
        "  My device name : %s\r\n", DEVICE_NAME);
    pc.printf(
        "  My mac data %02x:%02x:%02x:%02x:%02x:%02x\r\n",
        my_mac[5], my_mac[4], my_mac[3], my_mac[2], my_mac[1], my_mac[0]
    );
#ifdef USE_MAC
    pc.printf(
        "  mac_board_x   = {0x%02x,0x%02x,0x%02x,0x%02x,0x%02x,0x%02x};\r\n",
        my_mac[0], my_mac[1], my_mac[2], my_mac[3], my_mac[4], my_mac[5]
    );
    pc.printf(
        "  Please write above data(mac_board_x line (x=0,1,2,...))\r\n");
    pc.printf(
        "  into Client/main.cpp [ROM / Constant data] area\r\n");
#endif
    ble_uart.onDisconnection(disconnectionCallback);
    ble_uart.onDataWritten(onDataWritten_action);
    /* setup advertising */
    ble_uart.accumulateAdvertisingPayload(
        GapAdvertisingData::BREDR_NOT_SUPPORTED);
    ble_uart.setAdvertisingType(
        GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble_uart.accumulateAdvertisingPayload(
        GapAdvertisingData::COMPLETE_LOCAL_NAME,
        (const uint8_t *)DEVICE_NAME,
        sizeof(DEVICE_NAME)
    );
    ble_uart.accumulateAdvertisingPayload(
        GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
        (const uint8_t *)UARTServiceUUID_reversed,
        sizeof(UARTServiceUUID_reversed)
    );
    // Advertize Interval
    ble_uart.setAdvertisingInterval(1000); /* 1000ms;in multiples of 0.625ms.*/
    // Start
    ble_uart.startAdvertising();
    UARTService uartService(ble_uart);
    uartServicePtr = &uartService;
    while(true) {
        if (time_out) {
#if GOTO_SLEEP_MODE
            wakeup.set_and_wait(sleep_time);
            while(true) {   // never come here but just in case
                deepsleep();
            }
#endif
        }
        if (trigger_transmit) {
            static uint8_t cmd_buf[BFSIZE];
            static volatile bool   flag_continue = 0;
            trigger_transmit = false;
            pc.printf((const char*)rx_buf);
            if (flag_continue == true) {
                strcat((char *)cmd_buf, (char *)rx_buf);
                if (strchr((const char*)cmd_buf,(int)'\r') == 0) {
                    flag_continue = true;
                } else {
                    command(cmd_buf);
                    for(uint8_t i = 0; i < BFSIZE; i++) {
                        cmd_buf[i] = 0;
                    }
                    flag_continue = false;
                }
            }
            if ((rx_buf[0] == '~')) {
                strcpy((char *)cmd_buf, (char *)rx_buf);
                if (strchr((const char*)cmd_buf,(int)'\r') == 0) {
                    flag_continue = true;
                } else {
                    command(cmd_buf);
                    for(uint8_t i = 0; i < BFSIZE; i++) {
                        cmd_buf[i] = 0;
                    }
                    flag_continue = false;
                }
            }
        }
        ble_uart.waitForEvent();
    }
}

void command(uint8_t *cmd)
{
    uint8_t *p = cmd;

    while(*p == ' ') {
        ++p;   // skip space
    }
    if (*p++ == '~') {
        while(*p < '!') {
            ++p;   // skip space
        }
        uint8_t c = *p;
        //pc.printf("c=%c\r\n", c);
        switch (c) {
            case 'v':
                action_tx_vdd();
                break;
            case 't':
                action_tx_temperature();
                break;
            case 'q':
                action_tx_quit();
                break;
#if 0
            case 'w':
                action_tx_wait_time(cmd);
                break;
#endif
            case 'h':
            case '?':
                action_tx_help();
                break;
            default:
                //pc.printf("\r\nStep(%u)\r\n", __LINE__);
                break;
        }
    }
}

void periodicCallback(void)
{
#if GOTO_SLEEP_MODE
    if (--time_out_cntr == 0) {
        time_out = true;
    }
#endif
    if (rx_isr_busy == true) {
        rx_isr_busy = false;
    } else {
        tsk.signal_set(0x01);
    }
}

void serialRxCallback()
{
    ser_bf.push(pc.getc());
    rx_isr_busy = true;
    tsk.signal_set(0x01);
}

void pc_ser_rx()
{
    static uint8_t linebf_irq[BFSIZE];
    static volatile uint8_t linebf_irq_len = 0;

    while(true) {
        Thread::signal_wait(0x01);
        if (ser_bf.empty()) {
            if (linebf_irq_len != 0) {
                linebf_irq[linebf_irq_len] = 0;
                adjust_line(linebf_irq);
                linebf_irq_len = 0;
                bletx_mutex.lock();
                ble_uart.updateCharacteristicValue(
                    uartServicePtr->getRXCharacteristicHandle(),
                    linebf_irq,
                    NUM_ONCE
                );
                bletx_mutex.unlock();
            }
        }
        while(!ser_bf.empty()) {
            char c;
            ser_bf.pop(c);
            if (c == '\b') {
                linebf_irq_len--;
                pc.putc(c);
                pc.putc(' ');
                pc.putc(c);
            } else if ((c >= ' ') || (c == '\r') || (c == '\n')) {
                bool overflow = false;
                if ((c == '\r') || (c == '\n')) {
                    if (linebf_irq_len == NUM_ONCE - 1) { // remain only 1 buffer
                        overflow = true;
                        linebf_irq[linebf_irq_len++] = '\r';
                        pc.putc('\r');
                    } else {
                        overflow = false;
                        linebf_irq[linebf_irq_len++] = '\r';
                        linebf_irq[linebf_irq_len++] = '\n';
                        pc.printf("\r\n");
                    }
                } else {
                    linebf_irq[linebf_irq_len++] = c;
                    pc.putc(c);
                }
                if (linebf_irq_len >= NUM_ONCE ) {
                    linebf_irq[linebf_irq_len] = 0;
                    adjust_line(linebf_irq);
                    linebf_irq_len = 0;
                    bletx_mutex.lock();
                    ble_uart.updateCharacteristicValue(
                        uartServicePtr->getRXCharacteristicHandle(),
                        linebf_irq,
                        NUM_ONCE
                    );
                    bletx_mutex.unlock();
                    if (overflow == true) {
                        overflow = false;
                        linebf_irq[linebf_irq_len++] = '\n';
                        pc.putc('\n');
                    }
                }
            }
        }
    }
}

void adjust_line(uint8_t *bf)
{
    uint8_t i, c;

    for (i = 0; i <NUM_ONCE; bf++, i++) {
        c = *bf;
        if (c == 0) {
            break;
        }
    }
    for (; i < NUM_ONCE; bf++, i++) {
        *bf = 0x11;
    }
    *(bf + 1) = 0;
}

void onDataWritten_action(const GattWriteCallbackParams *params)
{
    if ((uartServicePtr != NULL) &&
            (params->handle == uartServicePtr->getTXCharacteristicHandle())) {
        strcpy((char *)rx_buf, (const char *)params->data);
        trigger_transmit = true;
    }
}

void action_tx_help()
{
    //          12345678901234567890
    sprintf((char *)tx_buf,"  ~?:help\r\n");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(200);
    //          12345678901234567890
    sprintf((char *)tx_buf,"  ~v:vdd\r\n");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(200);
    //          12345678901234567890
    sprintf((char *)tx_buf,"  ~t:temperature\r\n");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(200);
#if 0
    //          12345678901234567890
    sprintf((char *)tx_buf,"  ~w:wait, w 120\r\n");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(200);
#endif
    //          12345678901234567890
    sprintf((char *)tx_buf,"  ~q:quit/sleep\r\n");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(200);
}

void action_tx_vdd()
{
    sprintf((char *)tx_buf,"Vdd: %3.2f V\r\n", vdd.read_real_value());
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
}

void action_tx_temperature()
{
    int32_t p_temp;
    float temperature;

    // Update a temperature (inside nRF51822 chip)
    sd_temp_get(&p_temp);
    // -16.0f is offset vale for chip die temp
    //         to ambient temp (depend on your board)
    temperature = float(p_temp) / 4; // Original = float(p_temp)/4.0f - 16.0f;
    sprintf((char *)tx_buf,"T: %+4.1f dC\r\n", temperature);
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
}

#if 0
void action_tx_wait_time(uint8_t *cmd)
{
    int32_t dt;
    char *p;

    p = (char *)(cmd);
    p += 2; // point to time value
    if (xatoi(&p, &dt)) {
        if (dt <= 5) {
            dt = 5;
        }
        sleep_time = dt;    // set next wake-up period
    } else {
        DEBUG("data is unknown!\r\n");
        sleep_time = 30;
    }
    DEBUG("slp_t:%d\r\n", sleep_time);
    //pc.printf("slp_t:%d\r\n", sleep_time);
    //          12345678901234567890
    sprintf((char *)tx_buf, "W: %d sec\r\n", sleep_time);
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
}
#endif

void action_tx_quit()
{
#if GOTO_SLEEP_MODE
    ticker.detach();
    //          12345678901234567890
    sprintf((char *)tx_buf,"Terminated the BLE");
    tx_len = strlen((const char *)tx_buf);
    Update_Values();
    Thread::wait(1000);
    wakeup.set_and_wait(sleep_time);
    while(true) {   // never come here but just in case
        deepsleep();
    }
#else
    SCB->AIRCR = 0x05fa0004;    // System RESET!!
#endif
}

//  Change string -> integer
static int xatoi (char **str, int32_t *res)
{
    unsigned long val;
    unsigned char c, radix, s = 0;

    for (;;) {
        c = **str;
        if (c == 0) {
            return 0;
        }
        if (c == '-') {
            break;
        }
        if (c == '+') {
            (*str)++;
            c = **str;
        }
        if (c>='0'&& c<='9') {
            break;
        } else {
            (*str)++;
            c = **str;
        }
    }
    if (c == '-') {
        s = 1;
        c = *(++(*str));
    }
    if (c == '0') {
        c = *(++(*str));
        if (c <= ' ') {
            *res = 0;
            return 1;
        }
        if (c == 'x') {
            radix = 16;
            c = *(++(*str));
        } else {
            if (c == 'b') {
                radix = 2;
                c = *(++(*str));
            } else {
                if ((c >= '0')&&(c <= '9')) {
                    radix = 8;
                }   else {
                    return 0;
                }
            }
        }
    } else {
        if ((c < '1')||(c > '9')) {
            return 0;
        }
        radix = 10;
    }
    val = 0;
    while (c > ' ') {
        if (c >= 'a') c -= 0x20;
        c -= '0';
        if (c >= 17) {
            c -= 7;
            if (c <= 9) return 0;
        }
        if (c >= radix) return 0;
        val = val * radix + c;
        c = *(++(*str));
    }
    if (s) val = -val;
    *res = val;
    return 1;
}

#if 0
void interrupt_by_sw() // Go to sleep
{
    NVIC_SystemReset();
    // Not come here (Just in case)
    sleep();
}
#endif

void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    DEBUG("Disconnected!\r\n");
    DEBUG("Restarting the advertising process\r\n");
    ble_uart.startAdvertising();
}

void Update_Values(void)
{
    bletx_mutex.lock();
    ble_uart.updateCharacteristicValue(
        uartServicePtr->getRXCharacteristicHandle(),
        tx_buf,
        tx_len
    );
    bletx_mutex.unlock();
    tx_len = 0;
}

#endif
