//| 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 "cwFile.h"
#include "cwObject.h"
#include "cwMidi.h"
#include "cwDynRefTbl.h"
#include "cwScoreParse.h"
#include "cwSfScore.h"
#include "cwPerfMeas.h"
#include "cwVectOps.h"

/*

1. if a performed event is part of a set then mark the set complete when all events in it have been performed.
2. when a set is marked as complete it should be evaluated immediately.
3. once a complete set is evaluated it is never evaluated again (although incomplete sets may be evaluated again)

4. if a performed location is marked as a 'calc' location and all sets have been evaluated then the calc. is executed.
5. if a performed location is past the next-calc-loc then the calc is executed.
6. if a calc has already been executed and an event that is part of the calc is recieved then the
associated set is updated and the calc is executed again.

7. if a performed event occurs on a trigger location then the section update is executed.
8. if a performed event occurs on a trigger location that has already been executed then the section update is NOT
performed again.
  
 */

namespace cw
{
  namespace perf_meas
  {
    struct calc_str;
    
    typedef struct section_str
    {      
      const struct section_str* prev_section; // section prior to this section
      const sfscore::section_t* section;      // score section this section_t represents
      struct calc_str*          calc;         // calc record for this section
      bool                      triggeredFl;  // This section has already been triggered
    } section_t;

    typedef struct meas_info_str
    {
      unsigned              missEvtN;      // count of skipped events
      unsigned              missLocN;      // count of missed locations
      unsigned              transposeLocN; // count of transposed locations
      unsigned              interpLocN;    // count of interpolated locations

      unsigned vN;  // vN=locN for even,tempo vN=evtN for dyn
      
      double*               locSecV;       // locSecV[vN]
      unsigned*             locSecNV;      // locSecNV[vN] count of performed and interpolated notes (interpolated notes will have a False in the associated statusV[] location)
      double*               dSecV;         // dSecV[vN]
      bool*                 statusV;       // statusV[vN] - true if this location was performed by at least one note
      struct meas_info_str* link;
    } meas_info_t;

    struct loc_str;
    
    typedef struct set_str
    {
      struct loc_str*       loc;       // end location for this set
      const sfscore::set_t* set;       // score set this set_t represents
      meas_info_t*          meas_info; // 
      unsigned              lastPerfUpdateCnt; 
      double                value;     // The value associated with this set. DBL_MAX on initialization
      struct calc_str*      calc;      // calc record to which this set belongs
      struct set_str*       alink;     // perf_meas_t* links
      struct set_str*       slink;     // loc_t.setL links
      struct set_str*       clink;     // calc_t.setL links
    } set_t;

    // The 'calc_t' record hold pointers to all the sets assigned to a given section.
    // The record is different from a section record because it has a location assignment
    // prior to the section which it is represents. This allows all the sets for
    // a given section to be evaluated prior to the section being triggered.
    // The 'calc' record is assigned to the location of the last event in it's last set. 
    typedef struct calc_str
    {
      set_t*     setL;             // list of sets that this calc is applied to (links via set_t.clink)
      section_t* section;          // section where this calc is applied
      double     value[ kValCnt ]; // Aggregate var values for this section
    } calc_t;
    
    typedef struct loc_str
    {
      unsigned   locId;         // oloc location id and the index of this record into p->locA[]
      section_t* section;       // section that begins on this location
      set_t*     setL;          // sets that end on this location (links via loc_t.slink)
      calc_t*    calc;          // calc that can be completed on this location.
    } loc_t;
    
    typedef struct perf_meas_str
    {
      params_t          params;
      sfscore::handle_t scoreH;

      loc_t*            locA;   // locA[locN] 
      unsigned          locN;   // length of locA[] 
      set_t*            setL;   // sets linked on alink

      // Location index of the next section to be triggered.
      unsigned          last_section_loc_idx;

      // Location of the next calc record to be evaluated.
      unsigned          next_section_loc_idx; 

      unsigned          next_calc_loc_idx;
      
    } perf_meas_t;

    perf_meas_t* _handleToPtr( handle_t h )
    { return handleToPtr<handle_t,perf_meas_t>(h); }

    inline unsigned _loc_id_to_index( perf_meas_t* p, unsigned locId )
    {
      if( locId >= p->locN  )
        return kInvalidIdx;
      
      assert( locId == p->locA[locId].locId );
      
      return locId;
    }

    void _destroy_meas_info_records( set_t* set )
    {
      meas_info_t* m = set->meas_info;
      while( m!=nullptr )
      {
        meas_info_t* m0 = m->link;
        mem::release(m->locSecV);
        mem::release(m->locSecNV);
        mem::release(m->dSecV);
        mem::release(m->statusV);
        mem::release(m);
        m = m0;
      }
      
    }
    
