//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwText.h"
#include "cwObject.h"
#include "cwMidi.h"
#include "cwFileSys.h"
#include "cwDynRefTbl.h"
#include "cwScoreParse.h"
#include "cwSfScore.h"
#include "cwSfMatch.h"

namespace cw
{
  namespace sfmatch
  {
    
    sfmatch_t* _handleToPtr( handle_t h )
    { return handleToPtr<handle_t,sfmatch_t>(h); }

    rc_t _destroy( sfmatch_t* p )
    {
      rc_t rc = kOkRC;

      unsigned i;
      for(i=0; i<p->locN; ++i)
        mem::release(p->loc[i].evtV);
        
      mem::release(p->loc);
      mem::release(p->m);
      mem::release(p->p_mem);
        
      mem::release(p);
      return rc;
    };

    void _cmScMatchInitLoc( sfmatch_t* p )
    {
      unsigned li,ei;

      p->locN = event_count(p->scH);
      p->loc  = mem::resizeZ<loc_t>(p->loc,p->locN);


      // for each score location  
      for(li=0,ei=0; li<loc_count(p->scH); ++li)
      {
        unsigned i,n;

        const sfscore::loc_t* lp = loc_base(p->scH) + li;

        // count the number of note events at location li
        for(n=0,i=0; i<lp->evtCnt; ++i)
          if( lp->evtArray[i]->type == score_parse::kNoteOnTId )
            ++n;

        assert( ei+n <= p->locN );

        // duplicate each note at location li n times
        for(i=0; i<n; ++i)
        {
          unsigned j,k;

          p->loc[ei+i].evtCnt   = n;
          p->loc[ei+i].evtV     = mem::allocZ<event_t>(n);
          p->loc[ei+i].scLocIdx = li;
          p->loc[ei+i].barNumb  = lp->barNumb;

          for(j=0,k=0; j<lp->evtCnt; ++j)
            if( lp->evtArray[j]->type == score_parse::kNoteOnTId )
            {
              p->loc[ei+i].evtV[k].pitch    = lp->evtArray[j]->pitch;
              p->loc[ei+i].evtV[k].scEvtIdx = lp->evtArray[j]->index;
              ++k;
            }
        }

        ei += n;
      }

      assert(ei<=p->locN);
      p->locN = ei;
    }

    rc_t  _cmScMatchInitMtx( sfmatch_t* p, unsigned rn, unsigned cn )
    {
      //if( rn >p->mrn && cn > p->mcn )
      if( rn*cn > p->mrn*p->mcn )
      {
        return cwLogError(kInvalidArgRC, "MIDI sequence length must be less than %i. Score sequence length must be less than %i.",p->mmn,p->msn); 
      }

      // if the size of the mtx is not changing then there is nothing to do
      if( rn == p->rn && cn == p->cn )
        return kOkRC;

      // update the mtx size
      p->rn = rn;
      p->cn = cn;

      // fill in the default values for the first row
      // and column of the DP matrix
      unsigned i,j,k;
      for(i=0; i<rn; ++i)
        for(j=0; j<cn; ++j)
        {
          unsigned v[] = {0,0,0,0};

          if( i == 0 )
          {
            v[kSmMinIdx] = j;
            v[kSmInsIdx] = j;
          }
          else
            if( j == 0 )
            {
              v[kSmMinIdx] = i;
              v[kSmDelIdx] = i;
            }

          // zero the value field
          for(k=0; k<kSmCnt; ++k)
            p->m[ i + (j*rn) ].v[k] = v[k];      
        }

      return kOkRC;
    }

    value_t* _cmScMatchValPtr( sfmatch_t* p, unsigned i, unsigned j, unsigned rn, unsigned cn )
    {
      assert( i < rn && j < cn );

      return p->m + i + (j*rn);
    }

