/**
 * \addtogroup stm32w-cpu
 *
 * @{
 */

/*
 * Copyright (c) 2010, STMicroelectronics.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/**
* \file
*					Machine dependent STM32W radio code.
* \author
*					Salvatore Pitrulli
*					Chi-Anh La la@imag.fr
*         Simon Duquennoy <simonduq@sics.se>
*/

#include PLATFORM_HEADER
#include "hal/error.h"
#include "hal/hal.h"

#include "contiki.h"

#include "net/mac/frame802154.h"

#include "dev/stm32w-radio.h"
#include "net/netstack.h"

#include "net/packetbuf.h"
#include "net/rime/rimestats.h"
#include "sys/rtimer.h"

#define DEBUG 0

#include "dev/leds.h"
#define LED_ACTIVITY 0

#ifdef ST_CONF_RADIO_AUTOACK
#define ST_RADIO_AUTOACK ST_CONF_RADIO_AUTOACK
#else
#define ST_RADIO_AUTOACK 0
#endif /* ST_CONF_RADIO_AUTOACK */

#if RDC_CONF_DEBUG_LED
#define LED_RDC RDC_CONF_DEBUG_LED
#define LED_ACTIVITY 1
#else
#define LED_RDC 0
#endif  /* RDC_CONF_DEBUG_LED */

#if DEBUG > 0
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#else
#define PRINTF(...) do {} while (0)
#endif  /* DEBUG > 0 */

#if LED_ACTIVITY
#define LED_TX_ON() leds_on(LEDS_GREEN)
#define LED_TX_OFF() leds_off(LEDS_GREEN)
#define LED_RX_ON()     do {                                    \
                                if(LED_RDC == 0){               \
                                  leds_on(LEDS_RED);            \
                                }                               \
                        } while (0)
#define LED_RX_OFF()    do {                                    \
                                if(LED_RDC == 0){               \
                                  leds_off(LEDS_RED);           \
                                }                               \
                        } while (0)
#define LED_RDC_ON()    do {                                    \
                                if(LED_RDC == 1){               \
                                  leds_on(LEDS_RED);            \
                                }                               \
                        } while (0)
#define LED_RDC_OFF()   do {                                    \
                                if(LED_RDC == 1){               \
                                  leds_off(LEDS_RED);           \
                                }                               \
                        } while (0)
#else
#define LED_TX_ON()
#define LED_TX_OFF()
#define LED_RX_ON()
#define LED_RX_OFF()
#define LED_RDC_ON()
#define LED_RDC_OFF()
#endif  /* LED_ACTIVITY */

#if RDC_CONF_HARDWARE_CSMA
#define MAC_RETRIES 0
#endif  /* RDC_CONF_HARDWARE_CSMA */

#ifndef MAC_RETRIES
#define MAC_RETRIES 1
#endif  /* MAC_RETRIES */

#if MAC_RETRIES
int8_t mac_retries_left;
#define INIT_RETRY_CNT() (mac_retries_left = packetbuf_attr(PACKETBUF_ATTR_MAX_MAC_TRANSMISSIONS))
#define DEC_RETRY_CNT() (mac_retries_left--)
#define RETRY_CNT_GTZ() (mac_retries_left > 0)
#else
#define INIT_RETRY_CNT()
#define DEC_RETRY_CNT()
#define RETRY_CNT_GTZ() 0
#endif /* MAC_RETRIES */


/* If set to 1, a send() returns only after the packet has been transmitted.
  This is necessary if you use the x-mac module, for example. */
#ifndef RADIO_WAIT_FOR_PACKET_SENT
#define RADIO_WAIT_FOR_PACKET_SENT 1
#endif  /* RADIO_WAIT_FOR_PACKET_SENT */

#define TO_PREV_STATE()       do {                                    \
                                if(onoroff == OFF){                   \
                                  ST_RadioSleep();                    \
                                  ENERGEST_OFF(ENERGEST_TYPE_LISTEN); \
                                }                                     \
                              } while(0)
#if RDC_CONF_HARDWARE_CSMA
#define ST_RADIO_CHECK_CCA FALSE
#define ST_RADIO_CCA_ATTEMPT_MAX 0
#define ST_BACKOFF_EXP_MIN 0
#define ST_BACKOFF_EXP_MAX 0
#else  /* RDC_CONF_HARDWARE_CSMA */
#define ST_RADIO_CHECK_CCA TRUE
#define ST_RADIO_CCA_ATTEMPT_MAX 4
#define ST_BACKOFF_EXP_MIN 2
#define ST_BACKOFF_EXP_MAX 6
#endif  /* RDC_CONF_HARDWARE_CSMA */