    rc_t _destroy( perf_meas_t* p )
    {
      rc_t rc = kOkRC;

      for(unsigned i=0; i<p->locN; ++i)
      {
        set_t* s = p->locA[i].setL;
        while(s!=nullptr)
        {
          set_t* s0 = s->slink;
          
          _destroy_meas_info_records(s);
          
          mem::release(s);
          s = s0;
        }
        
        mem::release(p->locA[i].section);
        mem::release(p->locA[i].calc);
      }

      mem::release(p->locA);
      mem::release(p);
      return rc;
    }

    set_t* _find_set( perf_meas_t* p, unsigned setId )
    {
      for(set_t* s=p->setL; s!=nullptr; s=s->alink)
        if( s->set->id == setId )
          return s;

      cwLogError(kInvalidIdRC,"The setId '%i' is not valid.", setId );
      return nullptr;
    }

    meas_info_t* _create_meas_info_record( set_t* set )
    {
      meas_info_t* m = mem::allocZ<meas_info_t>();
      m->vN          = set->set->varId==score_parse::kDynVarIdx ? set->set->evtCnt :  set->set->locN;
      m->locSecV     = mem::allocZ<double>( m->vN );
      m->locSecNV    = mem::allocZ<unsigned>( m->vN );
      m->dSecV       = mem::allocZ<double>( m->vN );
      m->statusV     = mem::allocZ<bool>( m->vN );
      
      if( set->meas_info == nullptr )
        set->meas_info = m;
      else
      {
        meas_info_t* m0 = set->meas_info;
        while( m0->link != nullptr )
          m0 = m0->link;

        m0->link = m;
      }

      return m;
    }
    
    calc_t* _create_calc_record( perf_meas_t* p, sfscore::section_t* section )
    {
      rc_t rc = kOkRC;

      calc_t* calc = mem::allocZ<calc_t>();
      
      for(unsigned i=0; i<section->setCnt; ++i)
      {
        set_t* s;
        if((s = _find_set(p,section->setArray[i]->id)) == nullptr )
        {
          rc = cwLogError(kInvalidIdRC,"Error forming section set list for the section '%s'.",cwStringNullGuard(section->label));
          goto errLabel;
        }

        s->calc  = calc;
        s->clink = calc->setL;
        calc->setL = s;        
      }

    errLabel:

      if( rc != kOkRC )
        mem::release(calc);
      
      return calc;
    }

    void _advance_next_calc_and_section_indexes( perf_meas_t* p, unsigned cur_section_idx )
    {
      p->last_section_loc_idx = cur_section_idx;
      p->next_calc_loc_idx    = kInvalidIdx;
      p->next_section_loc_idx = kInvalidIdx;
      
      // Set 'last_section_loc_idx' and 'next_calc_loc_idx'
      for(unsigned i=cur_section_idx+1; i<p->locN && (p->next_section_loc_idx==kInvalidIdx || p->next_calc_loc_idx==kInvalidIdx); ++i)
      {
        if( p->next_section_loc_idx == kInvalidIdx && p->locA[i].section != nullptr )
          p->next_section_loc_idx = i;

        if( p->next_calc_loc_idx == kInvalidIdx && p->locA[i].calc != nullptr )
          p->next_calc_loc_idx = i;
      }

      if( p->next_section_loc_idx == kInvalidIdx )
        cwLogInfo("End-of-score reached on section index.");
      else
        if( p->next_calc_loc_idx == kInvalidIdx )
          cwLogWarning("No 'next calc index' was found for the section at location:%i",p->next_section_loc_idx);
    }

    void _reset_loc( loc_t& loc)
    {
      if( loc.section != nullptr )
        loc.section->triggeredFl = false;
      
      if( loc.calc != nullptr )
      {
        for(set_t* set = loc.calc->setL; set!=nullptr; set=set->clink)
        {
          set->value = std::numeric_limits<double>::max();
          set->lastPerfUpdateCnt = 0;
        }
        std::for_each(loc.calc->value,loc.calc->value+kValCnt,[](double& x){ x=std::numeric_limits<double>::max(); });
      }
    }
    
    rc_t _reset( perf_meas_t* p, unsigned init_locId )
    {
      rc_t     rc = kOkRC;
      unsigned i  = 0;

      unsigned init_loc_idx = kInvalidIdx;
      
      for(; i<p->locN; ++i)
      {
        if( p->locA[i].locId == init_locId )
          init_loc_idx = i;

        // reset all locations at and after the init. loc
        if( init_loc_idx != kInvalidIdx )
          _reset_loc(p->locA[i]);
        
      }
      
      if( init_loc_idx == kInvalidIdx )
      {
        rc = cwLogError(kInvalidIdRC,"The initial location id '%i' is not valid.",init_locId);
        goto errLabel;
      }

      _advance_next_calc_and_section_indexes(p,init_loc_idx);
  
    errLabel:
      return rc;
    }

