//| 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 "cwCsv.h"
#include "cwNumericConvert.h"
#include "cwTime.h"
#include "cwDynRefTbl.h"
#include "cwScoreParse.h"
#include "cwSfScore.h"
#include "cwVectOps.h"

namespace cw
{
  namespace score_parse
  {
    typedef struct label_id_str
    {
      unsigned    id;
      const char* label;
    } label_id_t;

    typedef struct var_ref_str
    {
      unsigned    flags;
      unsigned    id;
      const char* label;
    } var_ref_t;
    
    typedef struct dynRef_str
    {
      char*    label;
      unsigned level;
      uint8_t  vel;
    } dynRef_t;

    typedef struct score_parse_str
    {
      csv::handle_t csvH;

      double        srate;

      dyn_ref_tbl::handle_t dynRefH;

      set_t*        begSetL;
      set_t*        endSetL;
      
      section_t*    sectionL;
      
      unsigned      eventAllocN;
      unsigned      eventN;
      event_t*      eventA;
      
    } score_parse_t;

    label_id_t _opcode_ref[] = {
      { kBarTId,     "bar" },
      { kSectionTId, "sec" },
      { kBpmTId,     "bpm" },
      { kNoteOnTId,  "non" },
      { kNoteOffTId, "nof" },
      { kPedalTId,   "ped" },
      { kRestTId,    "rst" },
      { kCtlTId,     "ctl" },
      { kInvalidTId, "<inv>" }
    };

    var_ref_t _var_ref[] = {
      { kDynVarFl,                 kDynVarIdx,   "d" },
      { kDynVarFl | kSetEndVarFl,  kDynVarIdx,   "D" },
      { kEvenVarFl,                kEvenVarIdx,  "e" },
      { kEvenVarFl | kSetEndVarFl, kEvenVarIdx,  "E" },
      { kTempoVarFl,               kTempoVarIdx, "t" },
      { kTempoVarFl| kSetEndVarFl, kTempoVarIdx, "T" },
      { 0,                         kInvalidIdx, "<inv>" }
    };

    

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

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

 
      section_t* s = p->sectionL;
      while( s != nullptr )
      {
        section_t* s0 = s->link;
        mem::release(s->label);
        mem::release(s->setA);
        mem::release(s);
        s = s0;        
      }

      set_t* set = p->begSetL;
      while( set!=nullptr )
      {
        set_t* s0 = set->link;
        mem::release(set->eventA);
        mem::release(set);
        set = s0;
      }
      

      for(unsigned i=0; i<p->eventN; ++i)
        mem::release(p->eventA[i].sciPitch);
      
      mem::release(p->eventA);
      mem::release(p);
      return rc;
    }
    
    rc_t _parse_bar_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      
      if((rc = getv(csvH,"bar",e->barNumb)) != kOkRC )
        rc = cwLogError(rc,"Bar row parse failed.");

