#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmTime.h"
#include "cmMidi.h"
#include "cmMidiPort.h"



//===================================================================================================
//
//

enum 
{ 
  kBufByteCnt = 1024, 

  kExpectStatusStId=0,       // 0
  kExpectDataStId,           // 1
  kExpectStatusOrDataStId,   // 2
  kExpectEOXStId             // 3
};

typedef struct cmMpParserCb_str
{
  cmMpCallback_t           cbFunc;
  void*                    cbDataPtr;
  struct cmMpParserCb_str* linkPtr;
} cmMpParserCb_t;

typedef struct
{
  cmErr_t        err;

  cmMpParserCb_t* cbChain;
  
  cmMidiPacket_t  pkt;

  unsigned      state;          // parser state id
  unsigned      errCnt;         // accumlated error count
  cmMidiByte_t  status;         // running status
  cmMidiByte_t  data0;          // data byte 0
  unsigned      dataCnt;        // data byte cnt for current status 
  unsigned      dataIdx;        // index (0 or 1) of next data byte
  cmMidiByte_t* buf;            // output buffer
  unsigned      bufByteCnt;     // output buffer byte cnt
  unsigned      bufIdx;         // next output buffer index
  unsigned      msgCnt;         // count of channel messages in the buffer
} cmMpParser_t;

cmMpParser_t* _cmMpParserFromHandle( cmMpParserH_t h )
{
  cmMpParser_t* p = (cmMpParser_t*)h.h;
  assert(p!=NULL);
  return p;
}

void _cmMpParserReport( cmMpParser_t* p )
{
  cmRptPrintf(p->err.rpt,"state:%i st:0x%x d0:%i dcnt:%i didx:%i buf[%i]->%i msg:%i err:%i\n",p->state,p->status,p->data0,p->dataCnt,p->dataIdx,p->bufByteCnt,p->bufIdx,p->msgCnt,p->errCnt);
}

void _cmMpParserDestroy( cmMpParser_t* p )
{
  cmMemPtrFree(&p->buf);

  cmMpParserCb_t* c = p->cbChain;
  while(c != NULL)
  {
    cmMpParserCb_t* nc = c->linkPtr;
    cmMemFree(c);
    c = nc;
  }

  cmMemPtrFree(&p);

}
 
cmMpParserH_t cmMpParserCreate( unsigned devIdx, unsigned portIdx, cmMpCallback_t cbFunc, void* cbDataPtr, unsigned bufByteCnt, cmRpt_t* rpt )
{
  cmMpRC_t      rc = kOkMpRC;
  cmMpParserH_t h;
  cmMpParser_t* p = cmMemAllocZ( cmMpParser_t, 1 );

  cmErrSetup(&p->err,rpt,"MIDI Parser");

  p->pkt.devIdx        = devIdx;
  p->pkt.portIdx       = portIdx;

  //p->cbChain           = cmMemAllocZ( cmMpParserCb_t, 1 );
  //p->cbChain->cbFunc    = cbFunc;
  //p->cbChain->cbDataPtr = cbDataPtr;
  //p->cbChain->linkPtr   = NULL;
  p->cbChain           = NULL;
  p->buf               = cmMemAllocZ( cmMidiByte_t, bufByteCnt );
  p->bufByteCnt        = bufByteCnt;
  p->bufIdx            = 0;
  p->msgCnt            = 0;
  p->state             = kExpectStatusStId;
  p->dataIdx           = cmInvalidIdx;
  p->dataCnt           = cmInvalidCnt;
  p->status            = kInvalidStatusMdId;

  h.h = p;

  if( cbFunc != NULL )
    rc = cmMpParserInstallCallback(h, cbFunc, cbDataPtr );
    

  if( rc != kOkMpRC )
  {
    h.h = NULL;
    _cmMpParserDestroy(p);
  }
  

  return h;
} 

void        cmMpParserDestroy(    cmMpParserH_t* hp )
{
  if( hp==NULL || hp->h == NULL )
    return;

  cmMpParser_t* p = _cmMpParserFromHandle(*hp);

  _cmMpParserDestroy(p);
  
  hp->h = NULL;

}