    double _calc_set_list_mean( const set_t* setL, unsigned varTypeId )
    {
      double sum = 0.0;
      unsigned n = 0;
      
      for(const set_t* s=setL; s!=nullptr; s=s->clink)
        if( s->set->varId == varTypeId && s->value != std::numeric_limits<double>::max() )          
        {
          sum += s->value;
          n   += 1;
        }

      return n>0 ? sum/n : 0.0;
    }

    void _eval_one_dynamic_set(perf_meas_t* p, set_t* set )
    {
      double sum = 0;
      unsigned n = 0;
      for(unsigned i=0; i<set->set->evtCnt; ++i)
      {
        const sfscore::event_t* e = set->set->evtArray[i];
        if( e->perfFl )
        {
          sum += e->dynLevel>e->perfDynLevel ? e->dynLevel - e->perfDynLevel : e->perfDynLevel - e->dynLevel;
          n += 1;
        }
      }

      set->value = n==0 ? 0 : sum/n;
    }

    // Calc chord onset time as the avg. onset over of all notes in the chord.
    rc_t _calc_loc_sec( set_t* pm_set  )
    {
      rc_t rc = kOkRC;

      const sfscore::set_t* set = pm_set->set;
      meas_info_t*          m   = pm_set->meas_info;
      
      m->missEvtN = 0;

      assert(  set->locN == m->vN );
      
      vop::zero(m->locSecV,  set->locN);
      vop::zero(m->locSecNV, set->locN);
      
      // Get the time for each location by taking the mean
      // of all events on the same location.
      unsigned li       = 0;
      unsigned curLocId = set->evtArray[0]->oLocId;
      
      for(unsigned i=0; i<set->evtCnt; ++i)
      {
        if( !set->evtArray[i]->perfFl )
        {
          ++m->missEvtN;
        }
        else
        {        
          if( curLocId != set->evtArray[i]->oLocId )
          {
            li += 1;
            curLocId = set->evtArray[i]->oLocId;
          }

          if( li >= set->locN )
          {
            rc = cwLogError(kAssertFailRC,"An invalid location id was encountered %i >= %i.",li,set->locN);
            goto errLabel;
          }

          m->locSecV[ li] += set->evtArray[i]->perfSec;
          m->locSecNV[li] += 1;
        }
      }

      // Calc. mean.
      for(unsigned i=0; i<set->locN; ++i)
        if( m->locSecNV[i] > 0 )
          m->locSecV[i] /= m->locSecNV[i];
      
    errLabel:
      return rc;
    }

    void _interpolate_time_of_missing_notes( perf_meas_t* p, set_t* pm_set, unsigned& bi_ref, unsigned& ei_ref )
    {
      bi_ref      = kInvalidIdx;
      ei_ref      = kInvalidIdx;
      
      const sfscore::set_t* set = pm_set->set;
      meas_info_t* m = pm_set->meas_info;
      unsigned missN = 0;
      unsigned ei    = kInvalidIdx;
      unsigned bi    = kInvalidIdx;

      assert(  set->locN == m->vN );
      
      vop::fill(m->statusV, set->locN, false);
   
      // for each location
      for(unsigned i=0; i<set->locN; ++i)
      {
        // if this location was missed 
        if( m->locSecNV[i] == 0   )
        {
          missN += 1;
          m->missLocN += 1;
          continue;
        }

        // if this location was not in time order
        if( ei != kInvalidIdx && (m->locSecV[i]-m->locSecV[ei])<0 )
        {
          missN += 1;
          m->transposeLocN += 1;
          continue;
        }

        
        // if there are unplayed notes between this note
        // and the last played note at 'ei' then 
        // fill in the missing note times by splitting
        // the gap time evenly - note that this will
        // bias the evenness std to be lower than it
        // should be.
        if( missN > 0 && ei!=kInvalidIdx )
        {            
          double dsec = (m->locSecV[i] - m->locSecV[ei])/(missN+1);
          for(unsigned j=ei+1; j<i; ++j)
          {
            m->locSecV[j] = m->locSecV[j-1] + dsec;
            m->interpLocN += 1;
          }
        }

        if( bi == kInvalidIdx )
          bi = i;
          
        m->statusV[i] = true;
        missN = 0;
        ei    = i;  // ei=last valid location in m->locSecV[]
        
      }
      
      bi_ref = bi;
      ei_ref = ei;

      //vop::print( m->locSecV,  set->locN, "%f ", "locSecV:" );
      //vop::print( m->locSecNV, set->locN, "%i ", "locSecN:" );
      //vop::print( m->statusV,  set->locN, "%i ", "ok:");
        
    }