      return rc;
    }

    event_t* _hash_to_event( score_parse_t* p, unsigned hash )
    {
      for(unsigned i=0; i<p->eventN; ++i)
        if( p->eventA[i].hash == hash )
          return p->eventA + i;
      return nullptr;
    }

    section_t* _find_section( score_parse_t* p, const char* sectionLabel )
    {
      section_t* s = p->sectionL;
      for(; s!=nullptr; s=s->link)
        if( textIsEqual(s->label,sectionLabel) )
          return s;

      return nullptr;
    }
    
    section_t* _find_or_create_section( score_parse_t* p, const char* sectionLabel )
    {
      section_t* s;
      if((s = _find_section(p,sectionLabel)) == nullptr )
      {
        s = mem::allocZ<section_t>();

        s->label = mem::duplStr(sectionLabel);
        s->link  = p->sectionL;
        p->sectionL = s;        
      }

      //if( s != nullptr )
      //  printf("Section:%s\n",s->label);
      
      return s;
    }

    rc_t _parse_section_stats( score_parse_t* p, csv::handle_t csvH, event_t* e )
    {
      rc_t rc;
      
      if((rc = getv(csvH,
                    "even_min",  e->section->statsA[ kEvenStatIdx ].min,
                    "even_max",  e->section->statsA[ kEvenStatIdx ].max,
                    "even_mean", e->section->statsA[ kEvenStatIdx ].mean,
                    "even_std",  e->section->statsA[ kEvenStatIdx ].std,
                    "dyn_min",   e->section->statsA[ kDynStatIdx ].min,
                    "dyn_max",   e->section->statsA[ kDynStatIdx ].max,
                    "dyn_mean",  e->section->statsA[ kDynStatIdx ].mean,
                    "dyn_std",   e->section->statsA[ kDynStatIdx ].std,
                    "tempo_min", e->section->statsA[ kTempoStatIdx ].min,
                    "tempo_max", e->section->statsA[ kTempoStatIdx ].max,
                    "tempo_mean",e->section->statsA[ kTempoStatIdx ].mean,
                    "tempo_std", e->section->statsA[ kTempoStatIdx ].std,
                    "cost_min",  e->section->statsA[ kCostStatIdx ].min,
                    "cost_max",  e->section->statsA[ kCostStatIdx ].max,
                    "cost_mean", e->section->statsA[ kCostStatIdx ].mean,
                    "cost_std",  e->section->statsA[ kCostStatIdx ].std )) != kOkRC )
      {
        rc = cwLogError(rc,"Error parsing CSV meas. stats field.");
        goto errLabel;        
      }

    errLabel:
      
      return rc;
    }

    
    rc_t _parse_section_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      const char* sectionLabel = nullptr;
      
      if((rc = getv(csvH,"section",sectionLabel)) != kOkRC )
      {
        rc = cwLogError(rc,"Section row parse failed.");
        goto errLabel;
      }

      if((e->section = _find_or_create_section(p,sectionLabel)) == nullptr )
      {
        rc = cwLogError(kOpFailRC,"Section find/create failed for section: '%s'.",cwStringNullGuard(sectionLabel));
        goto errLabel;
      }

      //if((rc = _parse_section_stats(p,csvH,e)) != kOkRC )
      //  goto errLabel;        

      e->section->csvRowNumb = e->csvRowNumb;
      
    errLabel:
      return rc;
    }
    
    rc_t _parse_bpm_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      
      if((rc = getv(csvH,
                    "bpm",e->bpm,
                    "rval",e->bpm_rval )) != kOkRC )
      {
        rc = cwLogError(rc,"BPM row parse failed.");
      }
      
      return rc;
    }

    rc_t _parse_advance_to_var_label_section( const char*& text )
    {
      rc_t rc = kOkRC;
      
      // the label must have at least 3 characters to include both the flag character, a space, and a section identifier
      if( textLength(text) < 3 )
      {
        text += textLength(text);
        return rc;
      }
      
      if((text = nextWhiteChar(text)) != nullptr )
        if((text = nextNonWhiteChar(text)) != nullptr)
          if( textLength(text) > 0)
            goto errLabel;
        
      rc = cwLogError(kSyntaxErrorRC,"Parse of var target section failed on '%s'.",cwStringNullGuard(text));

    errLabel:
      return rc;
    }

    rc_t _parse_var_label( score_parse_t* p, const char* text, unsigned varIdx, unsigned varFlag, event_t* e )
    {
      rc_t        rc           = kOkRC;
      section_t*  section      = nullptr;
      
      if( textLength(text) == 0 )
        return rc;
      
      unsigned flags = var_char_to_flags(text);

      e->varA[ varIdx ].flags = flags;

      if( !cwIsFlag(flags,varFlag) )
      {
        rc = cwLogError(kSyntaxErrorRC,"Unknown attribute flag '%s'. Expected: '%s'.",text,var_flags_to_char(varFlag));
        goto errLabel;
      }

      // The last set in a group will have a target section id appended to the character identifier
      if((rc = _parse_advance_to_var_label_section(text)) != kOkRC )
        goto errLabel;

      // if this set has a target section id
      if( textLength(text) > 0 )
      {
        if((section = _find_or_create_section(p, text )) == nullptr )
        {
          rc = cwLogError(kOpFailRC,"The var target section find/create failed.");
          goto errLabel;
        }

        e->varA[ varIdx ].target_section  = section;
        e->varA[ varIdx ].flags          |= kSetEndVarFl;  // if a target section was included then this is also the 'end' id in the set.
      }
    errLabel:
      return rc;
      
    }
    
    rc_t _parse_note_on_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc,rc0=kOkRC,rc1=kOkRC,rc2=kOkRC;
      const char* sciPitch   = nullptr;
      const char* dmark      = nullptr;
      const char* graceLabel = nullptr;
      const char* tieLabel   = nullptr;
      const char* onsetLabel = nullptr;
      const char* dynLabel   = nullptr;
      const char* evenLabel  = nullptr;
      const char* tempoLabel = nullptr;
      const char* oLocId       = nullptr;
      
      if((rc = getv(csvH,
                    "oloc",  oLocId,
                    "rval",  e->rval,
                    "dots",  e->dotCnt,
                    "sci_pitch",sciPitch,
                    "dmark",dmark,
                    "status",e->status,
                    "d0",    e->d0,
                    "d1",    e->d1,
                    "grace", graceLabel,
                    "tie",   tieLabel,
                    "onset", onsetLabel,
                    "dyn",   dynLabel,
                    "even",  evenLabel,
                    "tempo", tempoLabel)) != kOkRC )
      {
        rc = cwLogError(rc,"Error parsing CSV note-on row.");
        goto errLabel;
      }

      if( textLength(oLocId) > 0 )
        if((rc = string_to_number(oLocId,e->oLocId)) != kOkRC )
        {
          rc = cwLogError(rc,"Error converting oLocId (%s) to number.",oLocId);
          goto errLabel;
        }
      
      rc0 = _parse_var_label(p, dynLabel,  kDynVarIdx,   kDynVarFl,   e );
      rc1 = _parse_var_label(p, evenLabel, kEvenVarIdx,  kEvenVarFl,  e );
      rc2 = _parse_var_label(p, tempoLabel,kTempoVarIdx, kTempoVarFl, e );

      if( textIsEqual(graceLabel,"g") )
        e->flags |= kGraceFl;

      if( textIsEqual(tieLabel,"t") )
        e->flags |= kTieBegFl;
      
      if( textIsEqual(tieLabel,"_") )
        e->flags |= kTieContinueFl;
      
      if( textIsEqual(tieLabel,"T") )
        e->flags |= kTieEndFl;

      if( textIsEqual(onsetLabel, "o" ))
          e->flags |= kOnsetFl;

      if( sciPitch != nullptr )
        e->sciPitch = mem::duplStr(sciPitch);

      if( e->d1 > 0 )
        if((e->dynLevel = marker_to_level(p->dynRefH,dmark))  == kInvalidIdx )
        {
          rc = cwLogError(kSyntaxErrorRC,"An invalid dynamic mark (%s) was encountered.",cwStringNullGuard(dmark));
          goto errLabel;
        }

      if( cwIsFlag(e->flags,kOnsetFl) )
      {
        if( cwIsFlag(e->flags,kTieContinueFl | kTieEndFl)  )
        {
          rc = cwLogError(kSyntaxErrorRC,"The '%s' event has both an onset flag and tie continue/end flag.",cwStringNullGuard(sciPitch));
          goto errLabel;
        }

      }
      else
      {
        if( e->varA[kDynVarIdx].flags )
        {
          rc = cwLogError(kSyntaxErrorRC,"The '%s' event has no onset flag but is included in a dynamics set.",cwStringNullGuard(sciPitch));
          goto errLabel;
        }
        
      }
      
    errLabel:
      return rcSelect(rc,rc0,rc1,rc2);                    
    }
    
    rc_t _parse_note_off_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      
      if((rc = getv(csvH,
                    "status",e->status,
                    "d0",e->d0,
                    "d1",e->d1)) != kOkRC )
      {
        rc = cwLogError(rc,"Error parsing CSV note-off row.");
      }
      return rc;      
    }
    
    rc_t _parse_pedal_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      
      if((rc = getv(csvH,
                    "status",e->status,
                    "d0",e->d0,
                    "d1",e->d1)) != kOkRC )
      {
        rc = cwLogError(rc,"Error parsing CSV pedal row.");
      }
      return rc;
    }
    
    rc_t _parse_ctl_row( score_parse_t* p, csv::handle_t csvH, event_t* e  )
    {
      rc_t rc;
      
      if((rc = getv(csvH,
                    "status",e->status,
                    "d0",e->d0,
                    "d1",e->d1)) != kOkRC )
      {
        rc = cwLogError(rc,"Error parsing CSV ctl row.");
      }
      return rc;
    }

    rc_t _set_hash( score_parse_t* p, unsigned opId, unsigned barNumb, uint8_t midiPitch, unsigned barPitchIdx, unsigned& hashRef )
    {
      rc_t rc = kOkRC;

      hashRef = 0;
      
      unsigned hash = form_hash(opId,barNumb,midiPitch,barPitchIdx);
              
      if( _hash_to_event(p,hash) != nullptr )
      {
        rc = cwLogError(kInvalidStateRC,"The event hash '%x' is is duplicated.",hash);
        goto errLabel;
      }

      hashRef = hash;
      
    errLabel:
      return rc;
    }
    
    rc_t _parse_events( score_parse_t* p, csv::handle_t csvH )
    {
      rc_t       rc           = kOkRC;
      section_t* cur_section  = nullptr;
      unsigned   cur_bar_numb = 1;
      unsigned   bar_evt_idx  = 0;
      unsigned   bpm          = 0;
      double     bpm_rval     = 0;
      unsigned   barPitchCntV[ midi::kMidiNoteCnt ];
      vop::zero(barPitchCntV,midi::kMidiNoteCnt);

      while((rc = next_line(csvH)) == kOkRC )
      {
        const char* opcodeLabel = nullptr;

        if( p->eventN >= p->eventAllocN )
        {
          rc = cwLogError(kBufTooSmallRC,"Event array full.");
          break;
        }
        
        event_t* e = p->eventA + p->eventN;

        e->oLocId = kInvalidIdx;
        
        if((rc = getv(csvH,
                      "opcode",opcodeLabel,
                      "voice", e->voice,
                      "eloc",  e->eLocId,
                      "tick",  e->tick,
                      "sec",   e->sec )) != kOkRC )
        {
          rc = cwLogError(rc,"Error parsing score CSV row." );
          goto errLabel;          
        }

        e->csvRowNumb = cur_line_index(csvH) + 1;
        e->opId       = opcode_label_to_id(opcodeLabel);
        e->index      = p->eventN;
        e->dynLevel   = kInvalidIdx;
        
        switch( e->opId )
        {
          case kBarTId:
            if((rc = _parse_bar_row(p, csvH, e )) == kOkRC )
            {
              cur_bar_numb = e->barNumb;
              bar_evt_idx  = 0;
              vop::zero(barPitchCntV,midi::kMidiNoteCnt);

              if((rc = _set_hash( p, e->opId, cur_bar_numb, 0, 0, e->hash )) != kOkRC )
              {
                rc = cwLogError(rc,"Error setting event hash for bar line:%i",cur_bar_numb);
                goto errLabel;
              }

              //printf("%i : 0x%x\n",cur_bar_numb,e->hash);
              
            }
            break;
            
          case kSectionTId:
            if((rc = _parse_section_row(p, csvH, e )) == kOkRC )
            {
              if( cur_section == nullptr )
                for(event_t* e0 = e-1; e0 >= p->eventA; --e0)
                  e0->section = e->section;
              
              cur_section = e->section;
            }
            break;
            
          case kBpmTId:
            if((rc = _parse_bpm_row(p,csvH, e )) == kOkRC )
            {
              // if the cur BPM has not yet been set then go backward setting
              // all events prior to this to the initial BPM
              if( bpm == 0 && e->bpm != 0 )
                std::for_each(p->eventA,e,[e](auto& x){ x.bpm = e->bpm; x.bpm_rval=e->bpm_rval; });

              // if the parsed BPM is invalid ...
              if( e->bpm == 0 || e->bpm_rval==0 )
              {
                e->bpm      = bpm; // ... then ignore it
                e->bpm_rval = bpm_rval;
              }
              else
              {
                bpm      = e->bpm; // ... otherwise make it the current BPM
                bpm_rval = e->bpm_rval;
              }

              // Be sure that all events on this location have the same BPM
              for(event_t* e0 = e - 1; e0>=p->eventA && e0->eLocId==e->eLocId; --e0)
              {
                e0->bpm      = e->bpm;
                e0->bpm_rval = e->bpm_rval;
              }
            }
            break;
            
          case kNoteOnTId:
            if((rc = _parse_note_on_row(p,csvH,e)) == kOkRC )
            {
              e->barPitchIdx = barPitchCntV[e->d0];

              if((rc = _set_hash( p, e->opId, cur_bar_numb, e->d0, e->barPitchIdx, e->hash )) != kOkRC )
              {
                rc = cwLogError(rc,"Error setting hash for note-on event: bar:%i pitch:%i bpi:%i", cur_bar_numb, e->d0, e->barPitchIdx );
                goto errLabel;
              }
              
              barPitchCntV[e->d0] += 1;
            }
            break;
            
          case kNoteOffTId:
            rc = _parse_note_off_row(p,csvH,e);
            break;
            
          case kPedalTId:
            rc = _parse_ctl_row(p,csvH,e);
            break;
            
          case kRestTId:
            break;
            
          case kCtlTId:
            rc = _parse_ctl_row(p,csvH,e);
            break;
            
          case kInvalidTId:
            rc = cwLogError(kInvalidIdRC,"Invalid opocde '%s'.", cwStringNullGuard(opcodeLabel));
            break;
            
          default:
            rc = cwLogError(kInvalidIdRC,"Unknown opocde '%s'.", cwStringNullGuard(opcodeLabel));
        }

        if( rc != kOkRC )
          break;

        e->section   = cur_section;
        e->barNumb   = cur_bar_numb;
        e->barEvtIdx = bar_evt_idx++;
        e->bpm       = bpm;
        e->bpm_rval  = bpm_rval;
        p->eventN   += 1;
        
      }

    errLabel:
      switch( rc )
      {
        case kOkRC:
          assert(0);
          break;
          
        case kEofRC:
          rc = kOkRC;
          break;
          
        default:
          cwLogError(rc,"CSV parse event failed on row:%i.", cur_line_index(csvH)+1 );
      }
       
      return rc;
    }
    
    rc_t _parse_csv( score_parse_t* p, const char* fname )
    {
        rc_t        rc       = kOkRC;
        const char* titleA[] = { "opcode","meas","index","voice","loc","eloc","oloc","tick","sec",
                                 "dur","rval","dots","sci_pitch","dmark","dlevel","status","d0","d1",
                                 "bar","section","bpm","grace","tie","onset","pedal","dyn","even","tempo" };
        
        unsigned      titleN   = sizeof(titleA)/sizeof(titleA[0]);
        csv::handle_t csvH;

        // open the CSV file and validate the title row
        if((rc = create( csvH, fname, titleA, titleN )) != kOkRC )
        {
          rc = cwLogError(rc,"Score CSV parse failed on '%s'.",fname);
          goto errLabel;
        }

       // get the line count from the CSV file
        if((rc = line_count(csvH,p->eventAllocN)) != kOkRC )
        {
          rc = cwLogError(rc,"Score CSV line count failed.");
          goto errLabel;    
        }

        // allocate the event array
        p->eventA = mem::allocZ<event_t>(p->eventAllocN);
        
        // parse the events
        if((rc = _parse_events(p, csvH )) != kOkRC )
          goto errLabel;

      errLabel:
        if(rc != kOkRC )
          rc = cwLogError(rc,"CSV parse failed on '%s'.",fname);
        destroy(csvH);
        return rc;      
    }

    set_t* _find_set( score_parse_t* p, unsigned setId )
    {
      for(set_t* set=p->begSetL; set!=nullptr; set=set->link)
        if( set->id == setId )
          return set;

      return nullptr;
    }
    
    set_t* _find_or_create_set( score_parse_t* p, unsigned setId, unsigned varTypeId )
    {
      set_t* set;
      if((set = _find_set(p,setId)) == nullptr )
      {
        set = mem::allocZ<set_t>();
        set->id = setId;
        set->varTypeId = varTypeId;
        
        if( p->endSetL == nullptr )
        {
          p->endSetL = set;
          p->begSetL = set;
        }
        else
        {
          p->endSetL->link = set;
          p->endSetL       = set;
        }
        
      }

      return set;
    }

    void _create_sets( score_parse_t* p )
    {
      set_t*   cur_set    = nullptr;
      unsigned setId      = 0;
      unsigned setNoteIdx = 0;
      unsigned endLocId     = kInvalidIdx;
        
      for(unsigned vi=0; vi<kVarCnt; ++vi)
        for(unsigned ei=0; ei<p->eventN; ++ei)
        {
          event_t* e = p->eventA + ei;
          
          // if an end evt has been located for this set
          // and this loc is past the loc of the end event
          // then the set is complete
          // (this handles the case where there are multiple events
          //  on the same end set location)
          if( endLocId != kInvalidIdx && (e->eLocId > endLocId || ei==p->eventN-1) )
          {
            cur_set->eventA  = mem::allocZ<event_t*>(cur_set->eventN);
            setId           += 1;
            setNoteIdx       = 0;
            cur_set          = nullptr;
            endLocId         = kInvalidIdx;
          }

          // if this event 
          if( e->varA[vi].flags != 0 )
          {
            
            if( cur_set == nullptr )
              cur_set = _find_or_create_set(p,setId,vi);
            
            e->varA[vi].set         = cur_set;
            e->varA[vi].setNoteIdx  = setNoteIdx++;
            cur_set->eventN        += 1;
            
            if( cwIsFlag(e->varA[vi].flags,kSetEndVarFl) )
              endLocId = e->eLocId;              
          }
        }
    }

    void _fill_sets( score_parse_t* p )
    {
      for(unsigned ei=0; ei<p->eventN; ++ei)
        for(unsigned vi=0; vi<kVarCnt; ++vi)
          if( p->eventA[ei].varA[vi].set != nullptr )
          {
            event_t* e = p->eventA + ei;
            
            assert( e->varA[vi].setNoteIdx < e->varA[vi].set->eventN );
            
            e->varA[vi].set->eventA[ e->varA[vi].setNoteIdx ] = e;        
          }
      
    }

    unsigned _set_count( score_parse_t* p )
    {
      unsigned n = 0;
      for(set_t* s = p->begSetL; s!=nullptr; s=s->link)
        ++n;
      return n;      
    }
        
    void _order_set_ids_by_time( score_parse_t* p )
    {
      typedef struct set_order_str
      {
        unsigned beg_evt_idx;
        set_t*   set;
      } set_order_t;
      
      unsigned     setAllocN = _set_count(p);
      unsigned     setN      = 0;
      set_order_t* setA      = mem::allocZ<set_order_t>(setAllocN);
      
      for(set_t* s=p->begSetL; s!=nullptr; s=s->link)
      {
        if( s->eventN > 0 )
        {
          setA[setN].beg_evt_idx = s->eventA[0]->index;
          setA[setN].set         = s;
          setN += 1;
        }
      }

      std::sort( setA, setA+setN, [](auto a, auto b){return a.beg_evt_idx<b.beg_evt_idx;});

      unsigned set_id = 0;
      std::for_each( setA, setA+setN, [&](auto a){ a.set->id = set_id++;  });

      mem::release(setA);
    }
    
    rc_t _validate_sets( score_parse_t* p )
    {
      rc_t rc = kOkRC;
      for(set_t* set = p->begSetL; set!=nullptr; set=set->link)
      {
        unsigned loc[]      = { kInvalidIdx, kInvalidIdx, kInvalidIdx };
        unsigned locN       = sizeof(loc)/sizeof(loc[0]);
        unsigned loc_i      = 0;
        unsigned csvRowNumb = -1;
        unsigned barNumb    = -1;
        
        // for each event in this set.
        for(unsigned i=0; i<set->eventN; ++i)
        {
          if( set->eventA[i] == nullptr )
          {
            rc = cwLogError(kInvalidStateRC,"The set %i of type %i section:%s.\n", set->id, set->varTypeId, set->targetSection->label );
            continue;
          }

          // track the last valid csv row numb
          csvRowNumb = set->eventA[i]->csvRowNumb;
          barNumb    = set->eventA[i]->barNumb;
          
          // track the number of locations the events in this set occur at
          if( loc_i < locN )
          {
            unsigned j;
            for(j=0; j<loc_i; ++j)
              if( loc[j] == set->eventA[i]->eLocId )
                break;

            if( j == loc_i )
              loc[ loc_i++ ] = set->eventA[i]->eLocId;
          }
        }

        // 'even' and 'tempo' sets must have at least three events
        if( set->varTypeId == kEvenVarIdx  && loc_i < locN )
        {
          rc = cwLogError(kSyntaxErrorRC,"The 'even' set %i (CSV row:%i bar:%i) of type %i does must have at least 3 events at different locations.",set->id,csvRowNumb,barNumb,set->varTypeId);
        }

        if(  set->varTypeId == kTempoVarIdx && loc_i < 2 )
        {
          rc = cwLogError(kSyntaxErrorRC,"The 'tempo' set %i (CSV row:%i bar:%i) of type %i does must have at least 2 events at different locations.",set->id,csvRowNumb,barNumb,set->varTypeId);
        }
      }

      if( rc != kOkRC )
        rc = cwLogError(rc,"Var set validation failed.");
      
      return rc;
    }

    void _fill_target_sections( score_parse_t* p )
    {
      
      for(unsigned vi=0; vi<kVarCnt; ++vi)
      {
        section_t* cur_sec = nullptr;
        
        for(int ei=(int)p->eventN; ei>=0; --ei)
          if( p->eventA[ei].varA[vi].set != nullptr )
            if( cwIsFlag(p->eventA[ei].varA[vi].flags,kSetEndVarFl) )
            {
              if( p->eventA[ei].varA[vi].target_section == nullptr )
                p->eventA[ei].varA[vi].target_section = cur_sec;
              else
                cur_sec = p->eventA[ei].varA[vi].target_section;

              p->eventA[ei].varA[vi].set->sectionSetIdx      = cur_sec->setN;
              p->eventA[ei].varA[vi].set->targetSection = cur_sec;
              
              cur_sec->setN += 1;

            }
      }
    }

    void _fill_section_sets( score_parse_t* p )
    {
      // allocate memory to hold the set ptr arrays in each section
      for(section_t* s = p->sectionL; s!=nullptr; s=s->link)
        s->setA = mem::allocZ<set_t*>(s->setN);
      
      //  fill the section->setA[] ptrs
      for(set_t* set = p->begSetL; set!=nullptr; set=set->link)
      {
        assert( set->sectionSetIdx < set->targetSection->setN );
        set->targetSection->setA[ set->sectionSetIdx ] = set;

        // set the section beg/end events
        if( set->targetSection->begSetEvent == nullptr )
        {
          set->targetSection->begSetEvent = set->eventA[0];
          set->targetSection->endSetEvent = set->eventA[ set->eventN-1 ];
        }
        else
        {
          if( set->eventA[0]->sec < set->targetSection->begSetEvent->sec )
            set->targetSection->begSetEvent = set->eventA[0];
          
          if( set->eventA[set->eventN-1]->sec > set->targetSection->endSetEvent->sec )
            set->targetSection->endSetEvent = set->eventA[ set->eventN-1 ];
        }
      }      
    }

    rc_t _fill_section_beg_end_evt( score_parse_t* p )
    {
      rc_t rc = kOkRC;
      for(unsigned i=0; i<p->eventN; ++i)
      {
        event_t* e = p->eventA + i;
        assert( e->section != nullptr );
        if( e->section->begEvent == nullptr || e->index < e->section->begEvent->index )
          e->section->begEvent = e;
        
        if( e->section->endEvent == nullptr || e->index > e->section->endEvent->index )
          e->section->endEvent = e;
      }

      return rc;
    }

    bool _compare_sections(const section_t*  sec0, const section_t* sec1)
    {
      return sec0->csvRowNumb < sec1->csvRowNumb;
    }
    
    void _sort_sections( score_parse_t* p )
    {
      // get count of sections
      unsigned secN = 0;
      for(section_t* s=p->sectionL; s!=nullptr; s=s->link)
        ++secN;

      // load secA[] with sections
      section_t** secA = mem::allocZ<section_t*>(secN);
      unsigned    i    = 0;
      for(section_t* s=p->sectionL; s!=nullptr; s=s->link)
        secA[i++]      = s;

      // sort the sections
      std::sort( secA, secA+secN, _compare_sections);

      // rebuild the section list in order
      section_t* begSec = nullptr;
      section_t* endSec = nullptr;
      
      for(i=0; i<secN; ++i)
      {
        secA[i]->link = nullptr;
        
        if( begSec == nullptr )
        {
          begSec = secA[i];
          endSec = secA[i];
        }
        else
        {
          endSec->link = secA[i];
          endSec       = secA[i];          
        }
      }

      p->sectionL = begSec;
      
      mem::release(secA);
    }   

    rc_t _validate_sections( score_parse_t* p, bool show_warnings_fl )
    {
      rc_t rc = kOkRC;

      section_t* s0 = nullptr;
      
      for(section_t* s=p->sectionL; s!=nullptr; s = s->link)
      {
        if( s->setN == 0 )
        {
          if( show_warnings_fl )
            cwLogWarning("The section '%s' does not have any sets assigned to it.",cwStringNullGuard(s->label));
        }
        else
          if( s->begSetEvent == nullptr || s->endSetEvent == nullptr )
          {
            rc = cwLogError(kInvalidStateRC,"The section '%s' does not beg/end events.",cwStringNullGuard(s->label));
            continue;
          }
        
        if( s0 != nullptr && (textCompare( s0->label, s->label ) >= 0 || s0->csvRowNumb > s->csvRowNumb ))
        {
          rc = cwLogError(kInvalidStateRC,"The section label '%s' is out of order with '%s'.",cwStringNullGuard(s->label),cwStringNullGuard(s0->label));
          continue;
        }

        s0 = s;
      }


      // verify that there are no event gaps between the sections
      if( p->sectionL != nullptr and p->sectionL->link != nullptr )
      {
        s0 = p->sectionL;
        for(section_t* s=s0->link; s!=nullptr; s=s->link)
        {
          if( s0->endEvent == nullptr )
            rc = cwLogError(kInvalidStateRC,"The section '%s' does not have an end event.",cwStringNullGuard(s0->label));
          else
          {
            if( s->begEvent == nullptr )
              rc = cwLogError(kInvalidStateRC,"The section '%s' does not have a begin event.",cwStringNullGuard(s->label));
            else
            {
              if( s0->endEvent->index+1 != s->begEvent->index )
                rc = cwLogError(kInvalidStateRC,"The sections '%s' and '%s' do not begin/end on consecutive events.",cwStringNullGuard(s0->label),cwStringNullGuard(s->label));
            }
          }
        
          s0 = s;
        }
      }

      
      if( rc != kOkRC )
        rc = cwLogError(rc,"Section validation failed.");
      return rc;
    }
    
    void _var_print( const event_t* e, unsigned varId, char* text, unsigned textCharN )
    {
      if( e->varA[varId].set == nullptr )
        snprintf(text,textCharN,"%s","               ");
      else
      {
        snprintf(text,textCharN,"%3s-%03i-%02i %4s",var_flags_to_char(e->varA[varId].flags),
                 e->varA[varId].set->id,
                 e->varA[varId].setNoteIdx,
                 e->varA[varId].flags & kSetEndVarFl ? e->varA[varId].set->targetSection->label : " ");
      }
    }
  }
}

