/*
 * Copyright (c) 2014, SICS Swedish ICT.
 * 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. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``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 INSTITUTE OR CONTRIBUTORS 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.
 *
 * This file is part of the Contiki operating system.
 *
 */

/**
 * \file
 *         Alternative implementation for SLIP:
 *         1. Accepts more than two packet
 *         2. Disables UART rx interrupt when buffer is full
 *         (thus invoking flow control if configured)
 * \author
 *         Niklas Finne <nfi@sics.se>
 *         Beshr Al Nahas <beshr@sics.se>
 *
 */

#include "contiki.h"

#include <MicroInt.h>
#include "net/ip/uip.h"
#include "net/ipv4/uip-fw.h"
#define BUF ((struct uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN])

#include "dev/slip.h"

#define DEBUG 0
#if DEBUG
#include <stdio.h>
#define PRINTF(...) printf(__VA_ARGS__)
#define PUTCHAR(X) do { putchar(X); putchar('\n'); } while(0)
#else
#define PRINTF(...) do {} while(0)
#define PUTCHAR(X) do {} while(0)
#endif

#define SLIP_END     0300
#define SLIP_ESC     0333
#define SLIP_ESC_END 0334
#define SLIP_ESC_ESC 0335
#define SLIP_NEUTRAL  0 /* means: none of the above */
#define SLIP_ESC_XON  0336
#define SLIP_ESC_XOFF 0337
#define XON   ((unsigned char)17)
#define XOFF  ((unsigned char)19)
#if UART_XONXOFF_FLOW_CTRL
volatile unsigned char xonxoff_state = XON;
#endif /* UART_XONXOFF_FLOW_CTRL */

PROCESS(slip_process, "SLIP driver");

#include "dev/uart0.h"
#define STORE_UART_INTERRUPTS       uart0_store_interrupts
#define RESTORE_UART_INTERRUPTS     uart0_restore_interrupts
#define DISABLE_UART_INTERRUPTS     uart0_disable_interrupts
#define ENABLE_UART_INTERRUPTS      uart0_enable_interrupts

/**
 * @brief A block of code may be made atomic by wrapping it with this
 * macro.  Something which is atomic cannot be interrupted by interrupts.
 */
/* A specific ATMOIC that disables UART interrupts only */
#define ATOMIC(blah) \
  { \
    /* STORE_UART_INTERRUPTS(); */ \
    DISABLE_UART_INTERRUPTS(); \
    { blah } \
    /* RESTORE_UART_INTERRUPTS(); */ \
    ENABLE_UART_INTERRUPTS(); \
  }

/* A generic ATMOIC that disables all interrupts */
#define GLOBAL_ATOMIC(blah) \
  { \
    MICRO_DISABLE_INTERRUPTS(); \
    { blah } \
    MICRO_ENABLE_INTERRUPTS(); \
  }

#if 1
#define SLIP_STATISTICS(statement)
#else
uint16_t slip_drop_bytes, slip_overflow, slip_error_drop;
/* No used in this file */
uint16_t slip_rubbish, slip_twopackets, slip_ip_drop;
unsigned long slip_received, slip_frames;
#define SLIP_STATISTICS(statement) statement
#endif

/* Must be at least one byte larger than UIP_BUFSIZE (for SLIP_END)! */
#ifdef SLIP_CONF_RX_BUFSIZE
#define RX_BUFSIZE SLIP_CONF_RX_BUFSIZE

#if RX_BUFSIZE < (UIP_BUFSIZE - UIP_LLH_LEN + 16)
#error "SLIP_CONF_RX_BUFSIZE too small for UIP_BUFSIZE"
#endif

#else
#define RX_BUFSIZE (UIP_CONF_BUFFER_SIZE * 2)
#endif

/*
 * Variables begin and end manage the buffer space in a cyclic
 * fashion. The first used byte is at begin and end is one byte past
 * the last. I.e. [begin, end) is the actively used space.
 */

static volatile uint16_t begin, end, end_counter;
static uint8_t rxbuf[RX_BUFSIZE];
static volatile uint8_t is_dropping = 0;
static volatile uint8_t is_full = 0;

