#include "EspLink.h"

#define READ_BUF_DFLT_SIZE 64

// Standard SLIP escape chars from RFC
#define SLIP_END      0300    // indicates end of packet
#define SLIP_ESC      0333    // indicates byte stuffing
#define SLIP_ESC_END  0334    // ESC ESC_END means END data byte
#define SLIP_ESC_ESC  0335    // ESC ESC_ESC means ESC data byte

EspLink::EspLink(Stream &streamIn, CmdRequestCB callback):stream(streamIn),requestCb(callback)
{
  readBuf = NULL;
  readLastChar = 0;
}

EspLink::~EspLink()
{
  if( readBuf != NULL )
    free( readBuf );
  readBuf = NULL;
}
    
void EspLink::writeChar(uint8_t data)
{
  switch(data)
  {
  case SLIP_END:
    stream.write(SLIP_ESC);
    stream.write(SLIP_ESC_END);
    break;
  case SLIP_ESC:
    stream.write(SLIP_ESC);
    stream.write(SLIP_ESC_ESC);
    break;
  default:
    stream.write(data);
  }

  crc16_add(data, &crc16_out);
}

/* CITT CRC16 polynomial ^16 + ^12 + ^5 + 1 */
/*---------------------------------------------------------------------------*/
void EspLink::crc16_add(uint8_t b, uint16_t *crc)
{
  *crc ^= b;
  *crc  = (*crc >> 8) | (*crc << 8);
  *crc ^= (*crc & 0xff00) << 4;
  *crc ^= (*crc >> 8) >> 4;
  *crc ^= (*crc & 0xff00) >> 5;
}

void EspLink::writeBuf(uint8_t * buf, uint16_t len)
{
  while(len-- > 0)
    writeChar(*buf++);
}

void EspLink::sendPacketStart(uint16_t cmd, uint32_t value, uint16_t argc)
{
  crc16_out = 0;
  stream.write( SLIP_END );
  writeBuf((uint8_t*)&cmd, 2);
  writeBuf((uint8_t*)&argc, 2);
  writeBuf((uint8_t*)&value, 4);
}

void EspLink::sendPacketArg(uint16_t len, uint8_t * data)
{
  writeBuf((uint8_t*)&len, 2);
  writeBuf(data, len);

  uint16_t pad = ((len+3)&~3) - len; // get to multiple of 4
  if (pad > 0) {
    uint32_t temp = 0;
    writeBuf((uint8_t*)&temp, pad);
  }
}

void EspLink::sendPacketEnd() {
  uint16_t crc = crc16_out;
  writeBuf((uint8_t*)&crc, 2);
  stream.write(SLIP_END);
}

void EspLink::parseSlipPacket()
{
  CmdRequest req;
  req.cmd = (CmdPacket *)readBuf;
  req.arg_num = 0;
  req.arg_ptr = readBuf + sizeof(CmdPacket);

  requestCb(&req);

  free(readBuf);
  readBuf = NULL;
}

void EspLink::checkPacket()
{
  if( readBufPtr <= 3 )
    return;
  uint16_t crc = 0;
  for(uint16_t i=0; i < readBufPtr - 2; i++)
    crc16_add(readBuf[i], &crc);

  uint16_t crcpacket = *(uint16_t*)(readBuf + readBufPtr - 2);

  if( crc == crcpacket )
  {
    readBufPtr -= 2;
    parseSlipPacket();
  }
}

void EspLink::readLoop()
{
  if( stream.available() > 0 )
  {
    int byt = stream.read();

    switch(readState)
    {
      case WAIT_FOR_SLIP_START:
        if( byt == SLIP_END )
        {
          if(readBuf != NULL)
            free(readBuf);
          readBufPtr = 0;
          readBufMax = READ_BUF_DFLT_SIZE;
          readBuf = (uint8_t *)malloc(readBufMax);
          readState = READ_SLIP_PACKAGE;
        }
        break;
      case READ_SLIP_PACKAGE:
        if( byt == SLIP_END )
        {
          readState = WAIT_FOR_SLIP_START;
          checkPacket();
          break;
        }
        if( byt == SLIP_ESC )
          break;
        if( readLastChar == SLIP_ESC && byt == SLIP_ESC_END )
          byt = SLIP_END;
        else if( readLastChar == SLIP_ESC && byt == SLIP_ESC_ESC )
          byt = SLIP_ESC;

        if( readBufPtr >= readBufMax )
        {
          readBufMax = readBufMax + READ_BUF_DFLT_SIZE;
          readBuf = (uint8_t *)realloc(readBuf, readBufMax);
          if( readBuf == NULL )
          {
            readState = WAIT_FOR_SLIP_START; // TODO
            break;
          }
        }
        readBuf[readBufPtr++] = byt;
        break;
    }

    readLastChar = byt;
  }
}

// Return the number of arguments given a command struct
uint32_t EspLink::cmdGetArgc(CmdRequest *req) {
  return req->cmd->argc;
}

// Copy the next argument from a command structure into the data pointer, returns 0 on success
// -1 on error
int32_t EspLink::cmdPopArg(CmdRequest *req, void *data, uint16_t len) {
  uint16_t length;

  if (req->arg_num >= req->cmd->argc)
    return -1;

  length = *(uint16_t*)req->arg_ptr;
  if (length != len) return -1; // safety check

  memcpy(data, req->arg_ptr + 2, length);
  req->arg_ptr += (length+5)&~3; // round up to multiple of 4

  req->arg_num ++;
  return 0;
}

// Skip the next argument
void EspLink::cmdSkipArg(CmdRequest *req) {
  uint16_t length;

  if (req->arg_num >= req->cmd->argc) return;

  length = *(uint16_t*)req->arg_ptr;

  req->arg_ptr += (length+5)&~3;
  req->arg_num ++;
}

// Return the length of the next argument
uint16_t EspLink::cmdArgLen(CmdRequest *req) {
  return *(uint16_t*)req->arg_ptr;
}