#include "archinc.h"
#include "cli.h"
#include "strutil.h"
#include "zarlink.h"
#include "zl_rmon.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[20];

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
   *
   * NOTE: internal memory is addressed in 8-byte chunks called octets, 
   * NOT bytes! Each port has eight octets of RMON data so, for example, 
   * port 1 base address is 0x8, port 7 base address is 0x38 (56).
   */

  baseAddr = (unsigned int)(((port * 0x8) + octet) & 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 ctrlClearStats(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] = 0x28; // write request
  request.requestFrameData8[1] = 0x00; // statistic counter memory

  /*
   * calculate base address in internal memory
   *
   * NOTE: internal memory is addressed in 8-byte chunks called octets, 
   * NOT bytes! Each port has eight octets of RMON data so, for example, 
   * port 1 base address is 0x8, port 7 base address is 0x38 (56).
   */

  baseAddr = (unsigned int)(((port * 0x8) + octet) & 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]);

  for(i=0;i<8/2;i++)
    zarlinkCtrlBuffer1Write((unsigned short)0);

  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
    }
  else {
    // no reply data buffers to retrieve
    return -1;
    }

  return 0;
}

static int ctrlClearPort(int port)
{
  int octet;
  int retStat = 0;

  for(octet=0;((octet<8) && (retStat == 0)); octet++) {
    retStat = ctrlClearStats(port, octet);
    }

  return retStat;
}

static int ctrlClearAll(void)
{
  int port;
  int retStat = 0;

  for(port=0;((port<10) && (retStat == 0)); port++) {
    retStat = ctrlClearPort(port);
    }

  return retStat;
}

static octet0 * pOctet0;
static octet1 * pOctet1;
static octet2 * pOctet2;
static octet3 * pOctet3;
static octet4 * pOctet4;
static octet5 * pOctet5;
static octet6 * pOctet6;
static octet7 * pOctet7;

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:
      pOctet0 = (octet0 *)pRawOctetData;
      portStats[port].bytesSent = pOctet0->bytesSent;
      portStats[port].unicastFrameSent = pOctet0->unicastFrameSent;
      portStats[port].frameSentFail = pOctet0->frameSentFail;
      retVal = 0;
      break;
    case 1:
      pOctet1 = (octet1 *)pRawOctetData;
      portStats[port].flowCtrlSent = pOctet1->flowCtrlSent;
      portStats[port].nonUnicastFrameSent = pOctet1->nonUnicastFrameSent;
      portStats[port].bytesReceived= pOctet1->bytesReceived;
      retVal = 0;
      break;
    case 2:
      pOctet2 = (octet2 *)pRawOctetData;
      portStats[port].frameReceived  = pOctet2->frameReceived;
      portStats[port].totalBytesReceived = pOctet2->totalBytesReceived;
      retVal = 0;
      break;
    case 3:
      pOctet3 = (octet3 *)pRawOctetData;
      portStats[port].totalFramesReceived = pOctet3->totalFramesReceived;
      portStats[port].flowCtrlFramesReceived = pOctet3->flowCtrlFramesReceived;
      portStats[port].mcastFramesReceived = pOctet3->mcastFramesReceived;
      portStats[port].bcastFramesReceived = pOctet3->bcastFramesReceived;

      retVal = 0;
      break;
    case 4:
      pOctet4 = (octet4 *)pRawOctetData;
      portStats[port].len64Frames = pOctet4->len64Frames;
      portStats[port].jabberFrames = pOctet4->jabberFrames;
      portStats[port].len65To127Frames = pOctet4->len65To127Frames;
      portStats[port].oversizeFrames = pOctet4->oversizeFrames;
      retVal = 0;
      break;
    case 5:
      pOctet5 = (octet5 *)pRawOctetData;
      portStats[port].len128To255Frames = pOctet5->len128To255Frames;
      portStats[port].len256To511Frames = pOctet5->len256To511Frames;
      portStats[port].len512To1023Frames = pOctet5->len512To1023Frames;
      portStats[port].len1024To1522Frames = pOctet5->len1024To1522Frames;
      retVal = 0;
      break;
    case 6:
      pOctet6 = (octet6 *)pRawOctetData;
      portStats[port].fragment = pOctet6->fragment;
      portStats[port].alignError  = pOctet6->alignError;
      portStats[port].runtFrames = pOctet6->runtFrames;
      portStats[port].crcErrors = pOctet6->crcErrors;
      portStats[port].shortEvent = pOctet6->shortEvent;
      retVal = 0;
      break;
    case 7:
      pOctet7 = (octet7 *)pRawOctetData;
      portStats[port].collision = pOctet7->collision;
      portStats[port].droppedFrames = pOctet7->droppedFrames;
      portStats[port].filteringCounter = pOctet7->filteringCounter;
      portStats[port].delayExceedDiscardCounter = pOctet7->delayExceedDiscardCounter;
      portStats[port].lateCollision = pOctet7->lateCollision;
      retVal = 0;
      break;
    default:
      break;
    }

  return retVal;
}

