#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmFloatTypes.h"
#include "cmComplexTypes.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLinkedHeap.h"
#include "cmSymTbl.h"
#include "cmJson.h"
#include "cmMidi.h"
#include "cmAudioFile.h"
#include "cmFile.h"
#include "cmFileSys.h"
#include "cmProcObj.h"
#include "cmProcTemplate.h"
#include "cmVectOpsTemplateMain.h"
#include "cmProc.h"
#include "cmProc2.h"
#include "cmProc3.h"
#include "cmPickup.h"
#include "cmAudLabelFile.h"

enum
{
  kRmsPuId,
  kMedPuId,
  kDifPuId,
  kAvgPuId,
  kOnsPuId,
  kFltPuId,
  kSupPuId,
  kAtkPuId,
  kRlsPuId,
  kSegPuId,
  kPuCnt
};

typedef struct
{
  cmErr_t   err;
  unsigned  chCnt;
  cmPuCh_t* chArray;
  cmCtx_t   ctx;    // stored ctx used by cmAudLabelFileAllocOpen()
  
  // cmProc objects
  cmCtx*             ctxp;
  cmAudioFileRd*     afrp;
  cmShiftBuf*        sbp;
  cmGateDetect2*     gdp;
  cmBinMtxFile_t*    mfp;

  const cmChar_t*    inAudFn;
  const cmChar_t*    inLabelFn;
  const cmChar_t*    outMtx0Fn;
  const cmChar_t*    outMtx1Fn;
  const cmChar_t*    outAudFn;
  cmReal_t           hopMs;

  cmGateDetectParams gd0Args;
  cmGateDetectParams gd1Args;
} cmPu_t;

cmPuH_t cmPuNullHandle = cmSTATIC_NULL_HANDLE;

cmPu_t* _cmPuHandleToPtr( cmPuH_t h )
{
  cmPu_t* p = (cmPu_t*)h.h;
  assert(p!=NULL);
  return p;
}

cmPuRC_t cmPuAlloc( cmCtx_t* ctx, cmPuH_t* hp )
{
  cmPuRC_t rc;
  if((rc = cmPuFree(hp)) != kOkPuRC )
    return rc;

  cmPu_t* p = cmMemAllocZ(cmPu_t,1);
  cmErrSetup(&p->err,&ctx->rpt,"Pickup");
 
  p->ctx = *ctx;
  hp->h = p;

  return rc;
}


cmPuRC_t cmPuFree( cmPuH_t* hp )
{
  if( hp == NULL || cmPuIsValid(*hp) == false )
    return kOkPuRC;

  cmPu_t* p = _cmPuHandleToPtr(*hp);

  cmMemPtrFree(&p->chArray);
  cmMemPtrFree(&p);
  hp->h = NULL;
  return kOkPuRC;
}

bool     cmPuIsValid( cmPuH_t h )
{ return h.h != NULL; }
  

cmPuRC_t _cmPuReadLabelsAndCreateArray( cmPu_t* p, const cmChar_t* labelFn, cmReal_t srate )
{
  cmPuRC_t rc = kOkPuRC;
  cmAlfH_t h  = cmAlfNullHandle;
  unsigned i;

  if( cmAudLabelFileAllocOpen(&p->ctx, &h, labelFn) != kOkAlfRC )
    return cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune audio label file open failed on '%s'",cmStringNullGuard(labelFn));
  
  if((p->chCnt   = cmAudLabelFileCount(h)) == 0 )
  {
    rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune audio label file '%s' does not contain any segment labels.",cmStringNullGuard(labelFn));
    goto errLabel;
  }

  p->chArray = cmMemResizeZ(cmPuCh_t,p->chArray,p->chCnt);

  for(i=0; i<p->chCnt; ++i)
  {
    const cmAlfLabel_t* lp;
    if(( lp = cmAudLabelFileLabel(h,i)) == NULL )
    {
      rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune label in '%s' at row %i could not be read.",cmStringNullGuard(labelFn),i+1);
      goto errLabel;
    }

    p->chArray[i].begSmpIdx = floor(srate * lp->begSecs);
    p->chArray[i].endSmpIdx = p->chArray[i].begSmpIdx; // default the segment to have 0 length.
    
  }

 errLabel:
  if( cmAudLabelFileFree(&h) != kOkAlfRC )
    rc = cmErrMsg(&p->err,kAlfFileFailPuRC,"The auto-tune label file close failed.");

  return rc;
}