const RadioTransmitConfig radioTransmitConfig = {
  TRUE,                         /* waitForAck; */
  ST_RADIO_CHECK_CCA,           /* checkCca;Set to FALSE with low-power MACs. */
  ST_RADIO_CCA_ATTEMPT_MAX,     /* ccaAttemptMax; */
  ST_BACKOFF_EXP_MIN,           /* backoffExponentMin; */
  ST_BACKOFF_EXP_MAX,           /* backoffExponentMax; */
  TRUE                          /* appendCrc; */
};

#define MAC_RETRIES 0

/*
 * The buffers which hold incoming data.
 */
#ifndef RADIO_RXBUFS
#define RADIO_RXBUFS 1
#endif  /* RADIO_RXBUFS */

/* +1 because of the first byte, which will contain the length of the packet. */
static uint8_t stm32w_rxbufs[RADIO_RXBUFS][STM32W_MAX_PACKET_LEN + 1];

#if RADIO_RXBUFS > 1
static volatile int8_t first = -1, last = 0;
#else   /* RADIO_RXBUFS > 1 */
static const int8_t first = 0, last = 0;
#endif  /* RADIO_RXBUFS > 1 */

#if RADIO_RXBUFS > 1
#define CLEAN_RXBUFS() do{first = -1; last = 0;}while(0)
#define RXBUFS_EMPTY() (first == -1)
int
RXBUFS_FULL()
{
  int8_t first_tmp = first;
  return first_tmp == last;
}
#else /* RADIO_RXBUFS > 1 */
#define CLEAN_RXBUFS() (stm32w_rxbufs[0][0] = 0)
#define RXBUFS_EMPTY() (stm32w_rxbufs[0][0] == 0)
#define RXBUFS_FULL() (stm32w_rxbufs[0][0] != 0)
#endif /* RADIO_RXBUFS > 1 */

static uint8_t
  __attribute__ ((aligned(2))) stm32w_txbuf[STM32W_MAX_PACKET_LEN + 1];


#define CLEAN_TXBUF() (stm32w_txbuf[0] = 0)
#define TXBUF_EMPTY() (stm32w_txbuf[0] == 0)
#define CHECKSUM_LEN 2

/*
 * The transceiver state.
 */
#define ON     0
#define OFF    1

#define BUSYWAIT_UNTIL(cond, max_time)                                    \
    do {                                                                  \
      rtimer_clock_t t0;                                                  \
      t0 = RTIMER_NOW();                                                  \
      while(!(cond) && RTIMER_CLOCK_LT(RTIMER_NOW(), t0 + (max_time)));   \
    } while(0)

#define GET_LOCK() locked++

static volatile uint8_t onoroff = OFF;
static uint8_t receiving_packet = 0;
static int8_t last_rssi;
static volatile StStatus last_tx_status;
static uint8_t locked;
static volatile uint8_t is_transmit_ack;
/*--------------------------------------------------------------------------*/
static void
RELEASE_LOCK(void)
{
  if(locked > 0)
    locked--;
}
/*---------------------------------------------------------------------------*/
PROCESS(stm32w_radio_process, "STM32W radio driver");
/*---------------------------------------------------------------------------*/
static int stm32w_radio_init(void);

static int stm32w_radio_prepare(const void *payload,
                                unsigned short payload_len);
static int stm32w_radio_transmit(unsigned short payload_len);

static int stm32w_radio_send(const void *data, unsigned short len);

static int stm32w_radio_read(void *buf, unsigned short bufsize);

static int stm32w_radio_channel_clear(void);

static int stm32w_radio_receiving_packet(void);

static int stm32w_radio_pending_packet(void);

static int stm32w_radio_on(void);

static int stm32w_radio_off(void);

static int add_to_rxbuf(uint8_t * src);

