#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmTime.h"
#include "cmAudioPort.h"
#include "cmAudioPortFile.h"
#include "cmAudioFileDev.h"

typedef struct
{
  cmAfdH_t devH;
  unsigned devIdx;  // afp dev idx
  unsigned baseApDevIdx; // global audio device index for first afp device
} cmApDev_t;

typedef struct
{
  cmErr_t    err;
  cmApDev_t* devArray;
  unsigned   devCnt;
  unsigned   baseApDevIdx;
} cmApf_t;

cmApf_t* _cmApf = NULL;

cmApRC_t      cmApFileAllocate( cmRpt_t* rpt )
{
  cmApRC_t rc = kOkApRC;

  if( _cmApf != NULL )
    cmApFileFree();

  _cmApf = cmMemAllocZ(cmApf_t,1);

  cmErrSetup(&_cmApf->err,rpt,"Audio Port File");
  _cmApf->devArray     = NULL;
  _cmApf->devCnt       = 0;
  _cmApf->baseApDevIdx = 0;

  return rc;
}

cmApRC_t      cmApFileFree()
{
  cmApRC_t rc = kOkApRC;

  if( _cmApf == NULL )
    return rc;

  if((rc = cmApFileFinalize()) != kOkApRC )
    return rc;

  cmMemFree(_cmApf);
  _cmApf = NULL;
  return rc;
}


cmApRC_t      cmApFileInitialize( cmRpt_t* rpt, unsigned baseApDevIdx )
{
  cmApRC_t rc = kOkApRC;

  unsigned i = 0;
  for(; i<_cmApf->devCnt; ++i)
    _cmApf->devArray[i].baseApDevIdx = baseApDevIdx;

  _cmApf->baseApDevIdx = baseApDevIdx;

  return rc;      
}

cmApRC_t      cmApFileFinalize()
{
  cmApRC_t rc = kOkApRC;

  if( _cmApf == NULL )
    return kOkApRC;

  unsigned i;
  for(i=0; i<_cmApf->devCnt; ++i)
  {
    cmApRC_t rc0;
    if((rc0 = cmApFileDeviceDestroy(i)) != kOkApRC )
      rc = rc0;
  }

  cmMemPtrFree(&_cmApf->devArray);
  _cmApf->devCnt = 0;

  cmMemPtrFree(&_cmApf);

  return rc;
}

unsigned      cmApFileDeviceCreate( 
  const cmChar_t* devLabel,
  const cmChar_t* iFn,
  const cmChar_t* oFn,
  unsigned        oBits,
  unsigned        oChCnt )
{
  unsigned i;

  // find an available device slot
  for(i=0; i<_cmApf->devCnt; ++i)
    if( cmAudioFileDevIsValid( _cmApf->devArray[i].devH ) == false )
      break;

  // if no device slot is availd ...
  if( i == _cmApf->devCnt )
  {
    // ... create a new one
    _cmApf->devArray = cmMemResizePZ(cmApDev_t, _cmApf->devArray, _cmApf->devCnt+1);
    ++_cmApf->devCnt;
  }

  // open the file device
  if( cmAudioFileDevInitialize( &_cmApf->devArray[i].devH, devLabel, i, iFn, oFn, oBits, oChCnt, _cmApf->err.rpt ) != kOkAfdRC )
  {
    cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device initialization failed.");
    i = cmInvalidIdx;
    goto errLabel;
  }

  _cmApf->devArray[i].devIdx = i;

 errLabel:
  
  return i;
}

cmApRC_t      cmApFileDeviceDestroy( unsigned devIdx )
{
  if( cmAudioFileDevFinalize( &_cmApf->devArray[devIdx].devH ) != kOkAfdRC )
    return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device finalize failed.");

  return kOkApRC;
}

unsigned      cmApFileDeviceCount()
{ return _cmApf->devCnt; }

const char*   cmApFileDeviceLabel( unsigned devIdx )
{
  assert( devIdx < cmApFileDeviceCount());
  return cmAudioFileDevLabel( _cmApf->devArray[devIdx].devH ); 
}

unsigned      cmApFileDeviceChannelCount(   unsigned devIdx, bool inputFl )
{
  assert( devIdx < cmApFileDeviceCount());
  return cmAudioFileDevChannelCount( _cmApf->devArray[devIdx].devH, inputFl ); 
}

double        cmApFileDeviceSampleRate(     unsigned devIdx )
{
  assert( devIdx < cmApFileDeviceCount());
  return cmAudioFileDevSampleRate( _cmApf->devArray[devIdx].devH ); 
}

unsigned      cmApFileDeviceFramesPerCycle( unsigned devIdx, bool inputFl )
{
  assert( devIdx < cmApFileDeviceCount());
  return cmAudioFileDevFramesPerCycle( _cmApf->devArray[devIdx].devH, inputFl ); 
}

