/* Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved.
 *
 * The information contained herein is property of Nordic Semiconductor ASA.
 * Terms and conditions of usage are described in detail in NORDIC
 * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
 *
 * Licensees are granted free, non-transferable use of the information. NO
 * WARRANTY of ANY KIND is provided. This heading must NOT be removed from
 * the file.
 *
 */

/* clang-format off */

/** @file
 *
 * @defgroup iot_sdk_tcp_server main.c
 * @{
 * @ingroup iot_sdk_app_lwip
 * @brief This file contains the source code for LwIP TCP Server sample application.
 *
 */

#include <stdbool.h>
#include <stdint.h>
#include "boards.h"
#include "app_timer_appsh.h"
#include "app_scheduler.h"
#include "app_button.h"
#include "nordic_common.h"
#include "softdevice_handler_appsh.h"
#include "ble_advdata.h"
#include "ble_srv_common.h"
#include "ble_ipsp.h"
#include "ble_6lowpan.h"
#include "mem_manager.h"
#include "app_trace.h"

/*
 * arm-none-eabi-gcc has BYTE_ORDER already defined, so in order to avoid
 * warnings in lwip, we have to undefine it
 *
 * TODO: Check if in the future versions of nRF5 SDK that changes.
 *       Current version of nRF51 SDK: 0.8.0
 *                          nRF5 SDK:  0.9.0
 */
#undef BYTE_ORDER
#include "lwip/init.h"
#include "lwip/inet6.h"
#include "lwip/ip6.h"
#include "lwip/ip6_addr.h"
#include "lwip/netif.h"
/*lint -save -e607 */
#include "lwip/tcp.h"
/*lint -restore */
#include "lwip/timers.h"
#include "nrf_platform_port.h"
#include "app_util_platform.h"

#define DEVICE_NAME                         "LwIPTCPServer"                                         /**< Device name used in BLE undirected advertisement. */

#define APP_TIMER_PRESCALER                 NRF51_DRIVER_TIMER_PRESCALER                            /**< Value of the RTC1 PRESCALER register. */
#define APP_TIMER_MAX_TIMERS                1                                                       /**< Maximum number of simultaneously created timers. */
#define APP_TIMER_OP_QUEUE_SIZE             1
#define LWIP_SYS_TIMER_INTERVAL             APP_TIMER_TICKS(250, APP_TIMER_PRESCALER)               /**< Interval for timer used as trigger to send. */

#define SCHED_MAX_EVENT_DATA_SIZE           128                                                     /**< Maximum size of scheduler events. */
#define SCHED_QUEUE_SIZE                    12                                                      /**< Maximum number of events in the scheduler queue. */

#define ADVERTISING_LED                     BSP_LED_0_MASK                                          /**< Is on when device is advertising. */
#define CONNECTED_LED                       BSP_LED_1_MASK                                          /**< Is on when device is connected. */
#define TCP_CONNECTED_LED                   BSP_LED_2_MASK                                          /**< Is on when device is connected. */
#define DISPLAY_LED_0                       BSP_LED_0_MASK                                          /**< LED used for displaying mod 4 of data payload received on UDP port. */
#define DISPLAY_LED_1                       BSP_LED_1_MASK                                          /**< LED used for displaying mod 4 of data payload received on UDP port. */
#define DISPLAY_LED_2                       BSP_LED_2_MASK                                          /**< LED used for displaying mod 4 of data payload received on UDP port. */
#define DISPLAY_LED_3                       BSP_LED_3_MASK                                          /**< LED used for displaying mod 4 of data payload received on UDP port. */
#define ALL_APP_LED                        (BSP_LED_0_MASK | BSP_LED_1_MASK | \
    BSP_LED_2_MASK | BSP_LED_3_MASK)                        /**< Define used for simultaneous operation of all application LEDs. */

#define APP_ADV_TIMEOUT                     0                                                       /**< Time for which the device must be advertising in non-connectable mode (in seconds). 0 disables timeout. */
#define APP_ADV_ADV_INTERVAL                MSEC_TO_UNITS(100, UNIT_0_625_MS)                       /**< The advertising interval. This value can vary between 100ms to 10.24s). */