static int read_from_rxbuf(void *dest, unsigned short len);
/*--------------------------------------------------------------------------*/
const struct radio_driver stm32w_radio_driver = {
  stm32w_radio_init,
  stm32w_radio_prepare,
  stm32w_radio_transmit,
  stm32w_radio_send,
  stm32w_radio_read,
  stm32w_radio_channel_clear,
  stm32w_radio_receiving_packet,
  stm32w_radio_pending_packet,
  stm32w_radio_on,
  stm32w_radio_off,
};
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_init(void)
{
  /* A channel also needs to be set. */
  ST_RadioSetChannel(RF_CHANNEL);

  /* Initialize radio (analog section, digital baseband and MAC). */
  /* Leave radio powered up in non-promiscuous rx mode. */
  ST_RadioInit(ST_RADIO_POWER_MODE_OFF);

  onoroff = OFF;
  ST_RadioSetPanId(IEEE802154_PANID);

  CLEAN_RXBUFS();
  CLEAN_TXBUF();

#if ST_RADIO_AUTOACK && !(UIP_CONF_LL_802154 && RIMEADDR_CONF_SIZE==8)
#error "Autoack and address filtering can only be used with EUI 64"
#endif
  ST_RadioEnableAutoAck(ST_RADIO_AUTOACK);
  ST_RadioEnableAddressFiltering(ST_RADIO_AUTOACK);

  locked = 0;
  process_start(&stm32w_radio_process, NULL);

  return 0;
}
/*---------------------------------------------------------------------------*/
void
stm32w_radio_set_promiscous(uint8_t on)
{
  if(on) {
    ST_RadioEnableAddressFiltering(0);
  } else {
    ST_RadioEnableAddressFiltering(ST_RADIO_AUTOACK);
  }
}
/*---------------------------------------------------------------------------*/
int
stm32w_radio_set_channel(uint8_t channel)
{
  if (ST_RadioSetChannel(channel) == ST_SUCCESS) {
    return 0;
  } else {
    return 1;
  }
}
/*---------------------------------------------------------------------------*/
static int
wait_for_tx(void)
{
  struct timer t;
  timer_set(&t, CLOCK_SECOND / 10);
  while(!TXBUF_EMPTY()) {
    if(timer_expired(&t)) {
      PRINTF("stm32w: tx buffer full.\r\n");
      return 1;
    }
    /* Put CPU in sleep mode. */
    halSleepWithOptions(SLEEPMODE_IDLE, 0);
  }
  return 0;
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_prepare(const void *payload, unsigned short payload_len)
{
  if(payload_len > STM32W_MAX_PACKET_LEN) {
    PRINTF("stm32w: payload length=%d is too long.\r\n", payload_len);
    return RADIO_TX_ERR;
  }
#if !RADIO_WAIT_FOR_PACKET_SENT
  /* 
   * Check if the txbuf is empty. Wait for a finite time.
   * This should not occur if we wait for the end of transmission in 
   * stm32w_radio_transmit().
   */
  if(wait_for_tx()) {
    PRINTF("stm32w: tx buffer full.\r\n");
    return RADIO_TX_ERR;
  }
#endif /* RADIO_WAIT_FOR_PACKET_SENT */

  /*
   * Copy to the txbuf. 
   * The first byte must be the packet length.
   */
  CLEAN_TXBUF();
  memcpy(stm32w_txbuf + 1, payload, payload_len);

  return RADIO_TX_OK;
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_transmit(unsigned short payload_len)
{
  stm32w_txbuf[0] = payload_len + CHECKSUM_LEN;
  INIT_RETRY_CNT();

  if(onoroff == OFF) {
    PRINTF("stm32w: Radio is off, turning it on.\r\n");
    ST_RadioWake();
    ENERGEST_ON(ENERGEST_TYPE_LISTEN);
  }
#if RADIO_WAIT_FOR_PACKET_SENT
  GET_LOCK();
#endif /* RADIO_WAIT_FOR_PACKET_SENT */
  last_tx_status = -1;
  LED_TX_ON();
  if(ST_RadioTransmit(stm32w_txbuf) == ST_SUCCESS) {
    ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
    ENERGEST_ON(ENERGEST_TYPE_TRANSMIT);
    PRINTF("stm32w: sending %d bytes\r\n", payload_len);

#if DEBUG > 1
    for(uint8_t c = 1; c <= stm32w_txbuf[0] - 2; c++) {
      PRINTF("%x:", stm32w_txbuf[c]);
    }
    PRINTF("\r\n");
#endif

#if RADIO_WAIT_FOR_PACKET_SENT

    if(wait_for_tx()) {
      PRINTF("stm32w: unknown tx error.\r\n");
      TO_PREV_STATE();
      LED_TX_OFF();
      RELEASE_LOCK();
      return RADIO_TX_ERR;
    }
    
    TO_PREV_STATE();
    if(last_tx_status == ST_SUCCESS || last_tx_status == ST_PHY_ACK_RECEIVED ||
          last_tx_status == ST_MAC_NO_ACK_RECEIVED) {
      RELEASE_LOCK();
      if(last_tx_status == ST_PHY_ACK_RECEIVED) {
        return RADIO_TX_OK;     /* ACK status */
      } else if(last_tx_status == ST_MAC_NO_ACK_RECEIVED ||
          last_tx_status == ST_SUCCESS) {
        return RADIO_TX_NOACK;
      }
    }
    LED_TX_OFF();
    RELEASE_LOCK();
    return RADIO_TX_ERR;

#else /* RADIO_WAIT_FOR_PACKET_SENT */
    TO_PREV_STATE();
    LED_TX_OFF();
    return RADIO_TX_OK;

#endif /* RADIO_WAIT_FOR_PACKET_SENT */
  }

#if RADIO_WAIT_FOR_PACKET_SENT
  RELEASE_LOCK();
#endif /* RADIO_WAIT_FOR_PACKET_SENT */
  TO_PREV_STATE();

  PRINTF("stm32w: transmission never started.\r\n");
  /* TODO: Do we have to retransmit? */

  CLEAN_TXBUF();
  LED_TX_OFF();
  return RADIO_TX_ERR;
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_send(const void *payload, unsigned short payload_len)
{
  if (stm32w_radio_prepare(payload, payload_len) == RADIO_TX_ERR) {
    return RADIO_TX_ERR;
  }
  return stm32w_radio_transmit(payload_len);
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_channel_clear(void)
{
  return ST_RadioChannelIsClear();
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_receiving_packet(void)
{
  return receiving_packet;
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_pending_packet(void)
{
  return !RXBUFS_EMPTY();
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_off(void)
{
  /* Any transmit or receive packets in progress are aborted.
   * Waiting for end of transmission or reception have to be done.
   */
  if(locked) {
    PRINTF("stm32w: try to off while sending/receiving (lock=%u).\r\n",
           locked);
    return 0;
  }
  /* off only if there is no transmission or reception of packet. */
  if(onoroff == ON && TXBUF_EMPTY() && !receiving_packet) {
    LED_RDC_OFF();
    ST_RadioSleep();
    onoroff = OFF;
    CLEAN_TXBUF();
    CLEAN_RXBUFS();

    ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
  }

  return 1;
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_on(void)
{
  PRINTF("stm32w: turn radio on\n");
  if(onoroff == OFF) {
    LED_RDC_ON();
    ST_RadioWake();
    onoroff = ON;

    ENERGEST_ON(ENERGEST_TYPE_LISTEN);
  }

  return 1;
}
/*---------------------------------------------------------------------------*/
int
stm32w_radio_is_on(void)
{
  return onoroff == ON;
}
/*---------------------------------------------------------------------------*/
void
ST_RadioReceiveIsrCallback(uint8_t *packet,
                           boolean ackFramePendingSet,
                           uint32_t time, uint16_t errors, int8_t rssi)
{
  LED_RX_ON();
  PRINTF("stm32w: incomming packet received\n");
  receiving_packet = 0;

  /* Copy packet into the buffer. It is better to do this here. */
  if(add_to_rxbuf(packet)) {
    process_poll(&stm32w_radio_process);
    last_rssi = rssi;
  }
  LED_RX_OFF();
  GET_LOCK();
  is_transmit_ack = 1;
  /* Wait for sending ACK */
  BUSYWAIT_UNTIL(!is_transmit_ack, RTIMER_SECOND / 1500);
  RELEASE_LOCK();
}
/*--------------------------------------------------------------------------*/
void
ST_RadioTxAckIsrCallback(void)
{
  /*
   * This callback is for simplemac 1.1.0. 
   * Till now we block (RTIMER_SECOND / 1500) 
   * to prevent radio off during ACK transmission.
   */
  is_transmit_ack = 0;
  /* RELEASE_LOCK(); */
}
/*--------------------------------------------------------------------------*/
void
ST_RadioTransmitCompleteIsrCallback(StStatus status,
                                    uint32_t txSyncTime, boolean framePending)
{
  ENERGEST_OFF(ENERGEST_TYPE_TRANSMIT);
  ENERGEST_ON(ENERGEST_TYPE_LISTEN);
  LED_TX_OFF();

  last_tx_status = status;

  if(status == ST_SUCCESS || status == ST_PHY_ACK_RECEIVED) {
    CLEAN_TXBUF();
  } else {
    if(RETRY_CNT_GTZ()) {

      /* Retransmission */
      LED_TX_ON();
      if(ST_RadioTransmit(stm32w_txbuf) == ST_SUCCESS) {
        ENERGEST_OFF(ENERGEST_TYPE_LISTEN);
        ENERGEST_ON(ENERGEST_TYPE_TRANSMIT);
        PRINTF("stm32w: retransmission.\r\n");
        DEC_RETRY_CNT();
      } else {
        CLEAN_TXBUF();
        LED_TX_OFF();
        PRINTF("stm32w: retransmission failed.\r\n");
      }
    } else {
      CLEAN_TXBUF();
    }
  }

  /* Debug outputs. */
  if(status == ST_SUCCESS || status == ST_PHY_ACK_RECEIVED) {
    PRINTF("stm32w: return status TX_END\r\n");
  } else if(status == ST_MAC_NO_ACK_RECEIVED) {
    PRINTF("stm32w: return status TX_END_NOACK\r\n");
  } else if(status == ST_PHY_TX_CCA_FAIL) {
    PRINTF("stm32w: return status TX_END_CCA_FAIL\r\n");
  } else if(status == ST_PHY_TX_UNDERFLOW) {
    PRINTF("stm32w: return status TX_END_UNDERFLOW\r\n");
  } else {
    PRINTF("stm32w: return status TX_END_INCOMPLETE\r\n");
  }
}
/*--------------------------------------------------------------------------*/
boolean
ST_RadioDataPendingShortIdIsrCallback(uint16_t shortId)
{
  receiving_packet = 1;
  return FALSE;
}
/*--------------------------------------------------------------------------*/
boolean
ST_RadioDataPendingLongIdIsrCallback(uint8_t * longId)
{
  receiving_packet = 1;
  return FALSE;
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(stm32w_radio_process, ev, data)
{
  int len;
  PROCESS_BEGIN();
  PRINTF("stm32w_radio_process: started\r\n");

  while(1) {
    PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL);
    PRINTF("stm32w_radio_process: calling receiver callback\r\n");

#if DEBUG > 1
    for(uint8_t c = 1; c <= RCVD_PACKET_LEN; c++) {
      PRINTF("%x", stm32w_rxbuf[c]);
    }
    PRINTF("\r\n");
#endif

    packetbuf_clear();
    len = stm32w_radio_read(packetbuf_dataptr(), PACKETBUF_SIZE);
    if(len > 0) {
      packetbuf_set_datalen(len);
      NETSTACK_RDC.input();
    }
    if(!RXBUFS_EMPTY()) {
      /*
       * Some data packet still in rx buffer (this happens because process_poll
       * doesn't queue requests), so stm32w_radio_process needs to be called
       * again.
       */
      process_poll(&stm32w_radio_process);
    }
  }
  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
static int
stm32w_radio_read(void *buf, unsigned short bufsize)
{
  return read_from_rxbuf(buf, bufsize);
}
/*---------------------------------------------------------------------------*/
void
ST_RadioOverflowIsrCallback(void)
{
  PRINTF("stm32w: radio overflow\r\n");
}
/*---------------------------------------------------------------------------*/
void
ST_RadioSfdSentIsrCallback(uint32_t sfdSentTime)
{
}
/*---------------------------------------------------------------------------*/
void
ST_RadioMacTimerCompareIsrCallback(void)
{
}
/*---------------------------------------------------------------------------*/
static int
add_to_rxbuf(uint8_t *src)
{
  if(RXBUFS_FULL()) {
    return 0;
  }

  memcpy(stm32w_rxbufs[last], src, src[0] + 1);
#if RADIO_RXBUFS > 1
  last = (last + 1) % RADIO_RXBUFS;
  if(first == -1) {
    first = 0;
  }
#endif

  return 1;
}
/*---------------------------------------------------------------------------*/
static int
read_from_rxbuf(void *dest, unsigned short len)
{
  if(RXBUFS_EMPTY()) {          /* Buffers are all empty */
    return 0;
  }

  if(stm32w_rxbufs[first][0] > len) {   /* Too large packet for dest. */
    len = 0;
  } else {
    len = stm32w_rxbufs[first][0];
    memcpy(dest, stm32w_rxbufs[first] + 1, len);
    packetbuf_set_attr(PACKETBUF_ATTR_RSSI, last_rssi);
  }

#if RADIO_RXBUFS > 1
  ATOMIC(first = (first + 1) % RADIO_RXBUFS;
         int first_tmp = first; if(first_tmp == last) {
         CLEAN_RXBUFS();}
  )
#else
  CLEAN_RXBUFS();
#endif

  return len;
}
/*---------------------------------------------------------------------------*/
short
last_packet_rssi()
{
  return last_rssi;
}
/** @} */