    bool _cmScMatchIsTrans( sfmatch_t* p, const midi_t* midiV, const value_t* v1p, unsigned bsi, unsigned i, unsigned j, unsigned rn, unsigned cn )
    {
      bool     fl  = false;
      value_t* v0p = _cmScMatchValPtr(p,i,j,rn,cn);

      if( i>=1 && j>=1
          && v1p->v[kSmMinIdx] == v1p->v[kSmSubIdx] 
          && cwIsNotFlag(v1p->flags,kSmMatchFl) 
          && v0p->v[kSmMinIdx] == v0p->v[kSmSubIdx]
          && cwIsNotFlag(v0p->flags,kSmMatchFl) 
          )
      {
        unsigned        c00 = midiV[i-1].pitch;
        unsigned        c01 = midiV[i  ].pitch;
        loc_t* c10 = p->loc + bsi + j - 1;
        loc_t* c11 = p->loc + bsi + j;
        fl = is_match(c11,c00) && is_match(c10,c01);
      }
      return fl;
    }

    unsigned _cmScMatchMin( sfmatch_t* p, unsigned i, unsigned j, unsigned rn, unsigned cn )
    { 
      return _cmScMatchValPtr(p,i,j,rn,cn)->v[kSmMinIdx];
    }

    // Return false if bsi + cn > p->locN
    // pitchV[rn-1]
    bool  _cmScMatchCalcMtx( sfmatch_t* p, unsigned bsi, const midi_t* midiV, unsigned rn, unsigned cn )
    {
      // loc[begScanLocIdx:begScanLocIdx+cn-1] must be valid
      if( bsi + cn > p->locN )
        return false;

      unsigned i,j;

      for(j=1; j<cn; ++j)
        for(i=1; i<rn; ++i)
        {
          loc_t*   loc      = p->loc + bsi + j - 1;
          unsigned pitch    = midiV[i-1].pitch;
          value_t* vp       = _cmScMatchValPtr(p,i,j,rn,cn);
          unsigned idx      = match_index(loc,pitch);
          vp->flags         = idx==kInvalidIdx ? 0            : kSmMatchFl;
          vp->scEvtIdx      = idx==kInvalidIdx ? kInvalidIdx : loc->evtV[idx].scEvtIdx;
          unsigned cost     = cwIsFlag(vp->flags,kSmMatchFl) ? 0 : 1;
          vp->v[kSmSubIdx]  = _cmScMatchMin(p,i-1,j-1, rn, cn) + cost;
          vp->v[kSmDelIdx]  = _cmScMatchMin(p,i-1,j  , rn, cn) + 1;
          vp->v[kSmInsIdx]  = _cmScMatchMin(p,i,  j-1, rn, cn) + 1;
          vp->v[kSmMinIdx]  = std::min( vp->v[kSmSubIdx], std::min(vp->v[kSmDelIdx],vp->v[kSmInsIdx]));
          vp->flags        |= _cmScMatchIsTrans(p,midiV,vp,bsi,i-1,j-1,rn,cn) ? kSmTransFl : 0;
        }

      return true;
    }

    void _cmScMatchPrintMtx( sfmatch_t* r, unsigned rn, unsigned cn)
    {
      unsigned i,j,k;
      for(i=0; i<rn; ++i)
      {
        for(j=0; j<cn; ++j)
        {
          printf("(");
      
          const value_t* vp = _cmScMatchValPtr(r,i,j,rn,cn);

          for(k=0; k<kSmCnt; ++k)
          {
            printf("%i",vp->v[k]);
            if( k<kSmCnt-1)
              printf(", ");
            else
              printf(" ");
          }

          printf("%c%c)",cwIsFlag(vp->flags,kSmMatchFl)?'m':' ',cwIsFlag(vp->flags,kSmTransFl)?'t':' ');
      
        }
        printf("\n");
      }
    }

    void _cmScMatchPathPush( sfmatch_t* r, unsigned code, unsigned ri, unsigned ci, unsigned flags, unsigned scEvtIdx )
    {
      assert(r->p_avl != NULL );

      path_t* p = r->p_avl;
      r->p_avl = r->p_avl->next;

      p->code    = code;
      p->ri      = ri;
      p->ci      = ci;
      p->flags   = code==kSmSubIdx && cwIsFlag(flags,kSmMatchFl) ? kSmMatchFl  : 0;
      p->flags  |= cwIsFlag(flags,kSmTransFl) ? kSmTransFl : 0;
      p->scEvtIdx= scEvtIdx;
      p->next    = r->p_cur;  
      r->p_cur   = p;
    }