#define DEAD_BEEF                           0xDEADBEEF                                              /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */

#define APPL_LOG                            app_trace_log                                           /**< Macro for logging application messages on UART, in case ENABLE_DEBUG_LOG_SUPPORT is not defined, no logging occurs. */
#define APPL_DUMP                           app_trace_dump                                          /**< Macro for dumping application data on UART, in case ENABLE_DEBUG_LOG_SUPPORT is not defined, no logging occurs. */

#define TCP_SERVER_PORT                     9000                                                    /**< TCP server listen port number. */
#define TCP_DATA_SIZE                       8                                                       /**< UDP Data size sent on remote. */

typedef enum
{
  TCP_STATE_IDLE,
  TCP_STATE_REQUEST_CONNECTION,
  TCP_STATE_CONNECTED,
  TCP_STATE_DATA_TX_IN_PROGRESS,
  TCP_STATE_DISCONNECTED
}tcp_state_t;

eui64_t                                     eui64_local_iid;                                        /**< Local EUI64 value that is used as the IID for*/
static ble_gap_adv_params_t                 m_adv_params;                                           /**< Parameters to be passed to the stack when starting advertising. */
static app_timer_id_t                       m_sys_timer_id;                                         /**< System Timer used to service LwIP timers periodically. */
static struct tcp_pcb                     * mp_tcp_port;                                            /**< TCP Port to listen on. */
static tcp_state_t                          m_tcp_state;                                            /**< TCP State information. */




/**@brief Function for error handling, which is called when an error has occurred.
 *
 * @warning This handler is an example only and does not fit a final product. You need to analyse
 *          how your product is supposed to react in case of error.
 *
 * @param[in] error_code  Error code supplied to the handler.
 * @param[in] line_num    Line number where the handler is called.
 * @param[in] p_file_name Pointer to the file name.
 */
void app_error_handler(uint32_t error_code, uint32_t line_num, const uint8_t * p_file_name)
{
  //Halt the application and notify of error using the LEDs.
  APPL_LOG("[** ASSERT **]: Error 0x%08lX, Line %ld, File %s\r\n",error_code, line_num, p_file_name);
  LEDS_ON(ALL_APP_LED);
  for(;;)
  {
  }

  // @note: In case on assert, it is desired to only recover and reset, uncomment the line below.
  //NVIC_SystemReset();
}


/**@brief Callback function for asserts in the SoftDevice.
 *
 * @details This function will be called in case of an assert in the SoftDevice.
 *
 * @warning This handler is an example only and does not fit a final product. You need to analyse
 *          how your product is supposed to react in case of Assert.
 * @warning On assert from the SoftDevice, the system can only recover on reset.
 *
 * @param[in]   line_num   Line number of the failing ASSERT call.
 * @param[in]   file_name  File name of the failing ASSERT call.
 */
void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
{
  app_error_handler(DEAD_BEEF, line_num, p_file_name);
}


/**@brief Function for the LEDs initialization.
 *
 * @details Initializes all LEDs used by this application.
 */
static void leds_init(void)
{
  // Configure application LED pins.
  LEDS_CONFIGURE(ALL_APP_LED);

  // Turn off all LED on initialization.
  LEDS_OFF(ALL_APP_LED);
}


/**@brief Function for initializing the Advertising functionality.
 *
 * @details Encodes the required advertising data and passes it to the stack.
 *          Also builds a structure to be passed to the stack when starting advertising.
 */