const char* cw::score_parse::opcode_id_to_label( unsigned opId )
{
  for(unsigned i=0; _opcode_ref[i].id != kInvalidTId; ++i)
    if( _opcode_ref[i].id == opId )
      return _opcode_ref[i].label;
  return nullptr;
}

unsigned    cw::score_parse::opcode_label_to_id( const char* label )
{
  for(unsigned i=0; _opcode_ref[i].id != kInvalidTId; ++i)
    if( textIsEqual(_opcode_ref[i].label,label) )
      return _opcode_ref[i].id;
  return kInvalidTId;
}

unsigned    cw::score_parse::var_char_to_flags( const char* label )
{
  for(unsigned i=0; _var_ref[i].flags != 0; ++i)
    if( textIsEqual(_var_ref[i].label,label,1) )
      return _var_ref[i].flags;
  return 0; 
}

const char* cw::score_parse::var_flags_to_char( unsigned flags )
{
  for(unsigned i=0; _var_ref[i].flags != 0; ++i)
    if( _var_ref[i].flags == flags )
      return _var_ref[i].label;
  return nullptr;  
}

const char* cw::score_parse::var_index_to_char( unsigned var_idx )
{
  for(unsigned i=0; _var_ref[i].flags != 0; ++i)
    if( _var_ref[i].id == var_idx )
      return _var_ref[i].label;
  return nullptr;  
}