static void (*input_callback)(void) = NULL;
/*---------------------------------------------------------------------------*/
void
slip_set_input_callback(void (*c)(void))
{
  input_callback = c;
}
static void
slip_write_char(uint8_t c)
{
  /* Escape SLIP control characters */
  if(c == SLIP_END) {
    slip_arch_writeb(SLIP_ESC);
    c = SLIP_ESC_END;
  } else if(c == SLIP_ESC) {
    slip_arch_writeb(SLIP_ESC);
    c = SLIP_ESC_ESC;
  }
#if UART_XONXOFF_FLOW_CTRL
  /* Escape XON/XOFF characters */
  else if(c == XON) {
    slip_arch_writeb(SLIP_ESC);
    c = SLIP_ESC_XON;
  } else if(c == XOFF) {
    slip_arch_writeb(SLIP_ESC);
    c = SLIP_ESC_XOFF;
  }
#endif /* UART_XONXOFF_FLOW_CTRL */
  slip_arch_writeb(c);
}
/*---------------------------------------------------------------------------*/
uint8_t
slip_write(const void *_ptr, int len)
{
  const uint8_t *ptr = _ptr;
  uint16_t i;
  uint8_t c;

  slip_arch_writeb(SLIP_END);

  for(i = 0; i < len; ++i) {
    c = *ptr++;
    slip_write_char(c);
  }
  slip_arch_writeb(SLIP_END);

  return len;
}
/*---------------------------------------------------------------------------*/
/* slip_send: forward (IPv4) packets with {UIP_FW_NETIF(..., slip_send)}
 * was used in slip-bridge.c
 */
uint8_t
slip_send(void)
{
  uint16_t i;
  uint8_t *ptr;
  uint8_t c;

  slip_arch_writeb(SLIP_END);

  ptr = &uip_buf[UIP_LLH_LEN];
  for(i = 0; i < uip_len; ++i) {
    if(i == UIP_TCPIP_HLEN) {
      ptr = (uint8_t *)uip_appdata;
    }
    c = *ptr++;
    slip_write_char(c);
  }
  slip_arch_writeb(SLIP_END);

  return UIP_FW_OK;
}
/*---------------------------------------------------------------------------*/
static void
rxbuf_init(void)
{
  begin = end = end_counter = 0;
  is_dropping = 0;
}
/*---------------------------------------------------------------------------*/
/* Upper half does the polling. */
static uint16_t
slip_poll_handler(uint8_t *outbuf, uint16_t blen)
{
  uint16_t len;
  uint16_t pos;
  uint8_t c;
  uint8_t state;

  if(end_counter == 0 && is_full == 0) {
    return 0;
  }
  for(len = 0, pos = begin, state = c = SLIP_NEUTRAL;
      len < blen + 1; /* +1 for SLIP_END! */
      ) {

    c = rxbuf[pos++];

    if(pos == RX_BUFSIZE) {
      /* Circular buffer: warp around */
      pos = 0;
    }
    if(c == SLIP_END) {
      /* End of packet */
      break;
    }
    if(len >= blen) {
      /* End of buffer with no SLIP_END
       * ==> something wrong happened */
      break;
    }
    switch(c) {
    case SLIP_ESC:
      state = SLIP_ESC;
      break;
    case SLIP_ESC_END:
      if(state == SLIP_ESC) {
        outbuf[len++] = SLIP_END;
        state = SLIP_NEUTRAL;
      } else {
        outbuf[len++] = c;
      } break;
    case SLIP_ESC_ESC:
      if(state == SLIP_ESC) {
        outbuf[len++] = SLIP_ESC;
        state = SLIP_NEUTRAL;
      } else {
        outbuf[len++] = c;
      } break;
#if UART_XONXOFF_FLOW_CTRL
    case SLIP_ESC_XON:
      if(state == SLIP_ESC) {
        outbuf[len++] = XON;
        state = SLIP_NEUTRAL;
      } else {
        outbuf[len++] = c;
      } break;
    case SLIP_ESC_XOFF:
      if(state == SLIP_ESC) {
        outbuf[len++] = XOFF;
        state = SLIP_NEUTRAL;
      } else {
        outbuf[len++] = c;
      } break;
#endif /* UART_XONXOFF_FLOW_CTRL */
    default:
      outbuf[len++] = c;
      state = SLIP_NEUTRAL;
      break;
    }
  }

  /* Update counters */
  if(c == SLIP_END) {
    ATOMIC(begin = pos;
           if(end_counter) {
             end_counter--;
           }
           )
    PUTCHAR('P');
  } else {
    /* Something went wrong, no SLIP_END found, drop everything */
    ATOMIC(rxbuf_init();
           is_dropping = 1;
           )
    SLIP_STATISTICS(slip_error_drop++);
    len = 0;
    PRINTF("SLIP: *** out of sync!\n");
  }

  if(end_counter > 0) {
    /* One more packet is buffered, need to be polled again! */
    process_poll(&slip_process);
  }
  return len;
}
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(slip_process, ev, data)
{
  PROCESS_BEGIN();

  rxbuf_init();

  while(1) {
    PROCESS_YIELD_UNTIL(ev == PROCESS_EVENT_POLL);

    /* Move packet from rxbuf to buffer provided by uIP. */
    uip_len = slip_poll_handler(&uip_buf[UIP_LLH_LEN],
                                UIP_BUFSIZE - UIP_LLH_LEN);

    PRINTF("SLIP: recv bytes %u frames RECV: %u. is_full %u, is_dropping %u.\n",
           end_counter, uip_len, is_full, is_dropping);

    /* We have free space now, resume slip RX */
    if(is_full) {
      is_full = 0;
      ENABLE_UART_INTERRUPTS();
    }

    if(uip_len > 0) {
      if(input_callback) {
        input_callback();
      }
#ifdef SLIP_CONF_TCPIP_INPUT
      SLIP_CONF_TCPIP_INPUT();
#else
      tcpip_input();
#endif
    }
  }

  PROCESS_END();
}
/*---------------------------------------------------------------------------*/
/* Return status from slip_input_byte:
 * -1 means RX buffer overflow ==> stop reading
 *  0 means do not exit power saving mode
 *  1 means exit power saving mode
 **/