static void advertising_init(void)
{
  uint32_t                err_code;
  ble_advdata_t           advdata;
  uint8_t                 flags = BLE_GAP_ADV_FLAG_BR_EDR_NOT_SUPPORTED;
  ble_gap_conn_sec_mode_t sec_mode;
  ble_gap_addr_t          my_addr;

  BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);

  err_code = sd_ble_gap_device_name_set(&sec_mode,
      (const uint8_t *)DEVICE_NAME,
      strlen(DEVICE_NAME));
  APP_ERROR_CHECK(err_code);

  err_code = sd_ble_gap_address_get(&my_addr);
  APP_ERROR_CHECK(err_code);

  my_addr.addr[5]   = 0x00;
  my_addr.addr_type = BLE_GAP_ADDR_TYPE_PUBLIC;

  err_code = sd_ble_gap_address_set(&my_addr);
  APP_ERROR_CHECK(err_code);

  IPV6_EUI64_CREATE_FROM_EUI48(eui64_local_iid.identifier,
      my_addr.addr,
      my_addr.addr_type);

  ble_uuid_t adv_uuids[] =
  {
    {BLE_UUID_IPSP_SERVICE,         BLE_UUID_TYPE_BLE}
  };

  //Build and set advertising data.
  memset(&advdata, 0, sizeof(advdata));

  advdata.name_type               = BLE_ADVDATA_FULL_NAME;
  advdata.flags                   = flags;
  advdata.uuids_complete.uuid_cnt = sizeof(adv_uuids) / sizeof(adv_uuids[0]);
  advdata.uuids_complete.p_uuids  = adv_uuids;

  err_code = ble_advdata_set(&advdata, NULL);
  APP_ERROR_CHECK(err_code);

  //Initialize advertising parameters (used when starting advertising).
  memset(&m_adv_params, 0, sizeof(m_adv_params));

  m_adv_params.type        = BLE_GAP_ADV_TYPE_ADV_IND;
  m_adv_params.p_peer_addr = NULL;                             // Undirected advertisement.
  m_adv_params.fp          = BLE_GAP_ADV_FP_ANY;
  m_adv_params.interval    = APP_ADV_ADV_INTERVAL;
  m_adv_params.timeout     = APP_ADV_TIMEOUT;
}


/**@brief Function for starting advertising.
*/
static void advertising_start(void)
{
  uint32_t err_code;

  err_code = sd_ble_gap_adv_start(&m_adv_params);
  APP_ERROR_CHECK(err_code);

  LEDS_ON(ADVERTISING_LED);
}


/**@brief Function for handling the Application's BLE Stack events.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 */
static void on_ble_evt(ble_evt_t * p_ble_evt)
{
  switch (p_ble_evt->header.evt_id)
  {
    case BLE_GAP_EVT_CONNECTED:
      APPL_LOG ("[APPL]: Connected.\r\n");
      break;
    case BLE_GAP_EVT_DISCONNECTED:
      APPL_LOG ("[APPL]: Disconnected.\r\n");
      advertising_start();
      break;
    default:
      break;
  }
}


/**@brief Function for dispatching a BLE stack event to all modules with a BLE stack event handler.
 *
 * @details This function is called from the BLE Stack event interrupt handler after a BLE stack
 *          event has been received.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 */
static void ble_evt_dispatch(ble_evt_t * p_ble_evt)
{
  ble_ipsp_evt_handler(p_ble_evt);
  on_ble_evt(p_ble_evt);
}


/**@brief Function for initializing the BLE stack.
 *
 * @details Initializes the SoftDevice and the BLE event interrupt.
 */
static void ble_stack_init(void)
{
  uint32_t err_code;

  // Initialize the SoftDevice handler module.
  SOFTDEVICE_HANDLER_APPSH_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, true);

  // Register with the SoftDevice handler module for BLE events.
  err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);
  APP_ERROR_CHECK(err_code);
}


/**@brief Function for the Event Scheduler initialization.
*/
static void scheduler_init(void)
{
  APP_SCHED_INIT(SCHED_MAX_EVENT_DATA_SIZE, SCHED_QUEUE_SIZE);
}


/**@brief Function to TCP port.
 *
 * @details Function to close the TCP port and reset any information on the port.
 */
static void tcp_port_close(struct tcp_pcb * p_pcb)
{
  m_tcp_state = TCP_STATE_REQUEST_CONNECTION;

  //Reset all information set on and/or callback registered for the  port.
  tcp_arg(p_pcb, NULL);
  tcp_sent(p_pcb, NULL);
  tcp_recv(p_pcb, NULL);
  tcp_err(p_pcb, NULL);
  tcp_poll(p_pcb, NULL, 0);

  UNUSED_VARIABLE(tcp_close(p_pcb));

  LEDS_OFF((DISPLAY_LED_0 | DISPLAY_LED_1 | DISPLAY_LED_2 | DISPLAY_LED_3));
  LEDS_ON(CONNECTED_LED);
}