cmPuRC_t      _cmPuWriteMtxFile(cmPu_t* p, bool segFl )
{
  cmPuRC_t rc = kOkPuRC;

  cmReal_t outV[ kPuCnt ];
  outV[ kRmsPuId ] = p->gdp->rms;
  outV[ kMedPuId ] = p->gdp->med;
  outV[ kDifPuId ] = p->gdp->dif;
  outV[ kAvgPuId ] = p->gdp->avg;
  outV[ kOnsPuId ] = p->gdp->ons;
  outV[ kFltPuId ] = p->gdp->flt;
  outV[ kSupPuId ] = p->gdp->sup;
  outV[ kAtkPuId ] = p->gdp->onFl;
  outV[ kRlsPuId ] = p->gdp->offFl;
  outV[ kSegPuId ] = segFl;
      
  // write the output file - plot with cmGateDetectPlot.m
  if( cmBinMtxFileExecR(p->mfp,outV,kPuCnt) != cmOkRC )
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Matrix file write failed.");
   
  return rc;
}

void _cmPuCalcGains( cmPu_t* p )
{
  unsigned i;
  cmReal_t avg = 0;

  if( p->chCnt == 0 )
    return;

  for(i=0; i<p->chCnt; ++i)
    avg += p->chArray[i].gateMaxAvg;

  avg /= p->chCnt;

  for(i=0; i<p->chCnt; ++i)
  {
    cmReal_t d = p->chArray[i].gateMaxAvg==0 ? 1.0 : p->chArray[i].gateMaxAvg;
    p->chArray[i].gain = avg / d;
  }
}

cmPuCh_t* _cmPuIncrCh( cmPu_t* p, cmPuCh_t* chp, unsigned* segSmpIdxPtr )
{
  if( *segSmpIdxPtr != p->chArray[0].begSmpIdx )
    ++chp;           

  if( chp >= p->chArray + p->chCnt )
    return NULL;

  if( chp+1 == p->chArray + p->chCnt )
    *segSmpIdxPtr = p->afrp->info.frameCnt;
  else
    *segSmpIdxPtr = (chp+1)->begSmpIdx;

  return chp;
}