const char* cw::score_parse::dyn_ref_level_to_label( handle_t h, unsigned level )
{
  score_parse_t* p = _handleToPtr(h);
  return level_to_marker( p->dynRefH, level );
}

unsigned cw::score_parse::dyn_ref_label_to_level( handle_t h, const char* label )
{
  score_parse_t* p = _handleToPtr(h);
  return marker_to_level( p->dynRefH, label );
}

unsigned cw::score_parse::dyn_ref_vel_to_level( handle_t h, uint8_t vel )
{
  score_parse_t* p = _handleToPtr(h);
  return velocity_to_level(p->dynRefH,vel);
}

unsigned    cw::score_parse::form_hash( unsigned op_id, unsigned bar, uint8_t midi_pitch, unsigned barPitchIdx )
{
  unsigned hash = 0;

  assert(barPitchIdx < 256 && bar < 0x7ff && op_id < 0xf );
  
  hash += (barPitchIdx & 0x000000ff);
  hash += (midi_pitch  & 0x000000ff) << 8;
  hash += (bar         & 0x00000fff) << 16;
  hash += (op_id       & 0x0000000f) << 28;
  
  return hash;
}

void        cw::score_parse::parse_hash( unsigned hash, unsigned& op_idRef, unsigned& barRef, uint8_t& midi_pitchRef, unsigned& barPitchIdxRef )
{
  barPitchIdxRef = hash &  0x000000ff;
  midi_pitchRef  = (hash & 0x0000ff00) >>  8;
  barRef         = (hash & 0x0fff0000) >> 16;
  op_idRef       = (hash & 0xf0000000) >> 28; 
}