    rc_t _eval_one_even_set(perf_meas_t* p, set_t* pm_set )
    {
      rc_t                  rc  = kOkRC;      
      const sfscore::set_t* set = pm_set->set;      
      unsigned              bi  = kInvalidIdx;
      unsigned              ei  = kInvalidIdx;
      meas_info_t*          m   = pm_set->meas_info;

      assert(  set->locN == m->vN );
      
      if((rc = _calc_loc_sec(pm_set)) != kOkRC )
        goto errLabel;

      _interpolate_time_of_missing_notes(p, pm_set, bi, ei );
      
      vop::zero(m->dSecV, set->locN);

      // Calc the std. deviation of the note delta times
      // of all notes [bi:ei].  Note that if the notes
      // before bi/after ei  were skipped then they are
      // left out of the calculation.  However skipped notes
      // interal to the range bi:ei were interpolated
      // as perfectly even.
      
      if( (ei - bi)+1 > 2 )
      {
        // calc the delta time for each time in locV[]
        unsigned stdN = 0;
        for(unsigned i=bi+1; i<=ei; ++i)
          m->dSecV[stdN++] = m->locSecV[i] - m->locSecV[i-1];
        
        
        //printf("Even: skipped evt:%i skipped locs:%i interp:%i bi:%i ei:%i final N:%i\n",m->missEvtN,m->missLocN,m->interpLocN,bi,ei,stdN);
        //vop::print( m->dSecV, stdN, "%f ", "std:" );
            
        pm_set->value = vop::std(m->dSecV, stdN );
      }

    errLabel:
      return rc;      
    }

    
    rc_t _eval_one_tempo_set(perf_meas_t* p, set_t* pm_set )
    {
      rc_t                  rc  = kOkRC;      
      const sfscore::set_t* set = pm_set->set;      
      meas_info_t*          m   = pm_set->meas_info;
      unsigned              bi  = kInvalidIdx;
      unsigned              ei  = kInvalidIdx;

      assert(  set->locN == m->vN );
      
      if((rc = _calc_loc_sec(pm_set)) != kOkRC )
        goto errLabel;

      _interpolate_time_of_missing_notes(p, pm_set, bi, ei );
      
      vop::zero(m->dSecV, set->locN);

      if( (ei - bi)+1 > 2 )
      {
        // calc the delta time for each time in locV[]
        unsigned dsecN = 0;
        for(unsigned i=bi+1; i<=ei; ++i)
          m->dSecV[dsecN++] = m->locSecV[i] - m->locSecV[i-1];
                
        //printf("Tempo: skipped evt:%i skipped locs:%i interp:%i bi:%i ei:%i final N:%i\n",m->missEvtN,m->missLocN,m->interpLocN,bi,ei,dsecN);
        //vop::print( m->dSecV, dsecN, "%f ", "dsecV:" );

        double sec_per_beat = vop::mean(m->dSecV, dsecN );
        double bpm = 60.0/sec_per_beat;
        
        
        pm_set->value = bpm;
      }

    errLabel:
      return rc;      
    }

    void _aggregate_dynamic_meas_set_values( perf_meas_t* p, calc_t* calc )
    {
      calc->value[ kDynValIdx ] = _calc_set_list_mean( calc->setL, score_parse::kDynVarIdx );
    }
    
    void _aggregate_even_meas_set_values( perf_meas_t* p, calc_t* calc )
    {
      calc->value[ kEvenValIdx ] = _calc_set_list_mean( calc->setL, score_parse::kEvenVarIdx );
    }
    
    void _aggregate_tempo_meas_set_values( perf_meas_t* p, calc_t* calc )
    {
      calc->value[ kTempoValIdx ] = _calc_set_list_mean( calc->setL, score_parse::kTempoVarIdx );
    }

    void _aggregate_cost_meas_set_values( perf_meas_t* p, calc_t* calc )
    {
      if( calc->section->prev_section != nullptr )
      {
        unsigned              beg_loc_idx = calc->section->prev_section->section->locPtr->index;
        unsigned              end_loc_idx = calc->section->section->locPtr->index;
        const sfscore::loc_t* loc         = loc_base(p->scoreH) + beg_loc_idx;
        const sfscore::loc_t* loc_end     = loc_base(p->scoreH) + end_loc_idx;
        double                sum         = 0;
        unsigned              n           = 0;
        
        for(; loc<loc_end; ++loc)
          for(unsigned i=0; i<loc->evtCnt; ++i)
            if( loc->evtArray[i]->perfFl && loc->evtArray[i]->perfMatchCost != std::numeric_limits<double>::max() )
            {
              sum += loc->evtArray[i]->perfMatchCost;
              n   += 1;
            }
        
        
        calc->value[ kMatchCostValIdx ] = n==0 ? 0 : sum / n;
      }
    }