/**@brief TCP Port Write complete callback.
 *
 * @details Calbback registered to be notified of the write complete event on the TCP port.
 *          In case write complete is notified with 'zero' length, port is closed.
 *
 * @param[in]   p_arg     Receive argument set on the port.
 * @param[in]   p_pcb     PCB identifier of the port.
 * @param[in]   len       Length of data written successfully.
 *
 * @retval ERR_OK.
 */
static err_t tcp_write_complete(void           * p_arg,
    struct tcp_pcb * p_pcb,
    u16_t            len)
{
  UNUSED_PARAMETER(p_arg);
  UNUSED_PARAMETER(p_pcb);

  if (len != 0)
  {
    //Write complete, reset the state to connected from transmit pending.
    m_tcp_state = TCP_STATE_CONNECTED;
  }
  else
  {
    //Something is not right on the port, close the port.
    tcp_port_close(mp_tcp_port);
  }
  return ERR_OK;
}


/**@brief Send test data on the port.
 *
 * @details Sends TCP data in Request of size 8 in format described in description above.
 *
 * @param[in]   p_pcb     PCB identifier of the port.
 */
static void tcp_send_data(struct tcp_pcb * p_pcb, uint32_t sequence_number)
{
  err_t  err = ERR_OK;

  if (m_tcp_state != TCP_STATE_DATA_TX_IN_PROGRESS)
  {
    //Register callback to get notification of data reception is complete.
    tcp_sent(p_pcb, tcp_write_complete);
    uint8_t tcp_data[TCP_DATA_SIZE];

    tcp_data[0] = (uint8_t )((sequence_number >> 24) & 0x000000FF);
    tcp_data[1] = (uint8_t )((sequence_number >> 16) & 0x000000FF);
    tcp_data[2] = (uint8_t )((sequence_number >> 8)  & 0x000000FF);
    tcp_data[3] = (uint8_t )(sequence_number         & 0x000000FF);

    tcp_data[4] = 'P';
    tcp_data[5] = 'o';
    tcp_data[6] = 'n';
    tcp_data[7] = 'g';

    //Enqueue data for transmission.
    err = tcp_write(p_pcb, tcp_data, TCP_DATA_SIZE, 1);

    if (err != ERR_OK)
    {
      APPL_LOG ("[APPL]: Failed to send TCP packet, reason %d\r\n", err);
    }
    else
    {
      m_tcp_state = TCP_STATE_DATA_TX_IN_PROGRESS;
    }
  }
  else
  {
    //Wait for tx to be complete.
  }
}


/**@brief Callback registered for receiving data on the TCP Port.
 *
 * @param[in]   p_arg     Receive argument set on the TCP port.
 * @param[in]   p_pcb     TCP PCB on which data is received.
 * @param[in]   p_buffer  Buffer with received data.
 * @param[in]   err       Event result indicating error associated with the receive,
 *                        if any, else ERR_OK.
 */