cw::rc_t cw::score_parse::create( handle_t& hRef, const char* fname, double srate, dyn_ref_tbl::handle_t dynRefH, bool show_warnings_fl )
{
  rc_t           rc = kOkRC;
  score_parse_t* p  = nullptr;
  
  if((rc = destroy(hRef)) != kOkRC )
    return rc;

  p = mem::allocZ<score_parse_t>();

  p->srate   = srate;
  p->dynRefH = dynRefH;
  
  if((rc = _parse_csv(p,fname)) != kOkRC )
  {
    rc = cwLogError(rc,"CSV parse failed.");
    goto errLabel;
  }

  _create_sets(p);

  _fill_sets(p);

  _order_set_ids_by_time( p );
  
  if((rc = _validate_sets(p)) != kOkRC )
    goto errLabel;
  
  _fill_target_sections(p);

  _fill_section_sets(p);

  _sort_sections(p);

  _fill_section_beg_end_evt(p);
  
  if((rc = _validate_sections(p,show_warnings_fl)) != kOkRC )
    goto errLabel;
  
  hRef.set(p);
  
 errLabel:
  if( rc != kOkRC )
  {
    rc = cwLogError(rc,"Score parse failed on '%s'.",cwStringNullGuard(fname));
    _destroy(p);
  }
  
  return rc;  
}

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

  score_parse_t* p = _handleToPtr(hRef);

  if((rc = _destroy(p)) != kOkRC )
    return rc;

  hRef.clear();

  return rc;
}