    set_t* _use_slink(set_t* s){ return s->slink; }
    set_t* _use_clink(set_t* s){ return s->clink; }

    // Set 'force_fl' to true if the sets should be evaluated even if they are not complete.
    void _eval_set_list( perf_meas_t* p, set_t* setL, set_t* (*link_func)(set_t*)  )
    {
      // for each set at this location
      for(set_t* s=setL; s!=nullptr; s=link_func(s))
      {
        // if this set has not been eval'd or has been updated with new perf. information since it was last eval'd
        if( s->lastPerfUpdateCnt==0 || s->set->perfUpdateCnt > s->lastPerfUpdateCnt )
        {
          s->lastPerfUpdateCnt = s->set->perfUpdateCnt;

          if( p->params.print_rt_events_fl )
            cwLogInfo("Set %i eval.",s->set->id);
          
          switch( s->set->varId )
          {
            case score_parse::kDynVarIdx:
              _eval_one_dynamic_set(p,s);
              break;
              
            case score_parse::kEvenVarIdx:
              _eval_one_even_set(p,s);
              break;
              
            case score_parse::kTempoVarIdx:
              _eval_one_tempo_set(p,s);
              break;

            default:
              cwLogError(kInvalidIdRC,"Unknown var type (%i) encountered while evaluating sets.",s->set->varId );
              assert(0);
          }
        }
      }
    }              
    
    rc_t _update_sets( perf_meas_t* p, unsigned loc_idx )
    {
      rc_t rc = kOkRC;
      if( p->locA[loc_idx].setL != nullptr && are_all_loc_set_events_performed( p->scoreH, loc_idx ) )
        _eval_set_list(p,p->locA[loc_idx].setL,_use_slink);
      
      return rc;      
    }

    rc_t _update_calc( perf_meas_t* p, unsigned loc_idx )
    {
      rc_t rc = kOkRC;
      // if the loc is past the next_calc_loc_idx or is equal to next_calc_loc_idx and all the set events have been performed for the location
      if( p->next_calc_loc_idx != kInvalidIdx
          && (loc_idx > p->next_calc_loc_idx
              || (loc_idx == p->next_calc_loc_idx
                  && are_all_loc_set_events_performed( p->scoreH, loc_idx ))))
      {

        if( p->params.print_rt_events_fl )
          cwLogInfo("Calc: Loc:%i  nci:%i",loc_idx,p->next_calc_loc_idx );
        
        calc_t* calc = p->locA[ p->next_calc_loc_idx ].calc;
        assert( calc != nullptr );

        // eval any sets that have not already been evaluated - even if they are not complete
        _eval_set_list(p,calc->setL, _use_clink);

        // aggregate the measurements from each measurement type into a single scalar values.
        _aggregate_dynamic_meas_set_values(p,calc);
        _aggregate_even_meas_set_values(p,calc);
        _aggregate_tempo_meas_set_values(p,calc);
        _aggregate_cost_meas_set_values(p,calc);
      }
      return rc;
    }

    rc_t _update_section( perf_meas_t* p, unsigned loc_idx, result_t& resultRef )
    {
      rc_t rc = kOkRC;

      // if next_section_loc_idx == kInvalidIdx then the end of the score has been encountered
      // (or we are in an invalid state)
      if( p->next_section_loc_idx == kInvalidIdx )
      {
        cwLogWarning("End-of-score or invalid state encountered (next_section_loc_idx==kInvalidIdx).");
        goto errLabel;
      }

      // TODO: Should the following be a loop which iterates
      // until p->next_section_loc_idx > loc_idx
      
      if( p->next_section_loc_idx <= loc_idx )
      {      
        assert( p->locA[p->next_section_loc_idx].section != nullptr );

        section_t* section = p->locA[p->next_section_loc_idx].section;

        if( section->triggeredFl == false )
        {
          assert( section->section != nullptr && section->section->locPtr != nullptr );
          
          resultRef.loc          = loc_idx;
          resultRef.sectionLoc   = section->section->locPtr->index;
          resultRef.sectionLabel = section->section->label;
          if( section->calc == nullptr )
            cwLogWarning("No sets assigned to section %s",cwStringNullGuard(section->section->label));
          else
          {
            resultRef.valueA       = section->calc->value;
            resultRef.valueN       = kValCnt;
          }
        
          section->triggeredFl = true;
          
          _advance_next_calc_and_section_indexes(p,p->next_section_loc_idx);

          //cwLogInfo("Section %s triggered.",cwStringNullGuard(resultRef.sectionLabel));
          
        }
      }
    errLabel:
      return rc;
    }