cmPuRC_t _cmPuCalcRerunGateDetectors( 
  cmPu_t*                   p,
  const cmChar_t*           outMtxFn,
  const cmChar_t*           outAudFn,
  const cmGateDetectParams* gdArgs, 
  unsigned                  procSmpCnt, 
  unsigned                  wndSmpCnt, 
  unsigned                  hopSmpCnt )
{
  cmPuRC_t       rc         = kOkPuRC;
  cmAudioFileWr* afwp       = NULL;
  unsigned       outChCnt   = 1;
  unsigned       outChIdx   = 0;
  unsigned       bitsPerSmp = 16;
  unsigned       smpIdx     = 0;
  cmSample_t*    smpV       = NULL;
  

  // rewind the audio file reader
  if( cmAudioFileRdSeek(p->afrp,0) != cmOkRC )
  {
    cmErrMsg(&p->err,kProcFailPuRC,"Audio file seek failed.");
    goto errLabel;
  }

  // reset the shift buffer
  if( cmShiftBufInit( p->sbp, procSmpCnt, wndSmpCnt, hopSmpCnt ) != cmOkRC )
  {
    cmErrMsg(&p->err,kProcFailPuRC,"Shift buffer reset failed.");
    goto errLabel;
  }

  // reset the gate detector
  if( cmGateDetectInit2( p->gdp, procSmpCnt, gdArgs ) != cmOkRC )
  {
    cmErrMsg(&p->err,kProcFailPuRC,"Gate detector reset failed.");
    goto errLabel;
  }

  // create an new matrix output file
  if( cmBinMtxFileInit( p->mfp, outMtxFn ) != cmOkRC )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Output matrix file '%s' initialization failed.",cmStringNullGuard(outMtxFn));
    goto errLabel;
  }

  // create an audio output file
  if( (afwp = cmAudioFileWrAlloc(p->ctxp, NULL, procSmpCnt, outAudFn, p->afrp->info.srate, outChCnt,  bitsPerSmp )) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Output audio file '%s' initialization failed.",cmStringNullGuard(outAudFn));
    goto errLabel;
  }
  
  smpV = cmMemAllocZ(cmSample_t,procSmpCnt);

  cmPuCh_t* chp      = p->chArray;
  unsigned segSmpIdx = chp->begSmpIdx; 
  bool      segFl    = false;

  // for each procSmpCnt samples
  for(; cmAudioFileRdRead(p->afrp) != cmEofRC; smpIdx += procSmpCnt )
  {
    // apply auto-gain to the audio vector
    cmVOS_MultVVS(smpV,p->afrp->outN,p->afrp->outV,chp->gain);

    // is this a segment boundary
    if( smpIdx+procSmpCnt >= p->afrp->info.frameCnt || (smpIdx <= segSmpIdx && segSmpIdx < smpIdx + procSmpCnt) )
    {
      segFl = true;

      if((chp = _cmPuIncrCh(p,chp, &segSmpIdx )) == NULL )
        break;
    }

    // shift the new samples into the shift buffer
    while(cmShiftBufExec(p->sbp,smpV,p->afrp->outN))
    {
      
      // update the gate detector
      cmGateDetectExec2(p->gdp,p->sbp->outV,p->sbp->outN);

      if( _cmPuWriteMtxFile(p,segFl) != kOkPuRC )
        goto errLabel;

      segFl =false;
    }

    // write the audio output file
    if( cmAudioFileWrExec(afwp, outChIdx,smpV,p->afrp->outN ) != cmOkRC )
    {
      cmErrMsg(&p->err,kProcFailPuRC,"A write failed to the audio output file '%s'.",outAudFn);
      goto errLabel;
    }

  }

 errLabel:  

  cmMemPtrFree(&smpV);
  
  if( cmAudioFileWrFree(&afwp) != cmOkRC )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Output audio file '%s' close failed.",cmStringNullGuard(outAudFn));
    goto errLabel;
  }

  return rc;
}