double cw::score_parse::sample_rate( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return p->srate;
}

unsigned cw::score_parse::event_count( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return p->eventN;
}

const cw::score_parse::event_t* cw::score_parse::event_array( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return p->eventA;
}

unsigned cw::score_parse::section_count( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  unsigned n = 0;
  for(section_t* s = p->sectionL; s!=nullptr; s=s->link)
    ++n;
  return n;
}

const cw::score_parse::section_t* cw::score_parse::section_list( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return p->sectionL;
}

unsigned cw::score_parse::set_count( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return _set_count(p);
}

const cw::score_parse::set_t* cw::score_parse::set_list( handle_t h )
{
  score_parse_t* p = _handleToPtr(h);
  return p->begSetL;
}

void cw::score_parse::report( handle_t h )
{
  score_parse_t* p        = _handleToPtr(h);
  unsigned       textBufN = 255;
  char           textBuf[ textBufN+1 ];
  const char*    S        = "S:";
  const char*    B        = "B:";
  const char*    blank    = " ";
  const unsigned flN      = 6;
  
  printf("row  op  section bpm b_rval bar  bei voc tick  sec     rval   dot eloc   oloc flags bpm stat  d0  d1  spich   hash  \n");
  printf("---- --- ------- --- ------ ----- --- --- ---- ------- ------ --- ----- ----- ----- --- ---- ---- --- ----- --------\n");

  for(unsigned i=0; i<p->eventN; ++i)
  {
    const event_t* e        = p->eventA + i;
    const char*    secLabel = i==0 || !textIsEqual(e->section->label,p->eventA[i-1].section->label) ? S : blank;
    const char*    barLabel = i==0 || e->barNumb != p->eventA[i-1].barNumb ? B : blank;

    unsigned       fli      = 0;
    char           flag_str[ flN ] = {0};
    
    if( cwIsFlag(e->flags,kGraceFl))       { flag_str[fli++] = 'g'; }
    if( cwIsFlag(e->flags,kTieBegFl))      { flag_str[fli++] = 't'; }
    if( cwIsFlag(e->flags,kTieContinueFl)) { flag_str[fli++] = '_'; }
    if( cwIsFlag(e->flags,kTieEndFl))      { flag_str[fli++] = 'T'; }
    if( cwIsFlag(e->flags,kOnsetFl))       { flag_str[fli++] = 'o'; }
    
    printf("%4i %3s  %2s%4s %3i %6.4f %2s%3i %3i %3i %4i %7.3f %6.3f %3i %5i %5i %5s %3i 0x%02x 0x%02x %3i %5s",
           e->csvRowNumb,
           opcode_id_to_label(e->opId),
           secLabel,
           e->section  == nullptr || e->section->label==nullptr ? "" : e->section->label ,
           e->bpm,
           e->bpm_rval,
           barLabel,
           e->barNumb,
           e->barEvtIdx,
           e->voice,
           e->tick,
           e->sec,
           e->rval,
           e->dotCnt,
           e->eLocId,
           e->oLocId,
           flag_str,
           e->bpm,
           e->status,
           e->d0,
           e->d1,
           e->sciPitch == nullptr ? "" : e->sciPitch);

    if( e->hash != 0 )
      printf(" %08x",e->hash);

    for(unsigned vi = 0; vi<kVarCnt; ++vi)
    {
      _var_print(e, vi, textBuf, textBufN );
      printf(" %s",textBuf);
    }
    printf("\n");
  }
  
}