    template< typename T >
    rc_t _write_result_vector(file::handle_t fH, section_t* section, set_t* set, const char* opLabel, const char* fmt, const T* vect, unsigned locN)
    {
      rc_t rc;
      
      if((rc = file::printf(fH,"%s,%i,%s,%s,%i,",
                            section->section->label,
                            set==nullptr ? -1 : set->set->id,
                            set==nullptr ? "" : score_parse::var_index_to_char(set->set->varId),
                            opLabel,
                            locN)) != kOkRC )
      {
        rc = cwLogError(rc,"Perf. measure vector header write failed.");
        goto errLabel;
      }

      for(const T* v=vect; v<vect+locN; ++v)
        if((rc = file::printf(fH,fmt,*v)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf. measure vector write failed.");
          goto errLabel;
        }

      if((rc = file::printf(fH,"\n")) != kOkRC )
      {
          rc = cwLogError(rc,"Perf. measure vector EOL failed.");
          goto errLabel;
      }
      
    errLabel:
      return rc;      
    }
    
    double dbl_max_to_neg_one( double x )
    { return x==std::numeric_limits<double>::max() ? -1.0 : x; }
    
  }
}

cw::rc_t cw::perf_meas::create( handle_t& hRef, sfscore::handle_t scoreH, const params_t& params )
{
  rc_t rc;
  if((rc = destroy(hRef)) != kOkRC )
    return rc;

  perf_meas_t*          p            = mem::allocZ<perf_meas_t>();
  const sfscore::loc_t* locA         = sfscore::loc_base(scoreH);
  const section_t*      prev_section = nullptr;
  
  p->scoreH               = scoreH;
  p->params               = params;
  p->locN                 = loc_count(scoreH);
  p->locA                 = mem::allocZ<loc_t>(p->locN);
  p->next_section_loc_idx = kInvalidIdx;
  p->next_calc_loc_idx    = kInvalidIdx;
  
  // for each score location
  for(unsigned i=0; i<p->locN; ++i)
  {
    // the index of the record into p->locA[]
    // is the same as the locId of the associated location
    assert( i == locA[i].index );
    
    p->locA[i].locId = locA[i].index;

    // if this location is the end of a set (or sets)  ...
    for(sfscore::set_t* s=locA[i].setList; s!=nullptr; s=s->llink)
    {
      // ... link the sets onto this location record
      set_t* set      = mem::allocZ<set_t>();
      set->loc        = p->locA + i;
      set->set        = s;      
      set->slink      = p->locA[i].setL;
      p->locA[i].setL = set;      
      set->alink      = p->setL;
      p->setL         = set;

      _create_meas_info_record(set);
    }
    
    // if this location is the start of a new section
    if( locA[i].begSectPtr != nullptr )
    {
      p->locA[i].section               = mem::allocZ<section_t>();
      p->locA[i].section->prev_section = prev_section;
      p->locA[i].section->section      = locA[i].begSectPtr;
      prev_section                     = p->locA[i].section;
      
      if(locA[i].begSectPtr->measLocPtr != nullptr )
      {
        // get the loc of the last event in the last set that applies to this section
        // (this is where the sets that supply this section will be evaluated)
        unsigned calc_loc_idx = locA[i].begSectPtr->measLocPtr->index;
        assert( calc_loc_idx < p->locN);

        // verify that the 'calc' location is <= the section location
        if( calc_loc_idx > locA[i].index )
        {
          rc = cwLogError(kInvalidStateRC,"The last loc %i of the last set in section '%s' is after the section start at loc: %i.", calc_loc_idx, cwStringNullGuard(locA[i].begSectPtr->label),i);
          goto errLabel;
        }

        // verify that that the calc location is available
        if( p->locA[ calc_loc_idx ].calc != nullptr )
        {
          rc = cwLogError(kInvalidStateRC,"A given location (%i) may only have one 'calc' record.",calc_loc_idx);
          goto errLabel;
        }
        
        // create the 'calc' record preceding this section 
        if(( p->locA[ calc_loc_idx ].calc = _create_calc_record( p, locA[i].begSectPtr )) == nullptr )
        {
          rc = cwLogError(kInvalidIdRC,"The 'calc' object create failed at location %i",calc_loc_idx);
          goto errLabel;
        }

        // set the calc.section pointer and the section.calc pointer
        p->locA[ calc_loc_idx ].calc->section = p->locA[i].section;
        p->locA[i].section->calc              = p->locA[ calc_loc_idx ].calc;
      }      
    }
  }

  if((rc = _reset(p,0)) != kOkRC )
  {
    rc = cwLogError(rc,"Perf. meas initial reset failed.");
    goto errLabel;
  }
  
  hRef.set(p);

 errLabel:
  if(rc != kOkRC )
    _destroy(p);
  
  return rc;
}


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