cmPuRC_t cmPuAutoGainCfg( 
  cmPuH_t                   h,
  const cmChar_t*           audioFn,
  const cmChar_t*           labelFn,
  const cmChar_t*           outMtx0Fn,
  const cmChar_t*           outMtx1Fn,
  const cmChar_t*           outAudFn,
  unsigned                  procSmpCnt,
  cmReal_t                  hopMs,
  const cmGateDetectParams* gd0Args,
  const cmGateDetectParams* gd1Args )
{
  cmPuRC_t        rc;
  cmPu_t*         p       = _cmPuHandleToPtr(h);
  int             smpIdx  = 0;
  int             chIdx   = 0;
  const cmReal_t  rmsMax  = 1.0;
  cmReal_t        minRms  = rmsMax;
  cmReal_t        gateMax = 0;
  cmReal_t        gateSum = 0;
  unsigned        gateCnt = 0;
  cmPuCh_t*       chp     = NULL;
  bool            segFl   = false;
  unsigned        segSmpIdx = cmInvalidIdx;

  // create a cmProc context
  if((p->ctxp = cmCtxAlloc(NULL, p->err.rpt, cmLHeapNullHandle, cmSymTblNullHandle )) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Proc context create failed.");
    goto errLabel;
  }

  // create a cmProc audio file reader
  if((p->afrp =  cmAudioFileRdAlloc(p->ctxp, NULL, procSmpCnt, audioFn, chIdx, 0, cmInvalidIdx)) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Audio file reader creation failed on '%s'.",cmStringNullGuard(audioFn));
    goto errLabel;
  }

  // given the sample rate calculate the hop and window size in samples
  unsigned       hopSmpCnt = floor(p->afrp->info.srate * hopMs / 1000);
  unsigned       wndSmpCnt = hopSmpCnt * gd0Args->medCnt;

  // create a shift buffer to maintain the RMS window for the gate detector
  if((p->sbp = cmShiftBufAlloc(p->ctxp,NULL,procSmpCnt,wndSmpCnt,hopSmpCnt )) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Shift buffer create failed.");
    goto errLabel;
  }

  // create a gate detector
  if((p->gdp = cmGateDetectAlloc2(p->ctxp,NULL,procSmpCnt,gd0Args)) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Gate detect create failed.");
    goto errLabel;
  }

  // create an output file to hold the results of the gate detector
  if( (p->mfp = cmBinMtxFileAlloc(p->ctxp,NULL,outMtx0Fn)) == NULL )
  {
    rc = cmErrMsg(&p->err,kProcFailPuRC,"Binary matrix file create failed.");
    goto errLabel;
  }

  // read the label file and create p->chArray
  if((rc = _cmPuReadLabelsAndCreateArray(p, labelFn, p->afrp->info.srate)) != kOkPuRC )
    goto errLabel;

  chp       = p->chArray;
  segSmpIdx = chp->begSmpIdx;

  // for each procSmpCnt samples
  for(; cmAudioFileRdRead(p->afrp) != cmEofRC; smpIdx += procSmpCnt )
  {
    // if this audio frame marks a segment beginning or 
    // the end-of-audio-file will occur on the next frame
    if( smpIdx+procSmpCnt >= p->afrp->info.frameCnt || (smpIdx <= segSmpIdx && segSmpIdx < smpIdx + procSmpCnt) )
    {
      segFl = true;

      // if no ending offset was located then update the gate sum
      if( gateMax != 0 )
      {
        gateSum += gateMax;
        gateCnt += 1;
        gateMax  = 0;
      } 
      
      // calc the avg max RMS value for this segment
      chp->gateMaxAvg = gateCnt == 0 ? 0.0 : gateSum / gateCnt;
      gateCnt = 0;
      gateSum = 0;
      gateMax = 0;

      minRms = rmsMax; // force the segment end to be after the segment beginning

      if((chp = _cmPuIncrCh(p,chp, &segSmpIdx )) == NULL )
        break;
    }

    // shift the new samples into the shift buffer
    while(cmShiftBufExec(p->sbp,p->afrp->outV,p->afrp->outN))
    {
      // update the gate detector
      cmGateDetectExec2(p->gdp,p->sbp->outV,p->sbp->outN);

      // write the output matrix file
      if( _cmPuWriteMtxFile(p,segFl) != kOkPuRC )
        goto errLabel;

      segFl = false;

      // if this frame is an RMS minimum or onset or offset
      // then select it as a possible segment end.
      // Note that for onsets this will effectively force the end to
      // come after the onset because the onset will not be an energy minimum
      // relative to subsequent frames.
      if( p->gdp->rms < minRms || p->gdp->onFl || p->gdp->offFl )
      {
        minRms         = p->gdp->rms;
        chp->endSmpIdx = smpIdx;

        // count onsets
        if( p->gdp->onFl )
          ++chp->onCnt;

        // count offsets
        if( p->gdp->offFl )
        {
          ++chp->offCnt;
          
          // update the gate sum and count
          gateSum += gateMax;
          gateCnt += 1;
          gateMax  = 0;
        }
      }

      // track the max RMS value during this gate
      if( p->gdp->gateFl && p->gdp->rms > gateMax )
        gateMax = p->gdp->rms;
      
    }
  }

  // calculate the channel gains
  if( rc == kOkPuRC )
    _cmPuCalcGains(p);

  rc = _cmPuCalcRerunGateDetectors(p,outMtx1Fn,outAudFn,gd1Args,procSmpCnt,wndSmpCnt,hopSmpCnt);

  p->gd0Args = *gd0Args;
  p->gd1Args = *gd1Args;

 errLabel:
  if( p->mfp != NULL )
    cmBinMtxFileFree(&p->mfp);

  if( p->gdp != NULL )
    cmGateDetectFree2(&p->gdp);

  if( p->sbp != NULL )
    cmShiftBufFree(&p->sbp);
  
  if( p->afrp != NULL )
    cmAudioFileRdFree(&p->afrp);

  if( p->ctxp != NULL )
    cmCtxFree(&p->ctxp);

  return rc;
}