int zarlinkRmonGet(int port)
{
  int i;
  int retVal = 0;

  for(i=0; ((i<8) && (retVal == 0)); i++) {

    retVal = ctrlRequestStats(port, i);
    if (retVal != 0) {
       return -1;
      }

    retVal = ctrlRetrieveStats(replyData16);
    if (retVal != 0) {
      return -2;
      }

    // TODO: check frame sequence number from returned data

    retVal = decodeStats((unsigned char *)(&(replyData16[4])), 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]  frames[%u]  totalBytes[%u]  totalFrames[%u]\r\n",
      portStats[port].bytesReceived, 
      portStats[port].frameReceived, 
      portStats[port].totalBytesReceived, 
      portStats[port].totalFramesReceived);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, "  RX: mcastFrames[%u]  bcastFrames[%u]  flowCtrlFrames[%u]\r\n",
      portStats[port].mcastFramesReceived, 
      portStats[port].bcastFramesReceived, 
      portStats[port].flowCtrlFramesReceived);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, " ERR: align[%u]  runt[%u]  crc[%u]  shortEvent[%u]\r\n",
      portStats[port].alignError, 
      portStats[port].runtFrames, 
      portStats[port].crcErrors, 
      portStats[port].shortEvent);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, "FLOW: flowCtrlFramesRecv[%u]  flowCtrlSent[%u]\r\n",
      portStats[port].flowCtrlFramesReceived,
      portStats[port].flowCtrlSent);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, " PKT: len64[%u]  len65-127[%u]  jabber[%u]  oversize[%u]\r\n",
      portStats[port].len64Frames,
      portStats[port].len65To127Frames,
      portStats[port].jabberFrames,
      portStats[port].oversizeFrames);
    clicharSendOutputBuffer(pOutBuf, bufLen);    
      
    sprintf(pOutBuf, " LEN: 128-255[%u]  256-511[%u]  512-1023[%u]  1024-1522[%u]\r\n",
      portStats[port].len128To255Frames,
      portStats[port].len256To511Frames,
      portStats[port].len512To1023Frames,
      portStats[port].len1024To1522Frames);
    clicharSendOutputBuffer(pOutBuf, bufLen);    

    sprintf(pOutBuf, "OOPS: fragment[%u]  dropped[%u]  filterCount[%u]  delayExceedDiscard[%u]\r\n",
      portStats[port].fragment,
      portStats[port].droppedFrames,
      portStats[port].filteringCounter,
      portStats[port].delayExceedDiscardCounter);
    clicharSendOutputBuffer(pOutBuf, bufLen);    
    }

  return retVal;
}

static void clearLocalStatsPort(int port)
{

  if ((port < 0) || (port > 9))
    return;

  portStats[port].bytesSent = 0;
  portStats[port].unicastFrameSent = 0;
  portStats[port].frameSentFail = 0;
  portStats[port].flowCtrlSent = 0;
  portStats[port].nonUnicastFrameSent = 0;
  portStats[port].bytesReceived = 0;
  portStats[port].frameReceived = 0;
  portStats[port].totalBytesReceived = 0;
  portStats[port].totalFramesReceived = 0;
  portStats[port].flowCtrlFramesReceived = 0;
  portStats[port].mcastFramesReceived = 0;
  portStats[port].bcastFramesReceived = 0;
  portStats[port].len64Frames = 0;
  portStats[port].jabberFrames = 0;
  portStats[port].len65To127Frames = 0;
  portStats[port].oversizeFrames = 0;
  portStats[port].len128To255Frames = 0;
  portStats[port].len256To511Frames = 0;
  portStats[port].len512To1023Frames = 0;
  portStats[port].len1024To1522Frames = 0;
  portStats[port].fragment = 0;
  portStats[port].alignError = 0;
  portStats[port].runtFrames = 0;
  portStats[port].crcErrors = 0;
  portStats[port].shortEvent = 0;
  portStats[port].collision = 0;
  portStats[port].droppedFrames = 0;
  portStats[port].filteringCounter = 0;
  portStats[port].delayExceedDiscardCounter = 0;
  portStats[port].lateCollision = 0;

}

static void clearLocalStatsAll(void)
{
  int i;

  for (i=0;i<10;i++) {
    clearLocalStatsPort(i);
    }
}

static int clearHandler(int argc, char *argv[], int flags)
{
  int retVal = -1;
  int bufLen;
  char * pOutBuf;
  int port;

  if (argc == 2) {
    port = (int)strtol(argv[1], (char **)NULL, 10);
    clearLocalStatsPort(port);
    retVal = ctrlClearPort(port);
    }
  else if (argc == 1) {
    clearLocalStatsAll();
    retVal = ctrlClearAll();
    }

  return retVal;
}


int zarlinkRmonInit(void)
{
  cliRegisterCommand("stats", statsHandler, "stats [port] - retrieve port statistics from switch");
  cliRegisterCommand("clear", clearHandler, "clear [port] - clear port statistics");
  
  clearLocalStatsAll();
  // ctrlClearAll();
  return 0;
}

void zarlinkRmonCounterOverflow(unsigned char intPort, unsigned long rmonOverflowBits)
{
  // TODO: Need to implement
}

#endif