    void _cmScMatchPathPop( sfmatch_t* r )
    {
      assert( r->p_cur != NULL );
      path_t* tp    = r->p_cur->next;
      r->p_cur->next = r->p_avl;
      r->p_avl       = r->p_cur;
      r->p_cur       = tp;
    }


    double _cmScMatchCalcCandidateCost( sfmatch_t* r )
    {
      path_t* cp = r->p_cur;
      path_t* bp = r->p_cur;
      path_t* ep = NULL;

      // skip leading inserts
      for(; cp!=NULL; cp=cp->next)
        if( cp->code != kSmInsIdx )
        {
          bp = cp;
          break;
        }
  
      // skip to trailing inserts
      for(; cp!=NULL; cp=cp->next)
        if( cp->code!=kSmInsIdx )
          ep = cp;

      // count remaining path length
      assert( ep!=NULL );
      unsigned n=1;
      for(cp=bp; cp!=ep; cp=cp->next)
        ++n;

      double   gapCnt  = 0;
      double   penalty = 0;
      bool     pfl     = cwIsFlag(bp->flags,kSmMatchFl);
      unsigned i;

      cp = bp;
      for(i=0; i<n; ++i,cp=cp->next)
      {
        // a gap is a transition from a matching subst. to an insert or deletion
        //if( pc != cp->code && cp->code != kSmSubIdx && pc==kSmSubIdx && pfl==true )
        if( pfl==true && cwIsFlag(cp->flags,kSmMatchFl)==false )
          ++gapCnt;

        //
        switch( cp->code )
        {
          case kSmSubIdx:
            penalty += cwIsFlag(cp->flags,kSmMatchFl) ? 0 : 1;
            penalty -= cwIsFlag(cp->flags,kSmTransFl) ? 1 : 0;
            break;

          case kSmDelIdx:
            penalty += 1;
            break;

          case kSmInsIdx:
            penalty += 1;
            break;
        }

        pfl = cwIsFlag(cp->flags,kSmMatchFl);
    
      }

      double cost = gapCnt/n + penalty;

      //printf("n:%i gaps:%f gap_score:%f penalty:%f score:%f\n",n,gapCnt,gapCnt/n,penalty,score);

      return cost;

    }

    double _cmScMatchEvalCandidate( sfmatch_t* r, double min_cost, double cost )
    {
  
      if( min_cost == DBL_MAX || cost < min_cost)
      {
        // copy the p_cur to p_opt[]
        path_t* cp = r->p_cur;
        unsigned         i;

        for(i=0; cp!=NULL && i<r->pn; cp=cp->next,++i)
        {
          r->p_opt[i].code    = cp->code;
          r->p_opt[i].ri      = cp->ri;
          r->p_opt[i].ci      = cp->ci;
          r->p_opt[i].flags   = cp->flags;
          r->p_opt[i].scEvtIdx= cp->scEvtIdx;
          r->p_opt[i].next    = cp->next==NULL ? NULL : r->p_opt + i + 1;
        }
    
        assert( i < r->pn );
        r->p_opt[i].code = 0; // terminate with code=0        
        min_cost         = cost;
      }

      return min_cost;
    }


    // NOTE: IF THE COST CALCULATION WAS BUILT INTO THE RECURSION THEN 
    // THIS FUNCTION COULD BE MADE MORE EFFICIENT BECAUSE PATHS WHICH
    // EXCEEDED THE min_cost COULD BE SHORT CIRCUITED.
    // 
    // traverse the solution matrix from the lower-right to 
    // the upper-left.
    double _cmScMatchGenPaths( sfmatch_t* r, int i, int j, unsigned rn, unsigned cn, double min_cost )
    {
      unsigned m;

      // stop when the upper-right is encountered
      if( i==0 && j==0 )
        return _cmScMatchEvalCandidate(r, min_cost, _cmScMatchCalcCandidateCost(r) );

      value_t* vp = _cmScMatchValPtr(r,i,j,rn,cn);

      // for each possible dir: up,left,up-left
      for(m=1; m<kSmCnt; ++m)
        if( vp->v[m] == vp->v[kSmMinIdx] )
        {
          // prepend to the current candidate path: r->p_cur
          _cmScMatchPathPush(r,m,i,j,vp->flags,vp->scEvtIdx);

          int ii = i-1;
          int jj = j-1;

          switch(m)
          {
            case kSmSubIdx:
              break;

            case kSmDelIdx:
              jj = j;
              break;

            case kSmInsIdx:
              ii = i;
              break;

            default:
              { assert(0); }
          }

          // recurse!
          min_cost = _cmScMatchGenPaths(r,ii,jj,rn,cn,min_cost);

          // remove the first element from the current path
          _cmScMatchPathPop(r);
        }

      return min_cost;
  
    }