cmPuRC_t cmPuAutoGainExec( cmPuH_t h, const cmChar_t* fileDir, unsigned procSmpCnt )
{
  cmPu_t*         p         = _cmPuHandleToPtr(h);
  const cmChar_t* inAudFn   = cmFsMakeFn(fileDir, p->inAudFn,   NULL, NULL );
  const cmChar_t* inLabelFn = cmFsMakeFn(fileDir, p->inLabelFn, NULL, NULL );
  const cmChar_t* outMtx0Fn = cmFsMakeFn(fileDir, p->outMtx0Fn, NULL, NULL );
  const cmChar_t* outMtx1Fn = cmFsMakeFn(fileDir, p->outMtx1Fn, NULL, NULL );
  const cmChar_t* outAudFn  = cmFsMakeFn(fileDir, p->outAudFn,  NULL, NULL );

  cmPuRC_t rc = cmPuAutoGainCfg(h,inAudFn,inLabelFn,outMtx0Fn,outMtx1Fn,outAudFn,procSmpCnt,p->hopMs,&p->gd0Args,&p->gd1Args);

  cmFsFreeFn(outAudFn);
  cmFsFreeFn(outMtx1Fn);
  cmFsFreeFn(outMtx0Fn);
  cmFsFreeFn(inLabelFn);
  cmFsFreeFn(inAudFn);

  return rc;
}

cmPuRC_t cmPuAutoGainCfgFromJson( 
  cmPuH_t             h,
  const cmChar_t*     cfgDir,
  cmJsonH_t           jsH,
  cmJsonNode_t*       onp,
  unsigned            procSmpCnt )
{
  cmPuRC_t rc;
  if((rc = cmPuReadJson(h,jsH,onp)) != kOkPuRC )
    return rc;
  

  return cmPuAutoGainExec(h,cfgDir,procSmpCnt);
}


void cmPuReport( cmPuH_t h, cmRpt_t* rpt )
{
  cmPu_t*         p       = _cmPuHandleToPtr(h);
  
  unsigned i;
  for(i=0; i<p->chCnt; ++i)
  {
    const cmPuCh_t* chp = p->chArray + i;
    cmRptPrintf(rpt,"beg:%i end:%i on:%i off:%i max:%f gain:%f\n",
      chp->begSmpIdx, chp->endSmpIdx, chp->onCnt, chp->offCnt, chp->gateMaxAvg, chp->gain );
  }
}