unsigned    cmMpParserErrorCount( cmMpParserH_t h )
{
  cmMpParser_t* p = _cmMpParserFromHandle(h);
  if( p == NULL )
    return 0;

  return p->errCnt;
}

void _cmMpParserCb( cmMpParser_t* p, cmMidiPacket_t* pkt, unsigned pktCnt )
{
  cmMpParserCb_t* c = p->cbChain;
  for(; c!=NULL; c=c->linkPtr)
  {
    pkt->cbDataPtr = c->cbDataPtr;
    c->cbFunc( pkt, pktCnt );
  }
}

void _cmMpTransmitChMsgs( cmMpParser_t* p )
{
  if( p->msgCnt > 0 )
  {
    p->pkt.msgArray = (cmMidiMsg*)p->buf;
    p->pkt.msgCnt   = p->msgCnt;
    p->pkt.sysExMsg = NULL;

    //p->cbFunc( &p->pkt, 1 );  
    _cmMpParserCb(p,&p->pkt,1);

    p->bufIdx = 0;
    p->msgCnt = 0;
  }
}

void _cmMpTransmitSysEx( cmMpParser_t* p )
{
  p->pkt.msgArray = NULL;
  p->pkt.sysExMsg = p->buf;
  p->pkt.msgCnt   = p->bufIdx;
  //p->cbFunc( &p->pkt, 1 );
  _cmMpParserCb(p,&p->pkt,1);
  p->bufIdx = 0;

}

void _cmMpParserStoreChMsg( cmMpParser_t* p, const cmTimeSpec_t* timeStamp,  cmMidiByte_t d )
{
  // if there is not enough room left in the buffer then transmit
  // the current messages
  if( p->bufByteCnt - p->bufIdx < sizeof(cmMidiMsg) )
    _cmMpTransmitChMsgs(p);


  assert( p->bufByteCnt - p->bufIdx >= sizeof(cmMidiMsg) );

  // get a pointer to the next msg in the buffer
  cmMidiMsg* msgPtr = (cmMidiMsg*)(p->buf + p->bufIdx);

  // fill the buffer msg
  msgPtr->timeStamp = *timeStamp;
  msgPtr->status  = p->status;

  switch( p->dataCnt )
  {
    case 0: 
      break;
    case 1:
      msgPtr->d0 = d;
      msgPtr->d1 = 0;
      break;

    case 2:
      msgPtr->d0 = p->data0;
      msgPtr->d1 = d;
      break;

    default:
      assert(0);
  }

  // update the msg count and next buffer 
  ++p->msgCnt;

  p->bufIdx += sizeof(cmMidiMsg);

}