    double _cmScMatchAlign( sfmatch_t* p, unsigned rn, unsigned cn, double min_cost )
    {
      int      i = rn-1;
      int      j = cn-1;
      unsigned m = _cmScMatchMin(p,i,j,rn,cn);

      if( m==std::max(rn,cn) )
        printf("Edit distance is at max: %i. No Match.\n",m);
      else
        min_cost = _cmScMatchGenPaths(p,i,j,rn,cn,min_cost);

      return min_cost;
    }

    void _cmScMatchMidiEvtFlags( sfmatch_t* p, const loc_t* lp, unsigned evtIdx, char* s, unsigned sn )
    {
      const sfscore::loc_t* slp = sfscore::loc_base(p->scH) + lp->scLocIdx;

      assert( evtIdx < slp->evtCnt );

      const sfscore::event_t* ep = slp->evtArray[evtIdx];
      unsigned            i  = 0;

      s[0] = 0;

      if( cwIsFlag(ep->flags,score_parse::kEvenVarFl) )
        s[i++] = 'e';

      if( cwIsFlag(ep->flags,score_parse::kTempoVarFl) )
        s[i++] = 't';

      if( cwIsFlag(ep->flags,score_parse::kDynVarFl) )
        s[i++] = 'd';

      //if( cwIsFlag(ep->flags,sfscore::kGraceScFl) )
      //  s[i++] = 'g';

      s[i++] = 0;

      assert( i <= sn );
  
    }


    void _gen_match_test_input_from_score( sfscore::handle_t scoreH, unsigned begLocIdx, unsigned locCnt, double srate )
    {
      printf("[\n");
      for(unsigned i=begLocIdx, k=0; i<begLocIdx+locCnt; ++i)
      {
        assert( begLocIdx < sfscore::loc_count(scoreH) );
        
        const sfscore::loc_t* loc = sfscore::loc_base(scoreH) + i;
        
        for(unsigned j=0; j<loc->evtCnt; ++j)
        {
          const sfscore::event_t* e = loc->evtArray[j];
          unsigned smpIdx = e->secs * srate;
          printf("{ muid:%i smpIdx:%i pitch:%i vel:%i },\n",k,smpIdx,e->pitch,e->vel);
          ++k;
        }
        
      }
      printf("]\n");

    }    
  }
}

cw::rc_t cw::sfmatch::create( handle_t& hRef, sfscore::handle_t scoreH, unsigned maxScWndN, unsigned maxMidiWndN )
{
  rc_t rc = kOkRC;
  sfmatch_t* p = nullptr;

  if((rc = destroy(hRef)) != kOkRC )
    return rc;

  p = mem::allocZ<sfmatch_t>();
  
  p->scH  = scoreH;
  p->mrn  = maxMidiWndN + 1;
  p->mcn  = maxScWndN   + 1;
  p->mmn  = maxMidiWndN;
  p->msn  = maxScWndN;

  _cmScMatchInitLoc(p);

  p->m     = mem::resizeZ<value_t>( p->m, p->mrn*p->mcn );
  p->pn    = p->mrn + p->mcn;
  p->p_mem = mem::resizeZ<path_t>( p->p_mem, 2*p->pn );
  p->p_avl = p->p_mem;
  p->p_cur = NULL;
  p->p_opt = p->p_mem + p->pn;

  // put pn path records on the available list
  for(unsigned i=0; i<p->pn; ++i)
  {
    p->p_mem[i].next = i<p->pn-1 ? p->p_mem + i + 1 : NULL;
    p->p_opt[i].next = i<p->pn-1 ? p->p_opt + i + 1 : NULL;
  }

  hRef.set(p);
  
  return rc;  
}