cmPuRC_t _cmPuJsonGainRead( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label )
{
  cmPuRC_t rc = kOkPuRC;
  cmJsonNode_t* arp;
  unsigned arrCnt = 0;
  cmPuCh_t* arr = NULL;

  // locate the JSON 'gain' array
  if(( arp = cmJsonFindValue(jsH,label,onp,kArrayTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON array node %s.",cmStringNullGuard(label));
    goto errLabel;
  }

  // get the count of elements in the 'gain' array
  arrCnt = cmJsonChildCount(arp);

  if( arrCnt > 0 )
  {
    arr = cmMemAllocZ(cmPuCh_t,arrCnt);

    unsigned i;
    for(i=0; i<arrCnt; ++i)
    {
      if( i<p->chCnt )
        arr[i] = p->chArray[i];

      if( cmJsonRealValue( cmJsonArrayElement(arp,i), &arr[i].gain ) != kOkJsRC )
      {
        rc = cmErrMsg(&p->err,kJsonFailPuRC,"An error occurred while accessing a JSON 'gain' element.");
        goto errLabel;
      }

    }
  }


 errLabel:

  if( rc != kOkPuRC )
  {
    cmMemPtrFree(&arr);
    arrCnt = 0;
  }

  cmMemPtrFree(&p->chArray);
  p->chArray = arr;
  p->chCnt   = arrCnt;

  return rc;
}

cmPuRC_t _cmPuJsonGdParmsRead( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label, cmGateDetectParams* gdParms )
{
  cmPuRC_t rc = kOkPuRC;
  cmJsonNode_t* gdp;
  const char* errLabelPtr;
  if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label));
    goto errLabel;
  }

  if( cmJsonMemberValues(gdp, &errLabelPtr, 
      "medCnt",      kIntTId,  &gdParms->medCnt,
      "avgCnt",      kIntTId,  &gdParms->avgCnt,
      "suprCnt",     kIntTId,  &gdParms->suprCnt,
      "offCnt",      kIntTId,  &gdParms->offCnt,
      "suprCoeff",   kRealTId, &gdParms->suprCoeff,
      "onThreshDb",  kRealTId, &gdParms->onThreshDb,
      "offThreshDb", kRealTId, &gdParms->offThreshDb,
      NULL ) != kOkJsRC )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Gate detect parameter restore failed for '%s'.",cmStringNullGuard(label));
    goto errLabel;
  }

 errLabel:
  return rc;
}

cmPuRC_t _cmPuJsonCfgParmsRead(cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp)
{
  cmPuRC_t      rc = kOkPuRC;
  cmJsonNode_t* gdp = onp;
  const char*   errLabelPtr;

 
  //if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL )
  //{
  //  rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label));
  //  goto errLabel;
  // }

  if( cmJsonMemberValues(gdp, &errLabelPtr, 
      "audioFn",   kStringTId, &p->inAudFn,
      "labelFn", kStringTId, &p->inLabelFn,
      "outMtx0Fn", kStringTId, &p->outMtx0Fn,
      "outMtx1Fn", kStringTId, &p->outMtx1Fn,
      "outAudFn",  kStringTId, &p->outAudFn,
      "hopMs",     kRealTId,   &p->hopMs,
      NULL ) != kOkJsRC )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Autotune cfg parameter read failed.");
    goto errLabel;
  }

 errLabel:
  return rc;

}

