// File: serial-interface-uart.c
//
// Description: Implementation of the interface described in serial-interface.h
// using the ASH UART protocol.
//
// Copyright 2006-2010 by Ember Corporation. All rights reserved.           *80*

#include PLATFORM_HEADER

#include "stack/include/ember-types.h"

#include "hal/hal.h"
#include "app/util/ezsp/ezsp-protocol.h"
#include "app/util/ezsp/ezsp.h"
#include "app/util/ezsp/serial-interface.h"
#include "hal/micro/generic/ash-protocol.h"
#include "hal/micro/generic/ash-common.h"
#include "app/ezsp-host/ash/ash-host.h"
#include "app/ezsp-host/ezsp-host-io.h"
#include "app/ezsp-host/ezsp-host-priv.h"
#include "app/ezsp-host/ezsp-host-queues.h"
#include "app/util/ezsp/ezsp-frame-utilities.h"
#include "debug-printing.h" //yjq add for printf

#define elapsedTimeInt16u(oldTime, newTime) \
  ((uint16_t) ((uint16_t)(newTime) - (uint16_t)(oldTime)))

//------------------------------------------------------------------------------
// Global Variables

static bool waitingForResponse = false;
static uint16_t waitStartTime;
#define WAIT_FOR_RESPONSE_TIMEOUT (ASH_MAX_TIMEOUTS * ashReadConfig(ackTimeMax))

static uint8_t ezspFrameLength;
uint8_t *ezspFrameLengthLocation = &ezspFrameLength;
static uint8_t ezspFrameContentsStorage[EZSP_MAX_FRAME_LENGTH];
uint8_t *ezspFrameContents = ezspFrameContentsStorage;

//------------------------------------------------------------------------------
// Serial Interface Downwards

EzspStatus ezspInit(void)
{
  EzspStatus status;
  uint8_t i;
  for (i = 0; i < 5; i++) {
    status = ashResetNcp();
    if (status != EZSP_SUCCESS) {
      return status;
    }
    status = ashStart();
    if (status == EZSP_SUCCESS) {
      return status;
    }
  }
  return status;
}

bool ezspCallbackPending(void)
{
  return (ncpSleepEnabled && ncpHasCallbacks);
}

void ezspClose(void)
{
  ezspSerialClose();
}

static bool checkConnection(void)
{
  bool connected = ashIsConnected();
  if (!connected) {
    // Attempt to restore the connection. This will reset the EM260.
    ezspClose();
    ezspInit();
  }

  return connected;
}

uint8_t serialPendingResponseCount(void)
{
	return ezspQueueLength(&rxQueue);
}

WEAK_TEST EzspStatus serialResponseReceived(void)
{
  EzspStatus status;
  EzspBuffer *buffer;
  EzspBuffer *dropBuffer = NULL;
  if (!checkConnection()) {
    ezspTraceEzspVerbose("serialResponseReceived(): EZSP_NOT_CONNECTED");
    return EZSP_NOT_CONNECTED;
  }
  ashSendExec();
  status = ashReceiveExec();
  if (status != EZSP_SUCCESS
      && status != EZSP_ASH_IN_PROGRESS
      && status != EZSP_NO_RX_DATA) {
    ezspTraceEzspVerbose("serialResponseReceived(): ashReceiveExec(): 0x%x",
                         status);
    return status;
  }
  if (waitingForResponse
      && elapsedTimeInt16u(waitStartTime, halCommonGetInt16uMillisecondTick())
      > WAIT_FOR_RESPONSE_TIMEOUT) {
    waitingForResponse = false;
    ezspTraceEzspFrameId("no response", ezspFrameContents);
    ezspTraceEzspVerbose("serialResponseReceived(): EZSP_ERROR_NO_RESPONSE");
    return EZSP_ERROR_NO_RESPONSE;
  }
  status = EZSP_NO_RX_DATA;
  buffer = ezspQueuePrecedingEntry(&rxQueue, NULL);
  while (buffer != NULL) {
    // While we are waiting for a response to a command, we use the asynch
    // callback flag to ignore asynchronous callbacks. This allows our caller
    // to assume that no callbacks will appear between sending a command and
    // receiving its response.
    if (waitingForResponse
        && (buffer->data[EZSP_FRAME_CONTROL_INDEX]
            & EZSP_FRAME_CONTROL_ASYNCH_CB)
        ) {
      if (ezspFreeListLength(&rxFree) == 0) {
        dropBuffer = buffer;
      }
      buffer = ezspQueuePrecedingEntry(&rxQueue, buffer);
    } else {
      ezspTraceEzspVerbose("serialResponseReceived(): ID=0x%x Seq=0x%x Buffer=%u",
                           buffer->data[EZSP_FRAME_ID_INDEX],
                           buffer->data[EZSP_SEQUENCE_INDEX],
                           buffer);
      ezspRemoveQueueEntry(&rxQueue, buffer);
      memcpy(ezspFrameContents, buffer->data, buffer->len);
      ezspTraceEzspFrameId("got response", buffer->data);
      ezspFrameLength = buffer->len;
      ezspFreeBuffer(&rxFree, buffer);
      ezspTraceEzspVerbose("serialResponseReceived(): ezspFreeBuffer(): %u", buffer);
      buffer = NULL;
      status = EZSP_SUCCESS;
      waitingForResponse = false;
    }
  }
  if (dropBuffer != NULL) {
    ezspRemoveQueueEntry(&rxQueue, dropBuffer);
    ezspFreeBuffer(&rxFree, dropBuffer);
    ezspTraceEzspFrameId("dropping", dropBuffer->data);
    ezspTraceEzspVerbose("serialResponseReceived(): ezspFreeBuffer(): drop %u", dropBuffer);
    ezspTraceEzspVerbose("serialResponseReceived(): ezspErrorHandler(): EZSP_ERROR_QUEUE_FULL");
    ezspErrorHandler(EZSP_ERROR_QUEUE_FULL);
  }
  return status;
}