  perf_meas_t* p = _handleToPtr(hRef);

  if((rc = _destroy(p)) != kOkRC )
  {
    rc = cwLogError(rc,"Destroy failed.");
    return rc;
  }

  hRef.clear();

  return rc;
}

cw::rc_t cw::perf_meas::reset( handle_t h, unsigned init_locId )
{
  perf_meas_t* p = _handleToPtr(h);
  return _reset(p,init_locId);
}


cw::rc_t cw::perf_meas::exec( handle_t h, const sfscore::event_t* event, result_t& resultRef )
{
  rc_t         rc  = kOkRC;
  perf_meas_t* p   = _handleToPtr(h);
  unsigned     loc_idx;

  resultRef = {};
  resultRef.loc = kInvalidIdx;
  resultRef.sectionLoc = kInvalidIdx;
  
  // get the index of the location record associated with this event
  if((loc_idx = _loc_id_to_index(p,event->oLocId )) == kInvalidIdx )
  {
    cwLogError(kInvalidIdRC,"The event loc %i is not valid.",event->oLocId);
    goto errLabel;
  }

  // if this event is prior to the last triggered section then ignore it
  if( p->last_section_loc_idx != kInvalidIdx && loc_idx < p->last_section_loc_idx )
  {
    cwLogWarning("Backtrack before last triggered section loc:%i < last section:%i.",loc_idx,p->last_section_loc_idx);
    goto errLabel;
  }


  // if this location is attached to a set then eval the set if it is complete
  if((rc = _update_sets(p,loc_idx)) != kOkRC )
  {
    rc = cwLogError(rc,"Set update failed.");
    goto errLabel;
  }

  // if this location is at or after 'next_calc_loc_idx' then evaluate the calc record
  if((rc = _update_calc(p,loc_idx)) != kOkRC )
  {
    rc = cwLogError(rc,"Calc. update failed.");
    goto errLabel;
  }

  // if this location is at or after the next section then update the section from the calc.
  if((rc = _update_section(p,loc_idx,resultRef)) != kOkRC )
  {
    rc = cwLogError(rc,"Section update failed.");
    goto errLabel;
  }
  
 errLabel:

  if( rc != kOkRC )
    rc = cwLogError(rc,"Perf-meas exec failed.");
  return rc;
}

void cw::perf_meas::report( handle_t h )
{
  perf_meas_t* p   = _handleToPtr(h);
  
  for(unsigned i=0; i<p->locN; ++i)
  {
    loc_t* loc = p->locA + i;
    bool fl = loc->section || loc->setL || loc->calc;
    
    if( fl )
      printf("%i : ",loc->locId );
    
    if( loc->section )
    {
      printf("section:%s ", loc->section->section->label );
    }
    
    set_t* s;
    if( loc->setL != nullptr )
    {
      printf("set: ");
      for(s=loc->setL; s!=nullptr; s=s->slink)
        printf("%i ", s->set->id);
    }
    
    if( loc->calc != nullptr )
    {
      printf("calc: ");
      for(s=loc->calc->setL; s!=nullptr; s=s->clink)
        printf("%i ", s->set->id);
    }

    if(fl)
      printf("\n");
      
  }
}