cw::rc_t cw::sfmatch::destroy( handle_t& hRef  )
{
  rc_t rc = kOkRC;
  if(!hRef.isValid())
    return rc;

  sfmatch_t* p = _handleToPtr(hRef);

  if((rc = _destroy(p)) != kOkRC )
    goto errLabel;

 errLabel:
  return rc;
}

cw::rc_t cw::sfmatch::exec(  handle_t h, unsigned oLocId, unsigned locN, const midi_t* midiV, unsigned midiN, double min_cost )
{
  rc_t   rc;
  unsigned rn = midiN + 1;
  unsigned cn = locN  + 1;
  sfmatch_t* p = _handleToPtr(h);

  // set the DP matrix default values
  if((rc = _cmScMatchInitMtx(p, rn, cn )) != kOkRC )
    return rc;

  // _cmScMatchCalcMtx() returns false if the score window exceeds the length of the score
  if(!_cmScMatchCalcMtx(p,oLocId, midiV, rn, cn) )
    return kEofRC;

  //_cmScMatchPrintMtx(p,rn,cn);
  
  // locate the path through the DP matrix with the lowest edit distance (cost)
  p->opt_cost =  _cmScMatchAlign(p, rn, cn, min_cost);

  return rc;
}

unsigned cw::sfmatch::sync( handle_t h, unsigned i_opt, midi_t* midiBuf, unsigned midiN, unsigned* missCntPtr )
{
  sfmatch_t* p       = _handleToPtr(h);
  path_t*    cp      = p->p_opt;
  unsigned   missCnt = 0;
  unsigned   esi     = kInvalidIdx;
  unsigned   i;
  
  for(i=0; cp!=NULL; cp=cp->next)
  {
    // there is no MIDI note associated with 'inserts'
    if( cp->code != kSmInsIdx )
    {
      assert( cp->ri > 0 );
      midiBuf[ cp->ri-1 ].oLocId = kInvalidIdx;
    }

    switch( cp->code )
    {
      case kSmSubIdx:
        midiBuf[ cp->ri-1 ].oLocId   = i_opt + i;
        midiBuf[ cp->ri-1 ].scEvtIdx = cp->scEvtIdx;

        if( cwIsFlag(cp->flags,kSmMatchFl) )
        {
          esi     = i_opt + i;
          missCnt = 0;
        }
        else
        {
          ++missCnt;
        }
        // fall through

      case kSmInsIdx:
        cp->oLocId = i_opt + i;
        ++i;
        break;

      case kSmDelIdx:
        cp->oLocId = kInvalidIdx;
        ++missCnt;
        break;
    }
  }

  if( missCntPtr != NULL )
    *missCntPtr = missCnt;

  return esi;
}