void cmMpParseMidiData( cmMpParserH_t h, const cmTimeSpec_t* timeStamp, const cmMidiByte_t* iBuf, unsigned iByteCnt )
{
  
  cmMpParser_t* p = _cmMpParserFromHandle(h);

  if( p == NULL )
    return;
  
  const cmMidiByte_t* ip = iBuf;
  const cmMidiByte_t* ep  = iBuf + iByteCnt;

  for(; ip < ep; ++ip )
  {
    // if this byte is a status byte
    if( cmMidiIsStatus(*ip) )
    {
      if( p->state != kExpectStatusStId && p->state != kExpectStatusOrDataStId )
        ++p->errCnt;

      p->status  = *ip;
      p->dataCnt = cmMidiStatusToByteCount(*ip);

      switch( p->status )
      {
        case kSysExMdId: // if this is the start of a sys-ex msg ...
          // ... clear the buffer to prepare from sys-ex data
          _cmMpTransmitChMsgs(p); 

          p->state   = kExpectEOXStId;
          p->dataCnt = cmInvalidCnt;
          p->dataIdx = cmInvalidIdx;
          p->buf[ p->bufIdx++ ] =  kSysExMdId;
          break;

        case kSysComEoxMdId: // if this is the end of a sys-ex msg
          assert( p->bufIdx < p->bufByteCnt );
          p->buf[p->bufIdx++] = *ip; 
          _cmMpTransmitSysEx(p);
          p->state = kExpectStatusStId;
          break;

        default: // ... otherwise it is a 1,2, or 3 byte msg status
          if( p->dataCnt > 0 )
          {
            p->state   = kExpectDataStId;
            p->dataIdx = 0;
          }
          else
          {
            // this is a status only msg - store it
            _cmMpParserStoreChMsg(p,timeStamp,*ip);

            p->state   = kExpectStatusStId;
            p->dataIdx = cmInvalidIdx;
            p->dataCnt = cmInvalidCnt;
          }

      }

      continue;
    }

    // at this point the current byte (*ip) is a data byte

    switch(p->state)
    {

      case kExpectStatusOrDataStId:
        assert( p->dataIdx == 0 );
          
      case kExpectDataStId:
        switch( p->dataIdx )
        {
          case 0: // expecting data byte 0 ...
            
            switch( p->dataCnt )
            {
              case 1: // ... of a 1 byte msg - the msg is complete
                _cmMpParserStoreChMsg(p,timeStamp,*ip);
                p->state = kExpectStatusOrDataStId; 
                break;

              case 2: // ... of a 2 byte msg - prepare to recv the second data byte
                p->state   = kExpectDataStId;
                p->dataIdx = 1;
                p->data0   = *ip;
                break;

              default:
                assert(0);
            }
            break;

          case 1: // expecting data byte 1 of a two byte msg
            assert( p->dataCnt == 2 );
            assert( p->state == kExpectDataStId );

            _cmMpParserStoreChMsg(p,timeStamp,*ip);
            p->state   = kExpectStatusOrDataStId;
            p->dataIdx = 0;
            break;

          default:
            assert(0);            
            
        }
        break;

      case kExpectEOXStId:
          assert( p->bufIdx < p->bufByteCnt );

          p->buf[p->bufIdx++] = *ip;

          // if the buffer is full - then transmit it
          if( p->bufIdx == p->bufByteCnt )
            _cmMpTransmitSysEx(p);

        break;

    }

  } // ip loop

  _cmMpTransmitChMsgs(p);
 
}

cmMpRC_t  cmMpParserMidiTriple(   cmMpParserH_t h, const cmTimeSpec_t* timeStamp, cmMidiByte_t status, cmMidiByte_t d0, cmMidiByte_t d1 )
{
  cmMpRC_t rc = kOkMpRC;
  cmMpParser_t* p = _cmMpParserFromHandle(h);
  cmMidiByte_t mb = 0xff; // a midi triple may never have a status of 0xff

  if( d0 == 0xff )
    p->dataCnt = 0;
  else
    if( d1 == 0xff )
      p->dataCnt = 1;
    else
      p->dataCnt = 2;

  p->status  = status;
  switch( p->dataCnt )
  {
    case 0:      
      mb = status;
     break;

    case 1:
      mb = d0;
      break;

    case 2:
      p->data0 = d0;
      mb = d1;
      break;

    default:
      rc = cmErrMsg(&p->err,kInvalidArgMpRC,"An invalid MIDI status byte (0x%x) was encountered by the MIDI data parser.");
      goto errLabel;
      break;
  }

  if( mb != 0xff )
    _cmMpParserStoreChMsg(p,timeStamp,mb);
  
  p->dataCnt = cmInvalidCnt;

 errLabel:
  return rc;
}

cmMpRC_t  cmMpParserTransmit(    cmMpParserH_t h )
{
  cmMpParser_t* p = _cmMpParserFromHandle(h);
  _cmMpTransmitChMsgs(p);
  return kOkMpRC;
}