WEAK_TEST EzspStatus serialSendCommand(void)
{
	/*emberAfAppPrintln("\r\n---------EZSP Send---------");
	for(int i=0;i<ezspFrameLength;i++){
		emberAfAppPrint("0x%02X ",ezspFrameContents[i]);
	}
	emberAfAppPrint("\r\n");*/

	EzspStatus status;
  if (!checkConnection()) {
    ezspTraceEzspVerbose("serialSendCommand(): EZSP_NOT_CONNECTED");
    return EZSP_NOT_CONNECTED;
  }

  ezspTraceEzspFrameId("send command", ezspFrameContents);
  status = ashSend(ezspFrameLength, ezspFrameContents);
  if (status != EZSP_SUCCESS) {
    ezspTraceEzspVerbose("serialSendCommand(): ashSend(): 0x%x", status);
    return status;
  }
  waitingForResponse = true;
  ezspTraceEzspVerbose("serialSendCommand(): ID=0x%x Seq=0x%x",
                       ezspFrameContents[EZSP_FRAME_ID_INDEX],
                       ezspFrameContents[EZSP_SEQUENCE_INDEX]);
  waitStartTime = halCommonGetInt16uMillisecondTick();
  return status;
}

uint8_t serialGetCommandLength(void)
{
  return *ezspFrameLengthLocation;
}

bool ezspOkToSleep(void)
{
  return
    (ncpSleepEnabled
     && (ezspSleepMode != EZSP_FRAME_CONTROL_IDLE)
     && ashOkToSleep() );
}

void ezspEnableNcpSleep(bool enable)
{
  uint8_t enableval = (uint8_t) enable;
  ezspSetValue(EZSP_VALUE_UART_SYNCH_CALLBACKS, 1, &enableval);
  ncpSleepEnabled = enable;
}

void ezspWakeUp(void)
{
  EzspStatus status = ashWakeUpNcp(true);
  while (status == EZSP_ASH_IN_PROGRESS) {
    simulatedTimePasses();
    status = ashWakeUpNcp(false);
  }
}

// Offset from start of ezspEcho frame to data payload - skip over length byte
#define ECHO_DATA_OFFSET      (EZSP_EXTENDED_PARAMETERS_INDEX + 1)
// Maximum length of ezspEcho data
#define ECHO_MAX_DATA_LENGTH  (EZSP_MAX_FRAME_LENGTH - ECHO_DATA_OFFSET)

EzspStatus serialTestFlowControl(void)
{
  uint8_t buffer[ECHO_MAX_DATA_LENGTH];
  uint8_t rnum;

  // Construct the largest possible echo command frame, in which every data byte
  // is preceded by an escape byte, by setting all data to the ASH_FLAG value.
  // If data randomization is enabled, the data array must be XOR'ed with the
  // ASH pseudo-random sequence. Note that the random sequence must be cycled
  // enough times to skip over the preceding bytes in the EZSP frame.
  rnum = ashRandomizeArray(0, buffer, ECHO_DATA_OFFSET);
  memset(buffer, ASH_FLAG, ECHO_MAX_DATA_LENGTH);
  if (ashReadConfig(randomize)) {
    (void)ashRandomizeArray(rnum, buffer, ECHO_MAX_DATA_LENGTH);
  }

  // Use delay command to have the NCP pause before reading the next command,
  // then see if the frame had to be retransmitted due to buffer overflow.
  ezspDelayTest(100);   // wait 100 milliseconds before reading next command
  ashCount.txReDataFrames = 0;
  (void)ezspEcho(ECHO_MAX_DATA_LENGTH, buffer, buffer);
  ezspTick();
  return (ashCount.txReDataFrames == 0) ? EZSP_SUCCESS
         : EZSP_HOST_FATAL_ERROR;
}