cmApRC_t      cmApFileDeviceSetup(          
  unsigned          devIdx, 
  double            srate, 
  unsigned          framesPerCycle, 
  cmApCallbackPtr_t callbackPtr,
  void*             userCbPtr )
{
  assert( devIdx < cmApFileDeviceCount());

  if( cmAudioFileDevSetup( _cmApf->devArray[devIdx].devH,_cmApf->baseApDevIdx,srate,framesPerCycle,callbackPtr,userCbPtr) != kOkAfdRC )
    return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed.");

  return kOkApRC;
}

cmApRC_t      cmApFileDeviceStart( unsigned devIdx )
{
  assert( devIdx < cmApFileDeviceCount());

  if( cmAudioFileDevStart( _cmApf->devArray[devIdx].devH ) != kOkAfdRC )
    return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed.");

  return kOkApRC;
}

cmApRC_t      cmApFileDeviceStop(  unsigned devIdx )
{
  assert( devIdx < cmApFileDeviceCount());

  if( cmAudioFileDevStop( _cmApf->devArray[devIdx].devH ) != kOkAfdRC )
    return cmErrMsg(&_cmApf->err,kAudioPortFileFailApRC,"The audio file device setup failed.");

  return kOkApRC;
}

bool          cmApFileDeviceIsStarted( unsigned devIdx )
{
  assert( devIdx < cmApFileDeviceCount());
  return cmAudioFileDevIsStarted( _cmApf->devArray[devIdx].devH );
}

void          cmApFileReport( cmRpt_t* rpt )
{
  unsigned i;
  for(i=0; _cmApf->devCnt; ++i)
  {
    cmRptPrintf(rpt,"%i: ",i);
    cmAudioFileDevReport( _cmApf->devArray[i].devH, rpt );
    cmRptPrintf(rpt,"\n");
  }
}


// device callback function used with cmAudioPortFileTest() note that this assumes
// that the packet buffer contain non-interleaved data.
void _cmApFileTestCb(
  cmApAudioPacket_t* inPktArray, 
  unsigned           inPktCnt, 
  cmApAudioPacket_t* outPktArray, 
  unsigned           outPktCnt )
{
  cmApAudioPacket_t* ip  = inPktArray;
  cmApAudioPacket_t* op  = outPktArray;
  unsigned           opi = 0;
  unsigned           ipi = 0;
  unsigned           oci = 0;
  unsigned           ici = 0;

  while(1)
  {
    if( ici == ip->chCnt)
    {
      ici = 0;
      if( ++ipi >= inPktCnt )
        break;

      ip = inPktArray + ipi;
    }


    if( oci == op->chCnt )
    {
      oci = 0;
      if( ++opi >= outPktCnt )
        break;

      ip = outPktArray + opi;
    }

    assert( ip->audioFramesCnt == op->audioFramesCnt );
    assert( cmIsFlag(ip->flags,kInterleavedApFl)==false && cmIsFlag(ip->flags,kInterleavedApFl)==false );

    cmApSample_t* ibp = ((cmApSample_t*)ip->audioBytesPtr) + (ip->audioFramesCnt*ici);
    cmApSample_t* obp = ((cmApSample_t*)op->audioBytesPtr) + (op->audioFramesCnt*oci);

    memcpy(obp,ibp,ip->audioFramesCnt*sizeof(cmApSample_t));

    ++ici;
    ++oci;
  }
}


void          cmApFileTest( cmRpt_t* rpt )
{
  unsigned dev0Idx;
 
  const cmChar_t* promptStr      = "apf> q=quit 1=start 0=stop\n";
  const cmChar_t* label0         = "file0";
  const cmChar_t* i0Fn           = "/home/kevin/media/audio/McGill-1/1 Audio Track.aiff";
  const cmChar_t* o0Fn           = "/home/kevin/temp/afd1.aif";
  unsigned        o0Bits         = 16;
  unsigned        o0ChCnt        = 2;
   double         srate          = 44100;
  unsigned        framesPerCycle = 512;

  // initialize audio port file API
  if( cmApFileInitialize(rpt,0) != kOkApRC )
    return;

  // create an audio port file
  if((dev0Idx = cmApFileDeviceCreate(label0,i0Fn,o0Fn,o0Bits,o0ChCnt)) == cmInvalidIdx )
    goto errLabel;
 
  // configure an audio port file
  if( cmApFileDeviceSetup( dev0Idx, srate, framesPerCycle, _cmApFileTestCb, NULL ) != kOkAfdRC )
    goto errLabel;
 
  char c;
  fputs(promptStr,stderr);
  fflush(stdin);

  while((c=getchar()) != 'q')
  {
    switch(c)
    {
      case '0': cmApFileDeviceStart(dev0Idx); break;
      case '1': cmApFileDeviceStop(dev0Idx);  break;
    }

    fputs(promptStr,stderr);
    fflush(stdin);
    c = 0;
  }



 errLabel:
  //cmApFileDeviceDestroy(dev0Idx);

  cmApFileFinalize();

}