cmPuRC_t cmPuReadJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp )
{
  cmPuRC_t      rc = kOkPuRC;
  cmPu_t*       p  = _cmPuHandleToPtr(h);
  cmJsonNode_t* atp;

  if(( atp = cmJsonFindValue(jsH,"cfg",onp,kObjectTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"The JSON 'autotune' object was not found.");
    goto errLabel;
  }

  if((rc = _cmPuJsonCfgParmsRead(p,jsH,atp)) != kOkPuRC )
    goto errLabel;

  if((rc = _cmPuJsonGdParmsRead(p,jsH,atp,"gdParms0",&p->gd0Args)) != kOkPuRC )
    goto errLabel;

  if((rc = _cmPuJsonGdParmsRead(p,jsH,atp,"gdParms1",&p->gd1Args)) != kOkPuRC )
    goto errLabel;

  if((rc = _cmPuJsonGainRead(p,jsH,atp,"gain")) != kOkPuRC )
    goto errLabel;

 errLabel:
  return rc;

}

unsigned        cmPuChannelCount(   cmPuH_t h )
{ 
  cmPu_t* p = _cmPuHandleToPtr(h);
  return p->chCnt;
}

const cmPuCh_t* cmPuChannel( cmPuH_t h, unsigned chIdx )
{
  cmPu_t* p = _cmPuHandleToPtr(h);
  assert( chIdx < p->chCnt );
  return p->chArray + chIdx;
}
    


cmPuRC_t _cmPuJsonSetInt( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* gdp, const cmChar_t* label, int val )
{
  cmPuRC_t rc = kOkPuRC;

  if( cmJsonReplacePairInt(jsH, gdp, label, kIntTId | kRealTId, val ) != kOkJsRC )
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Error setting integer JSON field: %s.",cmStringNullGuard(label));
  return rc;
}

cmPuRC_t _cmPuJsonSetReal( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* gdp, const cmChar_t* label, cmReal_t val )
{
  cmPuRC_t rc = kOkPuRC;

  if( cmJsonReplacePairReal(jsH, gdp, label, kIntTId | kRealTId, val ) != kOkJsRC )
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Error setting real JSON field: %s.",cmStringNullGuard(label));
  return rc;
}

cmPuRC_t _cmPuJsonGdParmsUpdate( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label, const cmGateDetectParams* gdParms )
{
  cmPuRC_t rc = kOkPuRC;
  cmJsonNode_t* gdp;

  if(( gdp = cmJsonFindValue(jsH,label,onp,kObjectTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON object node %s.",cmStringNullGuard(label));
    goto errLabel;
  }

  _cmPuJsonSetInt(  p, jsH, gdp, "medCnt",     gdParms->medCnt);
  _cmPuJsonSetInt(  p, jsH, gdp, "avgCnt",     gdParms->avgCnt);
  _cmPuJsonSetInt(  p, jsH, gdp, "suprCnt",    gdParms->suprCnt);
  _cmPuJsonSetInt(  p, jsH, gdp, "offCnt",     gdParms->offCnt);
  _cmPuJsonSetReal( p, jsH, gdp, "suprCoeff",  gdParms->suprCoeff);
  _cmPuJsonSetReal( p, jsH, gdp, "onThreshDb", gdParms->onThreshDb);
  _cmPuJsonSetReal( p, jsH, gdp, "offThresDb", gdParms->offThreshDb);

  rc = cmErrLastRC(&p->err);
 errLabel:
  return rc;

}

cmPuRC_t _cmPuJsonGainUpdate( cmPu_t* p, cmJsonH_t jsH, cmJsonNode_t* onp, const cmChar_t* label )
{
  cmPuRC_t rc = kOkPuRC;
  cmJsonNode_t* arp;
  unsigned i;

  // locate the JSON 'gain' array
  if(( arp = cmJsonFindValue(jsH,label,onp,kArrayTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"Unable to locate the JSON array node %s.",cmStringNullGuard(label));
    goto errLabel;
  }

  // get the count of elements in the 'gain' array
  unsigned arrCnt = cmJsonChildCount(arp);

  // update the existing 'gain' array elmements from p->chArray[]
  for(i=0; i<arrCnt && i<p->chCnt; ++i)
  {
    if(cmJsonSetReal( jsH, cmJsonArrayElement(arp,i), p->chArray[i].gain ) != kOkPuRC )
    {
      rc = cmErrMsg(&p->err,kJsonFailPuRC,"Set JSON 'gain' array elment failed.");
      goto errLabel;
    }
  }    

  // create new elements if the array was not long enough
  for(; i<p->chCnt; ++i)
    if( cmJsonCreateReal(jsH,arp,p->chArray[i].gain) != kOkJsRC )
    {
      rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON 'gain' element create failed.");
      goto errLabel;
    }

  // remove elements if the array begain with extra elements.
  if( arrCnt > p->chCnt )
  {
    if( cmJsonRemoveNode( jsH, cmJsonArrayElement(arp,i), true ) != kOkJsRC )
    {
      rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON 'gain' element removal failed.");
      goto errLabel;
    }
  }

 errLabel:
  return rc;
  
}

cmPuRC_t cmPuWriteJson( cmPuH_t h, cmJsonH_t jsH, cmJsonNode_t* onp )
{
  cmPuRC_t      rc = kOkPuRC;
  cmPu_t*       p  = _cmPuHandleToPtr(h);
  cmJsonNode_t* atp;

  if(( atp = cmJsonFindValue(jsH,"autoTune",onp,kObjectTId)) == NULL )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"The JSON 'autotune' object was not found.");
    goto errLabel;
  }

  if((rc = _cmPuJsonGdParmsUpdate(p,jsH,atp,"gdParms0",&p->gd0Args)) != kOkPuRC )
    goto errLabel;

  if((rc = _cmPuJsonGdParmsUpdate(p,jsH,atp,"gdParms1",&p->gd1Args)) != kOkPuRC )
    goto errLabel;

  if((rc = _cmPuJsonGainUpdate(p,jsH,atp,"gain")) != kOkPuRC )
    goto errLabel;
    
 errLabel:

  return rc;
}

cmPuRC_t cmPuWriteJsonFile( cmPuH_t h, const cmChar_t* jsonFn  )
{
  cmPuRC_t  rc  = kOkPuRC;
  cmJsonH_t jsH = cmJsonNullHandle;
  cmPu_t*   p   = _cmPuHandleToPtr(h);

  // initialize a JSON tree from 'jsonFn'.
  if( cmJsonInitializeFromFile(&jsH,jsonFn,&p->ctx) != kOkJsRC )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file initialization failed on '%s'.",cmStringNullGuard(jsonFn));
    goto errLabel;
  }

  // update the 'autoTune' object in the JSON tree
  if((rc = cmPuWriteJson(h,jsH,cmJsonRoot(jsH))) != kOkPuRC )
    goto errLabel;

  // write the JSON tree back to 'jsonFn'.
  if( cmJsonWrite(jsH,cmJsonRoot(jsH),jsonFn) != kOkJsRC )
  {
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file save failed on '%s'.",cmStringNullGuard(jsonFn));
    goto errLabel;
  }

 errLabel:
  // release the JSON tree
  if( cmJsonFinalize(&jsH) != kOkJsRC )
    rc = cmErrMsg(&p->err,kJsonFailPuRC,"JSON file finalization failed on '%s'.",cmStringNullGuard(jsonFn));

  return rc;
}

void cmPuTest(cmCtx_t* ctx)
{
  cmPuH_t            h          = cmPuNullHandle;
  cmGateDetectParams gd0Args;
  cmGateDetectParams gd1Args;
  const cmChar_t*    audioFn    = "/home/kevin/media/audio/gate_detect/gate_detect0.aif";
  const cmChar_t*    labelFn    = "/home/kevin/media/audio/gate_detect/gate_detect0_labels.txt";
  const cmChar_t*    outMtx0Fn  = "/home/kevin/media/audio/gate_detect/gd0.mtx";
  const cmChar_t*    outMtx1Fn  = "/home/kevin/media/audio/gate_detect/gd1.mtx";
  const cmChar_t*    outAudFn   = "/home/kevin/media/audio/gate_detect/gd_gain0.aif";
  const cmChar_t*    jsonFn     = "/home/kevin/src/kc/src/data/rsrc1.txt";
  unsigned           procSmpCnt = 64;
  cmReal_t           hopMs      = 10;

  gd0Args.medCnt      = 5;
  gd0Args.avgCnt      = 9;
  gd0Args.suprCnt     = 6;
  gd0Args.offCnt      = 3;
  gd0Args.suprCoeff   = 1.4;
  gd0Args.onThreshDb  = -53.0;
  gd0Args.offThreshDb = -80.0;

  gd1Args = gd0Args;
  gd1Args.onThreshDb = -45;

  if( cmPuAlloc(ctx, &h ) != kOkPuRC )
    goto errLabel;

  if( cmPuAutoGainCfg(h,audioFn,labelFn,outMtx0Fn,outMtx1Fn,outAudFn,procSmpCnt,hopMs,&gd0Args,&gd1Args) == kOkPuRC )
  {
    cmPuReport(h,&ctx->rpt);

    cmPuWriteJsonFile( h, jsonFn  );
  }
 errLabel:
   cmPuFree(&h);
 
   
}