cw::rc_t cw::perf_meas::write_result_json( handle_t h,
                                           const char* player_name,
                                           const char* perf_date,
                                           unsigned perf_take_numb,
                                           const char* out_fname )
{
  rc_t         rc       = kOkRC;
  perf_meas_t* p        = _handleToPtr(h);
  object_t*    root     = newDictObject();
  object_t*    sectionL = root->insert_pair("sectionL",newListObject());
  
  for(loc_t* loc=p->locA; loc<p->locA+p->locN; ++loc)    
    if( loc->section != nullptr && loc->section->calc != nullptr && loc->section->triggeredFl )
    {
      object_t*     sect   = newDictObject(sectionL);
      const double* valueV = loc->section->calc->value;
      object_t*     setL   = newListObject();
       
      sect->putv("label",loc->section->section->label,
                 "player_name",player_name,
                 "perf_date",perf_date,
                 "perf_take_numb",perf_take_numb,
                 "locId",loc->locId,
                 "dyn",dbl_max_to_neg_one(valueV[kDynValIdx]),
                 "even",dbl_max_to_neg_one(valueV[kEvenValIdx]),
                 "tempo",dbl_max_to_neg_one(valueV[kTempoValIdx]),
                 "cost",dbl_max_to_neg_one(valueV[kMatchCostValIdx]),
                 "setL",setL);

      for(set_t* set=loc->section->calc->setL; set!=nullptr; set=set->clink)
      {
        object_t* set_o = newDictObject(setL);
        
        set_o->putv("type",score_parse::var_index_to_char(set->set->varId),
                    "setId",set->set->id,
                    "locId",set->loc->locId,
                    "value",dbl_max_to_neg_one(set->value),
                    "missEvtN",set->meas_info->missEvtN,
                    "missLocN",set->meas_info->missLocN,
                    "transposeLocN",set->meas_info->transposeLocN,
                    "interpLocN",set->meas_info->interpLocN);

        set_o->put_numeric_list("locSecV",  set->meas_info->locSecV,  set->meas_info->vN );
        set_o->put_numeric_list("locSecNV", set->meas_info->locSecNV, set->meas_info->vN );
        set_o->put_numeric_list("dSecV",    set->meas_info->dSecV,    set->meas_info->vN );
        set_o->put_numeric_list("statusV",  set->meas_info->statusV,  set->meas_info->vN );

      }
    }
  
  if((rc = objectToFile(out_fname,root)) != kOkRC )
  {
    rc = cwLogError(rc,"Perf. measurement JSON file write failed.");
    goto errLabel;
  }
      
 errLabel:
  root->free();

  return rc;          
}

cw::rc_t cw::perf_meas::write_result_csv( handle_t h, const char* out_fname )
{
  rc_t rc = kOkRC;
  perf_meas_t* p = _handleToPtr(h);
  file::handle_t fH;

  if((rc = file::open(fH,out_fname, file::kWriteFl)) != kOkRC )
  {
    rc = cwLogError(rc,"Perf. measure result file create failed on '%s'.",cwStringNullGuard(out_fname));
    goto errLabel;
  }

  if((rc = file::printf(fH,"section,set,type,op,value,missEvtN,missLocN,transposeLocN,interpLocN\n")) != kOkRC )
  {
    rc = cwLogError(rc,"Perf. measure title write failed.");
    goto errLabel;
  }
  
  for(loc_t* loc=p->locA; loc<p->locA+p->locN; ++loc)    
    if( loc->section != nullptr && loc->section->calc != nullptr )
    {
      if((rc = _write_result_vector(fH,loc->section,nullptr,"section","%f,",loc->section->calc->value,kValCnt)) != kOkRC )
      {
          rc = cwLogError(rc,"Perf. measure file write result section header failed.");
          goto errLabel;          
      }

      for(set_t* set=loc->section->calc->setL; set!=nullptr; set=set->clink)
      {

        if((rc = file::printf(fH,"%s,%i,%s,summary,%f,%i,%i,%i,%i\n",
                              loc->section->section->label,
                              set->set->id,
                              score_parse::var_index_to_char(set->set->varId),
                              set->value != std::numeric_limits<double>::max() ? set->value : 0,
                              set->meas_info->missEvtN,
                              set->meas_info->missLocN,
                              set->meas_info->transposeLocN,
                              set->meas_info->interpLocN)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf. measure file write result failed.");
          goto errLabel;          
        }

        if((rc = _write_result_vector(fH,loc->section,set,"locSecV","%f,",set->meas_info->locSecV,set->set->locN)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf.measure locSecV[] write result failed.");
          goto errLabel;
        }

        if((rc = _write_result_vector(fH,loc->section,set,"locSecNV","%i,",set->meas_info->locSecNV,set->set->locN)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf.measure locSecNV[] write result failed.");
          goto errLabel;
        }

        if((rc = _write_result_vector(fH,loc->section,set,"dSecV","%f,", set->meas_info->dSecV,set->set->locN)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf.measure dSecV[] write result failed.");
          goto errLabel;
        }

        if((rc = _write_result_vector(fH,loc->section,set,"statusV","%i,",set->meas_info->statusV,set->set->locN)) != kOkRC )
        {
          rc = cwLogError(rc,"Perf.measure status[] write result failed.");
          goto errLabel;
        }
        
      }
    }

  if((rc = file::close(fH)) != kOkRC )
  {
    rc = cwLogError(rc,"Perf. meas. file close failed.");
    goto errLabel;
  }
  
 errLabel:
  return rc;
    
}

  
