#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "zarlink.h"

#ifdef CONFIG_INCLUDE_ZARLINK

#include <stdio.h>
#include <stdlib.h>

/*
 * when sending a request, use the current value of cmdFrameIndex. The reply received from 
 * the switch should match this index. If so, increment the index circularly. 
 */

static zarlinkPortStats_t portStats[10];

static union {
  unsigned char requestFrameData8[8];
  unsigned short requestFrameData16[4];
  } request;

static unsigned short replyData16[32];

static int ctrlRequestStats(int port, int octet)
{
  int i;
  unsigned char tmpStatus;
  unsigned int baseAddr;

  // initialize command frame data
  for(i=0;i<8;i++)
    request.requestFrameData8[i] = 0x00;

  request.requestFrameData8[0] = 0x18; // read request
  request.requestFrameData8[1] = 0x00; // statistic counter memory

  // calculate base address in internal memory
  baseAddr = (((unsigned int)((port * 0x40) + (octet * 0x8))) & 0x0FFF);

  request.requestFrameData8[2] = (unsigned char) (baseAddr        & 0xff);
  request.requestFrameData8[3] = (unsigned char)((baseAddr >>  8) & 0xff);
  request.requestFrameData8[4] = (unsigned char)((baseAddr >> 16) & 0xff);

  request.requestFrameData8[5] = 0x01; // number of 8-byte octets
  request.requestFrameData8[6] = (unsigned char)((port*8) + octet);

  // poll status reg bit 0
  for(i=0;i<10000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x01) != 0)
      break;
    }

  if ((tmpStatus & 0x01) == 0)
    return -1;

  for(i=0;i<8/2;i++)
    zarlinkCtrlBuffer1Write(request.requestFrameData16[i]);

  zarlinkCommandWrite(0x01);

  return 0;
}



static int ctrlRetrieveStats(unsigned short * pReplyData16)
{
  int i;
  unsigned char tmpStatus;

  if (pReplyData16 == NULL)
    return -1;

#if 0
  // poll status reg bit 2
  for(i=0;i<10000;i++) {
    tmpStatus = zarlinkStatusRead();
    if ((tmpStatus & 0x04) != 0)
      break;
    }
#else

  //
  // NOTE: reading this register clears _all_
  // interrupt causes, so a frame rx interrupt
  // could be lost here!
  //

  // poll interrupt reg bit 2
  for(i=0; i<1000; i++) {
    tmpStatus = zarlinkInterruptRead();
    if ((tmpStatus & 0x04) != 0)
      break;
    }

#endif

  if ((tmpStatus & 0x04) != 0){
    // retrieve buffer 2
    for(i=0;i<40/2;i++) {
      pReplyData16[i] = zarlinkCtrlBuffer2Read();
      }
    zarlinkCommandWrite(0x04);
    // zarlinkInterruptWrite(0x04);
    // TODO: do checking on this
    cmdFrameIndex++;
    }
  else {
    // no reply data buffers to retrieve
    return -1;
    }

  return 0;
}

static int decodeStats(char *pRawOctetData, int port, int octet)
{
  int retVal = -1;

  if (pRawOctetData == NULL)
    return retVal;

  if ((port < 0) || (port > 10))
    return retVal;

  if ((octet < 0) || (octet > 8))
    return retVal;

  /*
   * for completed read, the first byte (byte 0) of data, bits[7:4] should be 0b0101 for
   * completed read. The frame # (byte 7) should also be examined, and also the 
   * address a bytes 3 and 4. The actual data begins at byte 8.
   */

  switch(octet) {
    case 0:
      portStats[port].bytesSent = (*(unsigned int *)pRawOctetData);
      pRawOctetData += 4;
      portStats[port].unicastFrameSent = ((*(unsigned int *)pRawOctetData) & 0x0fff);
      pRawOctetData += 3;
      portStats[port].frameSentFail = (unsigned int)*pRawOctetData;
      retVal = 0;
      break;
    case 1:
      portStats[port].flowCtrlSent = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].nonUnicastFrameSent = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].bytesReceived = (*(unsigned int *)pRawOctetData);
      retVal = 0;
      break;
    case 2:
      portStats[port].frameReceived = (*(unsigned int *)pRawOctetData);
      pRawOctetData += 4;
      portStats[port].totalBytesReceived = (*(unsigned int *)pRawOctetData);
      retVal = 0;
      break;
    case 3:
      portStats[port].totalFramesReceived = ((*(unsigned int *)pRawOctetData) & 0x0fff);
      pRawOctetData += 3;
      portStats[port].flowCtrlFramesReceived = (unsigned int)*pRawOctetData;
      pRawOctetData += 1;
      portStats[port].mcastFramesReceived = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].bcastFramesReceived = (*(unsigned short *)pRawOctetData);
      retVal = 0;
      break;
    case 4:
      portStats[port].len64Frames = ((*(unsigned int *)pRawOctetData) & 0x0fff);
      pRawOctetData += 3;
      portStats[port].jabberFrames = (unsigned int)*pRawOctetData;
      pRawOctetData += 1;
      portStats[port].len65To127Frames = ((*(unsigned int *)pRawOctetData) & 0x0fff);
      pRawOctetData += 3;
      portStats[port].oversizeFrames = (unsigned int)*pRawOctetData;
      retVal = 0;
      break;
    case 5:
      portStats[port].len128To255Frames = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].len256To511Frames = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].len512To1023Frames = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].len1024To1522Frames = (*(unsigned short *)pRawOctetData);
      retVal = 0;
      break;
    case 6:
      portStats[port].fragment = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].alignError = (unsigned int)*pRawOctetData;
      pRawOctetData++;
      portStats[port].runtFrames = (unsigned int)*pRawOctetData;
      pRawOctetData++;
      portStats[port].crcErrors = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].shortEvent = (*(unsigned short *)pRawOctetData);
      retVal = 0;
      break;
    case 7:
      portStats[port].collision = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].droppedFrames = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].filteringCounter = (*(unsigned short *)pRawOctetData);
      pRawOctetData += 2;
      portStats[port].delayExceedDiscardCounter = (unsigned int)*pRawOctetData;
      pRawOctetData++;
      portStats[port].lateCollision = (unsigned int)*pRawOctetData;
      retVal = 0;
      break;
    default:
      break;
    }

  return retVal;
}