int
slip_input_byte(unsigned char c)
{
  static int in_frame = 0;
  uint16_t next, next_next;
  int error_return_code = is_full ? -1 : 0;
  int success_return_code = is_full ? -1 : 1;

  SLIP_STATISTICS(slip_received++);

#if UART_XONXOFF_FLOW_CTRL
  if(c == XOFF || c == XON) {
    xonxoff_state = c;
    return 1;
  } else {
    /* ANY char would be XON */
    xonxoff_state = XON;
  }
#endif /* UART_XONXOFF_FLOW_CTRL */

  if(is_dropping) {
    /* Make sure to drop full frames when overflow or
     * out of sync happens */
    if(c != SLIP_END) {
      SLIP_STATISTICS(slip_drop_bytes++);
    } else {
      is_dropping = 0;
      in_frame = 0;
    }
    return error_return_code;
  }

  if(!in_frame && c == SLIP_END) {
    /* Ignore slip end when not receiving frame */
    return error_return_code;
    /* increment and wrap */
  }
  next = end + 1;
  if(next >= RX_BUFSIZE) {
    next = 0;
  }
  next_next = next + 1;
  if(next_next >= RX_BUFSIZE) {
    next_next = 0;
    /* Next byte will overflow. Stop accepting. */
  }
  if(next_next == begin) {
    is_full = 1;
    /* disable UART interrupts */
    DISABLE_UART_INTERRUPTS();
    process_poll(&slip_process);
  }

  /* Buffer is full. We can't store anymore.
   * Shall not happen normally,
   * because of overflow protection above. */
  if(next == begin) {
    is_dropping = 1;
    SLIP_STATISTICS(slip_overflow++);
    is_full = 1;
    /* disable UART interrupts */
    DISABLE_UART_INTERRUPTS();
    process_poll(&slip_process);
    return -1;
  }

  rxbuf[end] = c;
  end = next;
  in_frame = 1;

  if(c == SLIP_END) {
    in_frame = 0;
    end_counter++;
    SLIP_STATISTICS(slip_frames++);
    process_poll(&slip_process);
    return success_return_code;
  }
  return error_return_code;
}
/*---------------------------------------------------------------------------*/
#if SLIP_BRIDGE_CONF_NO_PUTCHAR
int
putchar(int c)
{
  uart0_writeb(c);
  return 1;
}
#endif