cmMpRC_t      cmMpParserInstallCallback( cmMpParserH_t h, cmMpCallback_t  cbFunc, void* cbDataPtr )
{
  cmMpParser_t*   p        = _cmMpParserFromHandle(h);
  cmMpParserCb_t* newCbPtr = cmMemAllocZ( cmMpParserCb_t, 1 );
  cmMpParserCb_t* c        = p->cbChain;
  
  newCbPtr->cbFunc    = cbFunc;
  newCbPtr->cbDataPtr = cbDataPtr;
  newCbPtr->linkPtr   = NULL;

  if( p->cbChain == NULL )
    p->cbChain = newCbPtr;
  else  
  {
    while( c->linkPtr != NULL )
      c = c->linkPtr;

    c->linkPtr = newCbPtr;
  }
  
  return kOkMpRC;
}

cmMpRC_t      cmMpParserRemoveCallback(  cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbDataPtr )
{
  cmMpParser_t*   p = _cmMpParserFromHandle(h);
  cmMpParserCb_t* c1 = p->cbChain;  // target link
  cmMpParserCb_t* c0 = NULL;        // link pointing to target

  // search for the cbFunc to remove
  for(; c1!=NULL; c1=c1->linkPtr)
  {
    if( c1->cbFunc == cbFunc && c1->cbDataPtr == cbDataPtr)
      break;

    c0 = c1;
  }

  // if the cbFunc was not found
  if( c1 == NULL )
    return cmErrMsg(&p->err,kCbNotFoundMpRC,"Unable to locate the callback function %p for removal.",cbFunc);

  // the cbFunc to remove was found

  // if it was the first cb in the chain
  if( c0 == NULL )
    p->cbChain = c1->linkPtr;
  else
    c0->linkPtr = c1->linkPtr;

  cmMemFree(c1);
  
  return kOkMpRC;
}

bool cmMpParserHasCallback( cmMpParserH_t h, cmMpCallback_t cbFunc, void* cbDataPtr )
{
  cmMpParser_t*   p = _cmMpParserFromHandle(h);
  cmMpParserCb_t* c = p->cbChain;  // target link

  // search for the cbFunc to remove
  for(; c!=NULL; c=c->linkPtr)
    if( c->cbFunc == cbFunc && c->cbDataPtr == cbDataPtr )
      return true;

  return false;
}

//====================================================================================================
//
//

unsigned    cmMpDeviceNameToIndex(const cmChar_t* name)
{
  assert(name!=NULL);
  unsigned i;
  unsigned n = cmMpDeviceCount();
  for(i=0; i<n; ++i)
    if( strcmp(cmMpDeviceName(i),name)==0)
      return i;
  return cmInvalidIdx;
}

unsigned    cmMpDevicePortNameToIndex( unsigned devIdx, unsigned flags, const cmChar_t* name )
{
  unsigned i;
  unsigned n = cmMpDevicePortCount(devIdx,flags);
  for(i=0; i<n; ++i)
    if( strcmp(cmMpDevicePortName(devIdx,flags,i),name)==0)
      return i;

  return cmInvalidIdx;
}

//====================================================================================================
//
//

void cmMpTestPrint( void* userDataPtr, const char* fmt, va_list vl )
{
  vprintf(fmt,vl);
}

void cmMpTestCb( const cmMidiPacket_t* pktArray, unsigned pktCnt )
{
  unsigned i,j;
  for(i=0; i<pktCnt; ++i)
  {
    const cmMidiPacket_t* p = pktArray + i;

    for(j=0; j<p->msgCnt; ++j)
      if( p->msgArray != NULL )
        printf("%ld %ld 0x%x %i %i\n", p->msgArray[j].timeStamp.tv_sec, p->msgArray[j].timeStamp.tv_nsec, p->msgArray[j].status,p->msgArray[j].d0, p->msgArray[j].d1);
      else
        printf("0x%x ",p->sysExMsg[j]);

  }
}

void cmMpTest( cmCtx_t* ctx )
{
  char ch;
  unsigned parserBufByteCnt = 1024;
  cmMpInitialize(ctx,cmMpTestCb,NULL,parserBufByteCnt,"app");
  cmMpReport(&ctx->rpt);

 
  cmRptPrintf(&ctx->rpt,"<return> to continue\n");

  while((ch = getchar()) != 'q')
  {
    cmMpDeviceSend(2,0,0x90,60,60);
  }
 

  cmMpFinalize();
}