void cw::sfmatch::print_path( handle_t h, unsigned bsi, const midi_t* midiV )
{
  assert( bsi != kInvalidIdx );

  sfmatch_t* p     = _handleToPtr(h);
  path_t*    cp    = p->p_opt;
  path_t*    pp    = cp;
  int        polyN = 0;
  int        i;

  printf("loc: ");

  // get the polyphony count for the score window 
  for(i=0; pp!=NULL; pp=pp->next)
  {
    loc_t* lp = p->loc + bsi + pp->ci;
    if( pp->code!=kSmDelIdx  )
    {
      if(lp->evtCnt > (unsigned)polyN)
        polyN = lp->evtCnt;

      printf("%4i%4s ",bsi+i," ");
      ++i;
    }
    else
      printf("%4s%4s "," "," ");
  }

  printf("\n");

  // print the score notes
  for(i=polyN; i>0; --i)
  {
    printf("%3i: ",i);
    for(pp=cp; pp!=NULL; pp=pp->next)
    {

      if( pp->code!=kSmDelIdx )
      {
        int oLocId = bsi + pp->ci - 1;
        assert(0 <= oLocId && oLocId <= (int)p->locN);
        loc_t* lp = p->loc + oLocId;

        if( lp->evtCnt >= (unsigned)
            i )
        {
          unsigned sn = 6;
          char s[sn];
          _cmScMatchMidiEvtFlags(p,lp,i-1,s,sn );
          printf("%4s%-4s ",midi::midiToSciPitch(lp->evtV[i-1].pitch,NULL,0),s);
        }
        else
          printf("%4s%4s "," "," ");
      }
      else
        printf("%4s%4s ", (pp->code==kSmDelIdx? "-" : " ")," ");

      /*
        int oLocId = bsi + pp->ci - 1;
        assert(0 <= oLocId && oLocId <= p->locN);
        loc_t* lp = p->loc + oLocId;
        if( pp->code!=kSmDelIdx && lp->evtCnt >= i )
        printf("%4s ",cmMidiToSciPitch(lp->evtV[i-1].pitch,NULL,0));
        else
        printf("%4s ", pp->code==kSmDelIdx? "-" : " ");
      */
    }
    printf("\n");
  }

  printf("mid: ");

  // print the MIDI buffer
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->code!=kSmInsIdx )
      printf("%4s%4s ",midi::midiToSciPitch(midiV[pp->ri-1].pitch,NULL,0)," ");
    else
      printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," ");
  }

  printf("\nvel: ");

  // print the MIDI velocity
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->code!=kSmInsIdx )
      printf("%4i%4s ",midiV[pp->ri-1].vel," ");
    else
      printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," ");
  }

  printf("\nmni: ");

  // print the MIDI buffer index (mni)
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->code!=kSmInsIdx )
      printf("%4i%4s ",midiV[pp->ri-1].mni," ");
    else
      printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," ");
  }

  printf("\n op: ");

  // print the substitute/insert/delete operation
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    char c = ' ';
    switch( pp->code )
    {
      case kSmSubIdx: c = 's'; break;
      case kSmDelIdx: c = 'd'; break;
      case kSmInsIdx: c = 'i'; break;
      default:
        { assert(0); }
    }

    printf("%4c%4s ",c," ");
  }

  printf("\n     ");

  // give substitute attribute (match or transpose)
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    char s[3];
    int  k = 0;
    if( cwIsFlag(pp->flags,kSmMatchFl) )
      s[k++] = 'm';

    if( cwIsFlag(pp->flags,kSmTransFl) )
      s[k++] = 't';

    s[k]   = 0;

    printf("%4s%4s ",s," ");
  }

  printf("\nscl: ");

  // print the stored location index
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->oLocId == kInvalidIdx )
      printf("%4s%4s "," "," ");
    else
      printf("%4i%4s ",p->loc[pp->oLocId].scLocIdx," ");
  }
  
  printf("\nbar: ");

  // print the stored location index
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->oLocId==kInvalidIdx || pp->scEvtIdx==kInvalidIdx )
      printf("%4s%4s "," "," ");
    else
    {
      const sfscore::event_t* ep = sfscore::event(p->scH, pp->scEvtIdx );
      printf("%4i%4s ",ep->barNumb," ");
    }
  }


  printf("\nsec: ");

  // print seconds
  unsigned begSmpIdx = kInvalidIdx;
  for(pp=cp; pp!=NULL; pp=pp->next)
  {
    if( pp->code!=kSmInsIdx )
    {
      if( begSmpIdx == kInvalidIdx )
        begSmpIdx = midiV[pp->ri-1].smpIdx;

      printf("%2.2f%4s ", (double)(midiV[pp->ri-1].smpIdx - begSmpIdx)/96000.0," ");
      
    }
    else
      printf("%4s%4s ",pp->code==kSmInsIdx?"-":" "," ");


  }

  
  printf("\n\n");

}

unsigned cw::sfmatch::loc_to_index( handle_t h, unsigned loc )
{
  sfmatch_t* p = _handleToPtr(h);
  const loc_t* y = std::find_if(p->loc, p->loc+p->locN, [&loc](const loc_t& x){return x.scLocIdx==loc;} );
  unsigned idx = y - p->loc;
  return idx<p->locN ? idx : kInvalidIdx;
}