int zarlinkRmonGet(int port)
{
  int i;
  int retVal = 0;
  char * pOutBuf;
  int bufLen;

  pOutBuf = clicharGetOutputBuffer(&bufLen);

  for(i=0; ((i<8) && (retVal == 0)); i++) {

    retVal = ctrlRequestStats(port, i);
    sprintf(pOutBuf, "GetStats: 8 words request %d status[%02x]\r\n", i, zarlinkStatusRead());
    clicharSendOutputBuffer(pOutBuf, bufLen);
    if (retVal != 0) {
       return -1;
      }

    retVal = ctrlRetrieveStats(&(replyData16[4*i]));
    sprintf(pOutBuf, "GetStats: 8 words retrieve %d status[%02x]\r\n", i, zarlinkStatusRead());
    clicharSendOutputBuffer(pOutBuf, bufLen);
    if (retVal != 0) {
      return -2;
      }

    retVal = decodeStats((unsigned char *)(&(replyData16[4*i])), port, i);
    if (retVal != 0) {
      return -3;
      }
    }

  return 0;
}

static int statsHandler(int argc, char *argv[], int flags)
{
  int retVal = -1;
  int bufLen;
  char * pOutBuf;
  int port;

  if (argc < 2)
    return retVal;

  pOutBuf = clicharGetOutputBuffer(&bufLen);
  port = (int)strtol(argv[1], (char **)NULL, 10);
  retVal = zarlinkRmonGet(port);

  if (retVal == 0) {
    sprintf(pOutBuf, "--- Zarlink port stats for port [%d] ---\r\n", port);
    clicharSendOutputBuffer(pOutBuf, bufLen);    
    
    sprintf(pOutBuf, " TX: bytes[%u]  unicastFrame[%u] frameTxFail[%u]\r\n",
      portStats[port].bytesSent, portStats[port].unicastFrameSent, 
      portStats[port].frameSentFail);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, " RX: bytes[%u]  unicastFrame[%u] totalBytes[%u] totalFrames[%u]\r\n",
      portStats[port].bytesReceived, portStats[port].frameReceived, 
      portStats[port].totalBytesReceived, portStats[port].totalFramesReceived);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, "ERR: align[%u]  runtFrame[%u] crcErr[%u] shortEvent[%u]\r\n",
      portStats[port].alignError, portStats[port].runtFrames, 
      portStats[port].crcErrors, portStats[port].shortEvent);
    clicharSendOutputBuffer(pOutBuf, bufLen);    
    }

    /*
    portStats[i].flowCtrlFramesReceived;
    portStats[i].mcastFramesReceived;
    portStats[i].bcastFramesReceived;
    portStats[i].flowCtrlSent;
    portStats[i].nonUnicastFrameSent;
    portStats[i].len64Frames = 0;
    portStats[i].jabberFrames = 0;
    portStats[i].len65To127Frames = 0;
    portStats[i].oversizeFrames = 0;
    portStats[i].len128To255Frames = 0;
    portStats[i].len256To511Frames = 0;
    portStats[i].len512To1023Frames = 0;
    portStats[i].len1024To1522Frames = 0;
    portStats[i].fragment = 0;
    portStats[i].collision = 0;
    portStats[i].droppedFrames = 0;
    portStats[i].filteringCounter = 0;
    portStats[i].delayExceedDiscardCounter = 0;
    portStats[i].lateCollision = 0;
    */

  return retVal;
}



static void initLocalStats(void)
{
  int i;

  for (i=0;i<8;i++) {
    portStats[i].bytesSent = 0;
    portStats[i].unicastFrameSent = 0;
    portStats[i].frameSentFail = 0;
    portStats[i].flowCtrlSent = 0;
    portStats[i].nonUnicastFrameSent = 0;
    portStats[i].bytesReceived = 0;
    portStats[i].frameReceived = 0;
    portStats[i].totalBytesReceived = 0;
    portStats[i].totalFramesReceived = 0;
    portStats[i].flowCtrlFramesReceived = 0;
    portStats[i].mcastFramesReceived = 0;
    portStats[i].bcastFramesReceived = 0;
    portStats[i].len64Frames = 0;
    portStats[i].jabberFrames = 0;
    portStats[i].len65To127Frames = 0;
    portStats[i].oversizeFrames = 0;
    portStats[i].len128To255Frames = 0;
    portStats[i].len256To511Frames = 0;
    portStats[i].len512To1023Frames = 0;
    portStats[i].len1024To1522Frames = 0;
    portStats[i].fragment = 0;
    portStats[i].alignError = 0;
    portStats[i].runtFrames = 0;
    portStats[i].crcErrors = 0;
    portStats[i].shortEvent = 0;
    portStats[i].collision = 0;
    portStats[i].droppedFrames = 0;
    portStats[i].filteringCounter = 0;
    portStats[i].delayExceedDiscardCounter = 0;
    portStats[i].lateCollision = 0;
    }
}

int zarlinkRmonInit(void)
{
  initLocalStats();

  cliRegisterCommand("stats", statsHandler, "stats [port] - retrieve port statistics from switch");
}

#endif