err_t tcp_recv_data_handler(void           * p_arg,
    struct tcp_pcb * p_pcb,
    struct pbuf    * p_buffer,
    err_t            err)
{
  APPL_LOG ("[APPL]: >> TCP Data.\r\n");

  //Check event result before proceeding.
  if (err == ERR_OK)
  {
    uint8_t *p_data = p_buffer->payload;

    if (p_buffer->len == TCP_DATA_SIZE)
    {
      uint32_t sequence_number = 0;

      sequence_number  = ((p_data[0] << 24) & 0xFF000000);
      sequence_number |= ((p_data[1] << 16) & 0x00FF0000);
      sequence_number |= ((p_data[2] << 8)  & 0x0000FF00);
      sequence_number |= (p_data[3]         & 0x000000FF);

      LEDS_OFF(ALL_APP_LED);

      if (sequence_number & 0x00000001)
      {
        LEDS_ON(DISPLAY_LED_0);
      }
      if (sequence_number & 0x00000002)
      {
        LEDS_ON(DISPLAY_LED_1);
      }
      if (sequence_number & 0x00000004)
      {
        LEDS_ON(DISPLAY_LED_2);
      }
      if (sequence_number & 0x00000008)
      {
        LEDS_ON(DISPLAY_LED_3);
      }

      //Send Response
      tcp_send_data(p_pcb, sequence_number);
    }
    else
    {
      APPL_LOG ("[APPL]: TCP data received in incorrect format.\r\n");
    }
    // All is good with the data received, process it.
    tcp_recved(p_pcb, p_buffer->tot_len);
    UNUSED_VARIABLE(pbuf_free(p_buffer));
  }
  else
  {
    //Free the buffer in case its not NULL.
    if (p_buffer != NULL)
    {
      UNUSED_VARIABLE(pbuf_free(p_buffer));
    }

    //Something is not right with the port, close the port.
    tcp_port_close(mp_tcp_port);
  }

  return ERR_OK;
}





/**@brief Function for initializing IP stack.
 *
 * @details Initialize the IP Stack and its driver.
 */
static void ip_stack_init(void)
{
  uint32_t err_code = nrf51_sdk_mem_init();
  APP_ERROR_CHECK(err_code);

  //Initialize LwIP stack.
  lwip_init();

  //Initialize LwIP stack driver.
  err_code = nrf51_driver_init();
  APP_ERROR_CHECK(err_code);
}


/**@brief Timer callback used for periodic servicing of LwIP protocol timers.
 *
 * @details Timer callback used for periodic servicing of LwIP protocol timers.
 *
 * @param[in]   p_context   Pointer used for passing context. No context used in this application.
 */
static void system_timer_callback(void * p_context)
{
  UNUSED_VARIABLE(p_context);
  sys_check_timeouts();
}


/**@brief Function for the Timer initialization.
 *
 * @details Initializes the timer module. This creates and starts application timers.
 */
static void timers_init(void)
{
  uint32_t err_code;

  // Initialize timer module.
  APP_TIMER_APPSH_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, true);

  // Create timers.
  err_code = app_timer_create(&m_sys_timer_id,
      APP_TIMER_MODE_REPEATED,
      system_timer_callback);
  APP_ERROR_CHECK(err_code);
}


/**@brief Function to handle interface up event. */
void nrf51_driver_interface_up(void)
{
  uint32_t err_code;

  APPL_LOG ("[APPL]: IPv6 interface up.\r\n");

  sys_check_timeouts();

  m_tcp_state = TCP_STATE_REQUEST_CONNECTION;

  err_code = app_timer_start(m_sys_timer_id, LWIP_SYS_TIMER_INTERVAL, NULL);
  APP_ERROR_CHECK(err_code);

  LEDS_OFF(ADVERTISING_LED);
  LEDS_ON(CONNECTED_LED);
}


/**@brief Function to handle interface down event. */
void nrf51_driver_interface_down(void)
{
  uint32_t err_code;

  APPL_LOG ("[APPL]: IPv6 interface down.\r\n");

  err_code = app_timer_stop(m_sys_timer_id);
  APP_ERROR_CHECK(err_code);

  LEDS_OFF((DISPLAY_LED_0 | DISPLAY_LED_1 | DISPLAY_LED_2 | DISPLAY_LED_3));
  LEDS_ON(ADVERTISING_LED);

  m_tcp_state = TCP_STATE_DISCONNECTED;
}

void bleconfig_init(void) {
  //Initialize.
  app_trace_init();
  leds_init();
  timers_init();
  ble_stack_init();
  advertising_init();
  ip_stack_init ();
  scheduler_init();

  APPL_LOG ("\r\n");
  APPL_LOG ("[APPL]: Init done.\r\n");

  //Start execution.
  advertising_start();
}

void bleconfig_poll(void) {
  //Execute event schedule.
  app_sched_execute();
}