cw::rc_t cw::sfmatch::test( const object_t* cfg, sfscore::handle_t scoreH )
{
  rc_t            rc                   = kOkRC;
  const object_t* perf                 = nullptr;
  const object_t* gen_perf_example     = nullptr;
  bool            gen_perf_enable_fl   = false;
  unsigned        gen_perf_beg_loc_idx = 0;
  unsigned        gen_perf_loc_cnt     = 0;
  midi_t*         midiA                = nullptr;
  unsigned        maxScWndN            = 10;
  unsigned        maxMidiWndN          = 7;
  unsigned        init_score_loc       = 0;
  unsigned        beg_perf_idx         = 0;
  unsigned        perf_cnt             = 6;
  sfmatch::handle_t   matchH;
  
  // parse the test cfg
  if((rc = cfg->getv( "maxScWndN", maxScWndN,
                      "maxMidiWndN", maxMidiWndN,
                      "gen_perf_example", gen_perf_example,
                      "init_score_loc",init_score_loc,
                      "beg_perf_idx", beg_perf_idx,
                      "perf_cnt", perf_cnt,
                      "perf", perf)) != kOkRC )
  {
    rc = cwLogError(rc,"sfscore test parse params failed.");
    goto errLabel;
  }

  if((rc = gen_perf_example->getv( "enable_fl",   gen_perf_enable_fl,
                                   "beg_loc_idx", gen_perf_beg_loc_idx,
                                   "loc_cnt",     gen_perf_loc_cnt )) != kOkRC )
  {
    rc = cwLogError(rc,"sfscore test parse params 'gen_perf_example' failed.");
    goto errLabel;
  }

  if( gen_perf_enable_fl )
  {
    _gen_match_test_input_from_score( scoreH, gen_perf_beg_loc_idx, gen_perf_loc_cnt, sample_rate(scoreH) );
  }
  else
  {

    // Parse the cfg. perf[] array
    unsigned midiN = perf->child_count();
    midiA = mem::allocZ<midi_t>(midiN);
    
    for(unsigned i=0; i<midiN; ++i)
    {
      const object_t* node = perf->child_ele(i);
      
      if((rc = node->getv("muid",midiA[i].muid,
                          "smpIdx",midiA[i].smpIdx,
                          "pitch", midiA[i].pitch,
                          "vel", midiA[i].vel)) != kOkRC )
      {
        rc = cwLogError(rc,"parsing 'perf' record at index '%i' failed.",i);
        goto errLabel;
      }
    }

    // Create the cwMatch
    if((rc = create(matchH, scoreH, maxScWndN, maxMidiWndN )) != kOkRC )
    {
      rc = cwLogError(rc,"Score matcher create failed.");
      goto errLabel;
    }

    // Set the matcher to the first location to begin matching against
    unsigned oLocId = loc_to_index(matchH,init_score_loc);
    unsigned locN   = maxScWndN;

    // Count of MIDI events to track.
    perf_cnt = std::min(maxMidiWndN,perf_cnt);

    // Align MIDI notes to the score
    if((rc = exec(matchH, oLocId, locN, midiA + beg_perf_idx, perf_cnt, DBL_MAX )) != kOkRC )
    {
      rc = cwLogError(rc,"score match failed.");
      goto errLabel;
    }

    // Update the cwMatch to reflect the alignment found during the exec. 
    unsigned eli     = kInvalidIdx;
    unsigned missCnt = 0;
    unsigned i_opt   = oLocId;
    if(( eli = cw::sfmatch::sync( matchH, i_opt, midiA + beg_perf_idx, perf_cnt, &missCnt )) == kInvalidIdx )
    {
      rc = cwLogError(rc,"score match sync failed.");
      goto errLabel;
    }

    // Print the state alignment.
    unsigned bsi = oLocId;
    print_path( matchH, bsi, midiA );
    
  }
  
 errLabel:
  destroy(matchH);
  mem::release(midiA);
  
  return rc;
}
