//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Dec 15 15:00:17 PST 2011
// Last Modified: Thu Dec 15 15:00:22 PST 2011
// Last Modified: Wed Jun 20 16:22:44 PDT 2012 Added instruments abbreviations
// Last Modified: Tue Aug 28 10:24:57 PDT 2012 Default metric bug fix
// Filename:      ...sig/examples/all/jrpize.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/jrpize.cpp
// Syntax:        C++; museinfo
//
// Description:   
//
// Things this program does:
// (1) Add "l" markers on terminal and medial longs
// (2) Adds a RWG entry for terminal longs
// (3) Adds % RWG entry if "%" enhanced rhythmic values are used
// (4) default mensuration signs.
// (5) Section: text to !!section: comments and !!!OMD: records
//
// Things to add in the future:
// (a) automatic triplet brackets?
// (b) textual items.
// (c) mensuration sign exceptions.
//

#include "PerlRegularExpression.h"
#include "humdrum.h"

#ifndef OLDCPP
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #ifdef VISUAL
      #include <strstrea.h>     /* for windows 95 */
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
int       addTerminalLongs(HumdrumFile& infile);
int       addTrackLongs(HumdrumFile& infile, int track);
void      printOutput(HumdrumFile& infile, int terminalQ);
int       hasEditorial(HumdrumFile& infile);
int       hasEditorialRDF(HumdrumFile& infile);
int       hasLongRDF(HumdrumFile& infile);
void      handleMensuration(HumdrumFile& infile, int line);
void      printAbbreviation(const char* fullname);
void      checkAndPrintInstAbbr(HumdrumFile& infile, int line);
void      checkAndPrintVox(HumdrumFile& infile, int line);
int       hasSectionLabel(HumdrumFile& infile, int line);
void      printSectionLabel(HumdrumFile& infile, int line);
void      printDefaultKeyTimeMet(HumdrumFile& infile, int line);
void      processMensuration(HumdrumFile& infile, int line);
char*     getMensuration(char* buffer, HumdrumFile& infile, 
                                 int line, int spine);
char*     convertMenIntoMet(char* buffer, const char* mensur);
void      clearMensurationComment(HumdrumFile& infile, int line);
void      setItem(Array<Array<char> >& anArray, int i, 
                                 const char* aString);
void      printSectionInfo(void);
int       wrongSideOfBarline(HumdrumFile& infile, int line);
int       convertRscaleTextToInterpretation(HumdrumFile& infile, int line);
void      processBarlineComment(HumdrumFile& infile);
void      applyBarlineMarker(HumdrumFile& infile, int line, int column, 
                                 int barjump, int dashQ);
void      makeBarlineInvisible(HumdrumFile& infile, int line, int track, 
                                 char addchar, int dashQ);
void      printLabel(HumdrumFile& infile, int line);
void      printDefaultLabelExpansion(HumdrumFile& infile, int line);
int       hasDataBelow(HumdrumFile& infile, int line);
void      turnOffAnyActiveRscales(HumdrumFile& infile, int line);
void      mergeAdjacentNotes(HumdrumFile& infile, int line, int track);
void      fixAccentedText(HumdrumRecord& aRecord);
void      checkText(HumdrumRecord& aRecord, int index);
void      checkForPrimaryMensurationNeed(HumdrumFile& infile, int line);
int       cleanDefaultInterpretations(HumdrumFile& infile, int line);
void      absorbMensurations(HumdrumFile& infile, int line);

// global variables
Options   options;             // database for command-line arguments
int       hasTerminalLong = 0; // true if "l" marker added to data.
Array<Array<char> > Clef;      // used for prevailing clef by voice
Array<Array<char> > Keysig;    // used for prevailing key signature by voice
Array<Array<char> > Timesig;   // used for prevailing time signature by voice
Array<Array<char> > Metsig;    // used for prevailing meter signature by voice
Array<RationalNumber> Rscale;  // used to turn off *rscale:

Array<char > Section;   // used to flip on other side of measure line
char       SectionLabel = 'A'-1;

///////////////////////////////////////////////////////////////////////////

int main(int argc, char* argv[]) {
   HumdrumFile infile;
   Section.setSize(1);
   Section[0] = '\0';

   // process the command-line options
   checkOptions(options, argc, argv);

   // figure out the number of input files to process
   int numinputs = options.getArgCount();

   for (int i=0; i<numinputs || i==0; i++) {
      infile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         infile.read(cin);
      } else {
         infile.read(options.getArg(i+1));
      }

      hasTerminalLong = addTerminalLongs(infile);

      processBarlineComment(infile);

      printOutput(infile, hasTerminalLong);

   }

   return 0;
}


///////////////////////////////////////////////////////////////////////////


//////////////////////////////
//
// processBarlineComment --
//

void processBarlineComment(HumdrumFile& infile) {
   int i, j;
   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   PerlRegularExpression pre3;
   int number;
   int dashQ = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isLocalComment()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!pre.search(infile[i][j], 
               "!LO:TX:.*t=\\s*barline[s]:\\s*(\\d+[^:]*)", "i")) {
            continue;
         }
         if (pre2.search(pre.getSubmatch(1), "/")) {
            cerr << "ERROR: barlines directive has fraction: "
                 << pre.getSubmatch() << " on line " << i+1 << endl;
            exit(1);
         }
         if (pre3.search(infile[i][j], "dash", "i")) {
            dashQ = 1;
         }
         number = atoi(pre.getSubmatch());
         applyBarlineMarker(infile, i, j, number, dashQ);
         infile[i].setToken(j, "!");
      }
   }
}



//////////////////////////////
//
// applyBarlineMarkers --
//

void applyBarlineMarker(HumdrumFile& infile, int line, int column, 
      int barjump, int dashQ) {
   int i;
   int visibleQ;
   int barcount = 0;
   int track = infile[line].getPrimaryTrack(column);
   PerlRegularExpression pre;
   for (i=line+1; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }
      // exit loop if end of music or section is found
      if (pre.search(infile[i][0], "=="))     { break; }
      if (pre.search(infile[i][0], "\\|\\|")) { break; }
      if (pre.search(infile[i][0], ":"))      { break; }

      barcount++;
      visibleQ = !(barcount % barjump);
      if (visibleQ) {
         // or maybe make sure that barline is visible...
         continue;
      }
      if (dashQ) {
         makeBarlineInvisible(infile, i, track, '.', dashQ);
      } else {
         makeBarlineInvisible(infile, i, track, '-', dashQ);
      }
   }
}



//////////////////////////////
//
// makeBarlineInvisible --
//

void makeBarlineInvisible(HumdrumFile& infile, int line, int track,
      char addchar, int dashQ) {
   if (!infile[line].isMeasure()) {
      return;
   }
   char charstring[2] = {0};
   charstring[0] = addchar;
   PerlRegularExpression pre;
   char buffer[1024] = {0};
   int j;
   int t;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      t = infile[line].getPrimaryTrack(j);
      if (t != track) {
         continue;
      }
      if (strchr(infile[line][j], addchar) != NULL) {
         // already is invisible
         continue;
      }
      strcpy(buffer, infile[line][j]);
      strcat(buffer, charstring);
      infile[line].setToken(j, buffer);
      if (!dashQ) {
         mergeAdjacentNotes(infile, line, t);
      }
   }
}


//////////////////////////////
//
// mergeAdjacentNotes --
//

void mergeAdjacentNotes(HumdrumFile& infile, int line, int track) {
   int lasti = 0;
   int lastj = 0;
   int i, j;
   int nexti = 0;
   int nextj = 0;
   int t;

   for (i=line+1; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         t = infile[i].getPrimaryTrack(j);
         if (t != track) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         nexti = i;
         nextj = j;
         break;
      }
      if (nexti > 0) {
         break;
      }
   }

   for (i=line-1; i>0; i--) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         t = infile[i].getPrimaryTrack(j);
         if (t != track) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         lasti = i;
         lastj = j;
         break;
      }
      if (lasti > 0) {
         break;
      }
   }

   if ((nexti == 0) || (lasti == 0)) {
      return;
   }

   char buffer[1024] = {0};
   PerlRegularExpression pre;
   Array<char> buffer2;

   if ((strchr(infile[lasti][lastj], 'r') != NULL) &&
       (strchr(infile[nexti][nextj], 'r') != NULL) ) {
      if ((strchr(infile[nexti][nextj], 'y') != NULL) &&
         (strchr(infile[lasti][lastj], 'y') != NULL)) {
         return;
      }
      strcpy(buffer, infile[nexti][nextj]);
      strcat(buffer, "yy");
      infile[nexti].setToken(nextj, buffer);
      return;
   }

   if (strchr(infile[lasti][lastj], 'l') != NULL) {
      // avoid terminal nulls
      return;
   }

   if ((strchr(infile[lasti][lastj], '[') != NULL) &&
       (strchr(infile[nexti][nextj], ']') != NULL) ) {

      if ((strchr(infile[nexti][nextj], 'y') != NULL) &&
         (strchr(infile[lasti][lastj], 'y') != NULL)) {
         return;
      }

      // hide tie on first note
      buffer2.setSize(strlen(infile[lasti][lastj]) + 1);
      strcpy(buffer2.getBase(), infile[lasti][lastj]);
      pre.sar(buffer2, "\\[", "[y", "g");
      infile[lasti].setToken(lastj, buffer2.getBase());
       
       // hide the entire second note
       strcpy(buffer, infile[nexti][nextj]);
       strcat(buffer, "yy");
       infile[nexti].setToken(nextj, buffer);
   }
}



//////////////////////////////
//
// addTerminalLongs -- place "l" marker on the last note of each voice 
//     (or chord in each voice).  Also the last note befour any 
//     double barlines.
//

int addTerminalLongs(HumdrumFile& infile) {
   int result = 0;
  
   Array<int> ktracks;
   infile.getTracksByExInterp(ktracks, "**kern");

   int i;
   for (i=0; i<ktracks.getSize(); i++) {
      result |= addTrackLongs(infile, ktracks[i]);
   }

   return result;
}



//////////////////////////////
//
// addTrackLongs -- add long markers to a specific track.
//

int addTrackLongs(HumdrumFile& infile, int track) {
   int i, j;
   int ptrack;
   int output = 0;
   int addQ = 1;
   char buffer[1024] = {0};
   for (i=infile.getNumLines()-1; i>=0; i--) {
      if (addQ == 0) {
         if (infile[i].isMeasure()) {
            for (j=0; j<infile[i].getFieldCount(); j++) {
               ptrack = infile[i].getPrimaryTrack(j);
               if (ptrack != track) {
                  continue;
               }
               if (strstr(infile[i][j], "||")) {
                  addQ = 1;
                  break;
               }
            }
         }
         continue;
      }
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         ptrack = infile[i].getPrimaryTrack(j);
         if (ptrack != track) {
            continue;
         }
         if (strchr(infile[i][j], '_') != NULL) {
            continue;
         }
         if (strchr(infile[i][j], ']') != NULL) {
            continue;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }
         if (strchr(infile[i][j], 'l') != NULL) {
            // don't mark a note which is already marked.
            addQ = 0; // clear the add state since already marked
            continue;
         }
         // found a note which should be marked.
         strcpy(buffer, infile[i][j]);
         strcat(buffer, "l");
         output = 1;
         infile[i].changeField(j, buffer);
         
         // disable addQ, but keep going on current line.
         addQ = 0;
      }
      
   }

   return output; 
}



//////////////////////////////
//
// setItem --
//

void setItem(Array >& anArray, int i, const char* aString) {
   anArray[i].setSize(strlen(aString)+1);
   strcpy(anArray[i].getBase(), aString);
}



//////////////////////////////
//
// printSectionInfo --
//

void printSectionInfo(void) {
   if (Section[0] == '\0') {
      return;
   }
   cout << "!!section: " << Section << "\n";
   cout << "!!!OMD:\t" << Section << "\n";
   Section[0] = '\0';
   Section.setSize(1);
}



//////////////////////////////
//
// convertRscaleTextToInterpretation --
//

int convertRscaleTextToInterpretation(HumdrumFile& infile, int line) {
   if (!infile[line].isComment()) {
      return 0;
   }
   int j;
   char buffer[1024] = {0};
   int hasRscale = 0;
   PerlRegularExpression pre;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], 
            "^!LO:TX:.*t=rscale:\\s*(\\d+[^:]*)", "i")) {
         strcpy(buffer, "!rscale:");
         strcat(buffer, pre.getSubmatch(1));
         infile[line].setToken(j, buffer);
         hasRscale = 1;
      } else {
      }
   }

   PerlRegularExpression pre2;
   int track;
   if (hasRscale) {
      for (j=0; j<infile[line].getFieldCount(); j++) {
         if (pre.search(infile[line][j], "^!rscale:\\s*(.*)\\s*", "i")) {
            track = infile[line].getPrimaryTrack(j);
            cout << "*rscale:" << pre.getSubmatch(1);
            if (pre2.search(pre.getSubmatch(), "(\\d+)/(\\d+)")) {
               Rscale[track] = atoi(pre2.getSubmatch(1));
               Rscale[track] /= atoi(pre2.getSubmatch(2));
            } else {
               Rscale[track] = atoi(pre2.getSubmatch(1));
            }
            infile[line].setToken(j, "!");
         } else {
            cout << "*";
         }
         if (j<infile[line].getFieldCount() - 1) {
            cout << "\t";
         }
      }
      cout << "\n";
   }

   return hasRscale;
}





//////////////////////////////
//
// printDefaultLabelExpansion --
//

void printDefaultLabelExpansion(HumdrumFile& infile, int line) {
   int i;
   char label[2] = {0};
   label[0] = ++SectionLabel;
   char buffer[1024] = {0};
   strcpy(buffer, "*>[");
   strcat(buffer, label);
   int count = 1;
   PerlRegularExpression pre;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isMeasure()) {
         continue;
      }
     
      if (pre.search(infile[i][0], "\\|\\||==|:")) {
         if (hasDataBelow(infile, i)) {
            count++;
            label[0]++;
            strcat(buffer, ",");
            strcat(buffer, label);
         }
      }
   }

   if (count <= 1) {
      return;
   }

   strcat(buffer, "]");
   int j;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      cout << buffer;
      if (j < infile[line].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";

   // print *>A label now
   for (j=0; j<infile[line].getFieldCount(); j++) {
      cout << "*>A";
      if (j < infile[line].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";
}



//////////////////////////////
//
// hasDataBelow -
//

int hasDataBelow(HumdrumFile& infile, int line) {
   int i;
   for (i=line+1; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         return 1;
      }
   }

   return 0;
}



//////////////////////////////
//
// printOutput --
//

void printOutput(HumdrumFile& infile, int terminalQ) {
   int firstBarline = 0;
   int dataFoundQ = 0;
   int i, j;
   int editorialQ = hasEditorial(infile);
   int insertedQ = 0;
   int labelinitQ = 0;
   int defaultPrinted = 0;

   Rscale.setSize(infile.getMaxTracks()+1);
   Rscale.setAll(1);

   PerlRegularExpression pre;

   Clef.setSize(infile.getMaxTracks()+1);
   Keysig.setSize(infile.getMaxTracks()+1);
   Timesig.setSize(infile.getMaxTracks()+1);
   Metsig.setSize(infile.getMaxTracks()+1);
   for (i=0; i<infile.getMaxTracks()+1; i++) {
      setItem(Clef, i, "*");
      setItem(Keysig, i, "*");
      setItem(Timesig, i, "*");
      setItem(Metsig, i, "*");
   }

   for (i=0; i<infile.getNumLines(); i++) {
      if (defaultPrinted && infile[i].isData()) {
         defaultPrinted = 0;
      }

      if ((defaultPrinted == 0) && infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].isClef(j)) {
               setItem(Clef, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isKeySig(j)) {
               setItem(Keysig, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isTimeSig(j)) {
               setItem(Timesig, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isMetSig(j)) {
               setItem(Metsig, infile[i].getPrimaryTrack(j), infile[i][j]);
            }
         }
      }

      if ((dataFoundQ == 0) && (firstBarline == 0) && infile[i].isMeasure()) {
         firstBarline = i;
         continue;;
      }

      if (infile[i].isMeasure() && hasDataBelow(infile, i) && 
          ((strstr(infile[i][0], "||") != NULL) ||
                                    (strstr(infile[i][0], ":") != NULL))) {
         if ((i > 0) && (strcmp(infile[i-1][0], "!!LO:LB:i:g=z") != 0)) {
            // printing line break provided that there is not one here already
            // (this filters out extra linebreaks for repeated use of jrpize program)
            cout << "!!LO:LB:i:g=z" << "\n";
         }
      }

      if (infile[i].isMeasure() && ((strstr(infile[i][0], "||") != NULL) ||
                                    (strstr(infile[i][0], ":") != NULL) ||
                                    (strstr(infile[i][0], "==") != NULL))) {
         turnOffAnyActiveRscales(infile, i);
      }

      if (infile[i].isInterpretation()) {
         checkAndPrintVox(infile, i);
      }
   
      if ((labelinitQ == 0) && infile[i].isAllClef()) {
         printDefaultLabelExpansion(infile, i);
      }

      convertRscaleTextToInterpretation(infile, i);

      if (infile[i].isSpineManipulator() && (firstBarline > 0)) {
         cout << infile[firstBarline] << "\n";
         firstBarline = 0;
      }

      if (hasSectionLabel(infile, i)) {
         printSectionLabel(infile, i);
         continue;
      }
   
      if ((infile[i].isLocalComment() || infile[i].isData()) 
            && (dataFoundQ == 0) && (firstBarline > 0)) {
         cout << infile[firstBarline] << "\n";
         firstBarline = 0;
         dataFoundQ = 1;
      }

      if (infile[i].isGlobalComment() || infile[i].isBibliographic()) {
         // filter out line/page breaks from the MusicXML, they are
         // not intended to be actual system breaks.
         if (pre.search(infile[i][0], "break:original")) {
            continue;
         }
      }

      clearMensurationComment(infile, i);

      if (infile[i].isNull()) {
         continue;
      }

      fixAccentedText(infile[i]);

      if (defaultPrinted) {
         if (cleanDefaultInterpretations(infile, i)) {
            continue;
         }
      }

      cout << infile[i] << "\n";

      if (infile[i].isMeasure()) {
         if (Section[0] != '\0') {
            printSectionInfo();
         }
      }

      if (defaultPrinted == 0) {
         if (infile[i].isAllTimeSig()) {
            processMensuration(infile, i);
         }
      }

      if (infile[i].isInterpretation()) {
         checkAndPrintInstAbbr(infile, i);
      }
      if ((!insertedQ) && (strcmp(infile[i][0], "*-") == 0)) {
         if (terminalQ && (!hasLongRDF(infile))) {
            cout << "!!!RDF**kern: l=long note in original notation" << "\n";
         }
         if (editorialQ && (!hasEditorialRDF(infile))) {
            cout << "!!!RDF**kern: i=editorial accidental" << "\n";
         }
         insertedQ = 1;
         for (int ii=i+1; ii<infile.getNumLines(); ii++) {
            cout << infile[ii] << "\n";
         }
         break;
      }

      // if (defaultPrinted == 0) {
      //    if (infile[i].isInterpretation()) {
      //       handleMensuration(infile, i);
      //    }
      // }

      if (infile[i].isMeasure() && ((strstr(infile[i][0], "||") != NULL) ||
                                     (strstr(infile[i][0], ":") != NULL))) {
         printLabel(infile, i);
         printDefaultKeyTimeMet(infile, i);
         defaultPrinted = 1;
      }

   }
}



//////////////////////////////
//
// cleanDefaultInterpretations --
//

int cleanDefaultInterpretations(HumdrumFile& infile, int line) {
   if (!infile[line].isInterpretation()) {
      return 0;
   }
   int j;
   int leftcount = 0;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (strcmp(infile[line][j], "*") == 0) {
         continue;
      }
      if (infile[line].isClef(j)) {
         infile[line].changeField(j, "*");
      } else if (infile[line].isKeySig(j)) {
         infile[line].changeField(j, "*");
      } else if (infile[line].isTimeSig(j)) {
         infile[line].changeField(j, "*");
      } else if (infile[line].isMetSig(j)) {
         infile[line].changeField(j, "*");
      } else {
         leftcount++;
      }
   }

   if (leftcount == 0) {
      return 1;
   } else {
      return 0;
   }
}


//////////////////////////////
//
// turnOffAnyActiveRscales --
//

void turnOffAnyActiveRscales(HumdrumFile& infile, int line) {
   int j;
   int track;
   int hasRscale = 0;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      if (Rscale[track] != 1) {
         hasRscale = 1;
         break;
      }
   }

   if (hasRscale == 0) {
      return;
   }

   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      if (Rscale[track] != 1) {
         cout << "*rscale:1";
         Rscale[track] = 1;
      } else {
         cout << "*";
      }
      if (j < infile[line].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";
   

}



//////////////////////////////
//
// printLabel --
//

void printLabel(HumdrumFile& infile, int line) {
   if (!infile[line].isMeasure()) {
      return;
   }

   SectionLabel++;

   int j;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      cout << "*>" << SectionLabel;
      if (j < infile[line].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";

}



//////////////////////////////
//
// clearMensurationComment --
//

void clearMensurationComment(HumdrumFile& infile, int line) {
   int j;
   if (!infile[line].isLocalComment()) {
      return;
   }
   PerlRegularExpression pre;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], 
            "^!LO:TX:.*t=(\\s*all\\s*:\\s*)?[mM]en[A-Z0-9]", "")) {
         infile[line].setToken(j, "!");
      }
   }
}



//////////////////////////////
//
// processMensuration --  Time signature just printed, so
//     look for a metrical signature in each track, printing
//     *met(C|) if time signature is 2/1 and there is no mensuration
//     or
//     *met(O) if time signature is 3/1 and there is no mensuration
//

void processMensuration(HumdrumFile& infile, int line) {
   if (!infile[line].isInterpretation()) {
      return;
   }
   char buffer[1024] = {0};
   int j;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      getMensuration(buffer, infile, line, j);
      if (strcmp(buffer, "*ZZY") == 0) {
         cout << "*Q";
      } else {
         cout << buffer;
      }
      setItem(Metsig, infile[line].getPrimaryTrack(j), buffer);
      if (j < infile[line].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";

   checkForPrimaryMensurationNeed(infile, line);
}



//////////////////////////////
//
// absorbMensurations --  used by default mensuration processing
//

void absorbMensurations(HumdrumFile& infile, int line) {
   char buffer[1024] = {0};
   int i, j;
   for (i=line; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         break;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         getMensuration(buffer, infile, i, j);
         if (strlen(buffer) > 0) {
            if (strcmp(buffer, "*ZZY") == 0) {
               // no mensuration, handle default for meter elsewhere.
            } else {
               setItem(Metsig, infile[i].getPrimaryTrack(j), buffer);
            }
         }
      }
   }
}



//////////////////////////////
//
// checkForPrimaryMenurationNeed -- If all mensurations in **kern spines are 
//    not the same, then print the majority mensuration in a global comment.
//

void checkForPrimaryMensurationNeed(HumdrumFile& infile, int line) {
   int i, j;
   int track, lasttrack;

   Array<int> tracks;
   infile[line].getTracksByExInterp(tracks, "**kern");

   if (tracks.getSize() <= 1) {
      return;
   }
 
   int equalQ = 1;
   for (i=1; i<tracks.getSize(); i++) {
      track = tracks[i];
      lasttrack = tracks[i-1];
      if (strcmp(Metsig[track].getBase(), Metsig[lasttrack].getBase()) != 0) {
         equalQ = 0;
         break;
      }
   }

   if (equalQ) {
      // all mensurations are the same, so don't print a 
      // "!!primary-mensuration:" line.
      return;
   }

   Array<int> counts;
   counts.setSize(tracks.getSize());
   counts.setAll(0);

   int ti;
   int tj;

   for (i=0; i<tracks.getSize(); i++) {
      ti = tracks[i];
      for (j=0; j<tracks.getSize(); j++) {
         tj = tracks[j];
         if (strcmp(Metsig[ti].getBase(), Metsig[tj].getBase()) == 0) {
            counts[tj]++;
            break;
         }
      }
   }

   // find maximum count
   int maxi = 0;

   for (i=1; i<counts.getSize(); i++) {
      if (counts[i] > counts[maxi]) {
         maxi = i;
      }
   }

   if (strcmp(Metsig[maxi].getBase(), "*") == 0) {
      return;
   }
   if (strcmp(Metsig[maxi].getBase(), "") == 0) {
      return;
   }

   // found the most common mensuration, so assume it is the 
   // primary mensuration:
   cout << "!!primary-mensuration: " << Metsig[maxi].getBase()+1 << endl;

}



//////////////////////////////
//
// getMensuration --
//

char* getMensuration(char* buffer, HumdrumFile& infile, int line, int spine) {
   strcpy(buffer, "*ZZY");
   int foundDataQ = 0;
   int i, j;
   PerlRegularExpression pre;

   if (!infile[line].isExInterp(spine, "**kern")) {
      strcpy(buffer, "*");
      return buffer;
   }

   int targettrack = infile[line].getPrimaryTrack(spine);
   int tracki = 0;
   int trackj = 0;
   
   int mencount = 0;
   int lasti = 0;
   int lastj = 0;

   int timesigi = 0;
   int timesigj = 0;

   int track;

   for (i=line+1; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         foundDataQ = i;
         break;
      }
      if (!infile[i].isLocalComment()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], "!") == 0) {
            continue;
         }
         track = infile[i].getPrimaryTrack(j);
         if (pre.search(infile[i][j], 
            "^!LO:TX:.*t=(\\s*all\\s*:\\s*)?[mM]en[A-Z0-9]", "")) {
            mencount++;
            lasti = i;
            lastj = j;
            if (track == targettrack) {
               tracki = i;
               trackj = j;
            }
         }
         if (pre.search(infile[i][j], "^\\*M\\d+/\\d+")) {
            if (track == targettrack) {
               timesigi = i;
               timesigj = j;
            }
         }
      }
   }

   if (!foundDataQ) {
      strcpy(buffer, "*ZZZ");
      return buffer;
   }

   int ii = tracki; 
   int jj = trackj; 
   if (mencount == 1) {
      ii = lasti;
      jj = lastj;
   }
   if (pre.search(infile[ii][jj], 
         "^!LO:TX:.*t=(\\s*all\\s*:\\s*)?([Mm]en[^\\s]*)", "i")) {
      if (infile[ii][jj][0] != '!') {
         cerr << "Funny error on line " << ii+1 << " column " << jj+1 << endl;
         cerr << infile[ii] << endl;
         exit(1);
      }
      convertMenIntoMet(buffer, pre.getSubmatch(2));
      if (mencount != 1) {
         infile[ii].setToken(jj, "!");
      }
      return buffer;
   }

   if ((ii == 0) || (jj == 0)) {
      // set a default mensuration since none found in data
      if (pre.search(infile[line][spine], "^\\*M2/1$")) {
         strcpy(buffer, "*met(C|)");
         return buffer;
      } else if (pre.search(infile[line][spine], "^\\*M3/1$")) {
         strcpy(buffer, "*met(O)");
         return buffer;
      } else {
         return buffer;
         if (timesigi != 0) {
            strcpy(buffer, "*met(___)");
         } else {
            strcpy(buffer, "*met(y)");
         }
      }
      return buffer;
   }

   return buffer;
}



//////////////////////////////
//
// convertMenIntoMet --
//

char*  convertMenIntoMet(char* buffer, const char* mensur) {
   PerlRegularExpression pre;

   if (pre.search(mensur, "MenCutC\\s*$", "i")) {
      strcpy(buffer, "*met(C|)");
      return buffer;
   }
   if (pre.search(mensur, "MenCircle\\s*$", "i")) {
      strcpy(buffer, "*met(O)");
      return buffer;
   }
   if (pre.search(mensur, "MenC3\\s*$", "i")) {
      strcpy(buffer, "*met(C3)");
      return buffer;
   }
   if (pre.search(mensur, "MenOover3\\s*$", "i")) {
      strcpy(buffer, "*met(O/3)");
      return buffer;
   }
   if (pre.search(mensur, "MenC\\s*$", "i")) {
      strcpy(buffer, "*met(C)");
      return buffer;
   }
   if (pre.search(mensur, "MenCircle2\\s*$", "i")) {
      strcpy(buffer, "*met(O2)");
      return buffer;
   }
   if (pre.search(mensur, "MenO2\\s*$", "i")) {
       // not legal, but include in case of data error:
      strcpy(buffer, "*met(O2)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCircle\\s*$", "i")) {
      strcpy(buffer, "*met(O|)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutC3\\s*$", "i")) {
      strcpy(buffer, "*met(C|3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCircleDot\\s*$", "i")) {
      strcpy(buffer, "*met(O.)");
      return buffer;
   }
   if (pre.search(mensur, "MenC2\\s*$", "i")) {
      strcpy(buffer, "*met(C2)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutC2\\s*$", "i")) {
      strcpy(buffer, "*met(C|2)");
      return buffer;
   }
   if (pre.search(mensur, "Men2\\s*$", "i")) {
      strcpy(buffer, "*met(2)");
      return buffer;
   }
   if (pre.search(mensur, "Men3\\s*$", "i")) {
      strcpy(buffer, "*met(3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCDot\\s*$", "i")) {
      strcpy(buffer, "*met(C.)");
      return buffer;
   }
   if (pre.search(mensur, "Men3over2\\s*$", "i")) {
      strcpy(buffer, "*met(3/2)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCircle3\\s*$", "i")) {
      strcpy(buffer, "*met(O|3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCircle3Over2\\s*$", "i")) {
      strcpy(buffer, "*met(O|3/2)");
      return buffer;
   }
   if (pre.search(mensur, "MenCircle3\\s*$", "i")) {
      strcpy(buffer, "*met(O3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCOver3\\s*$", "i")) {
      strcpy(buffer, "*met(C|/3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCOver2\\s*$", "i")) {
      strcpy(buffer, "*met(C|/2)");
      return buffer;
   }
   if (pre.search(mensur, "MenCircleOver3\\s*$", "i")) {
      strcpy(buffer, "*met(O/3)");
      return buffer;
   }
   if (pre.search(mensur, "MenCutCDot\\s*$", "i")) {
      strcpy(buffer, "*met(C.|)");
      return buffer;
   }

   cerr << "ERROR: unknown mensuration: " << mensur << endl;
   exit(1);
}



//////////////////////////////
//
// printDefaultKeyTimeMet --
//

void printDefaultKeyTimeMet(HumdrumFile& infile, int line) {
   if (!infile[line].isMeasure()) {
      return;
   }

   absorbMensurations(infile, line);

   int i, j;
   int datafound = 0;
   int hasMetsig = 0;

   // search for new key,time,met data at the current
   // location and store it before printing the default
   // key,time,met.
   for (i=line+1; i<infile.getNumLines(); i++) {
      if (infile[i].isData()) {
         datafound = 1;
         break;
      }
      if (infile[i].isInterpretation()) {
         for (j=0; j<infile[i].getFieldCount(); j++) {
            if (infile[i].isClef(j)) {
               setItem(Clef, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isKeySig(j)) {
               setItem(Keysig, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isTimeSig(j)) {
               setItem(Timesig, infile[i].getPrimaryTrack(j), infile[i][j]);
            } else if (infile[i].isMetSig(j)) {
               hasMetsig = 1;
            }
         }
      }
   }

   int track;


   // print the prevailing key,time,met information
   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      cout << Clef[track];
      if (j<infile[line].getFieldCount()-1) {
         cout << "\t";
      }
   }
   cout << "\n";

   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      cout << Keysig[track];
      if (j<infile[line].getFieldCount()-1) {
         cout << "\t";
      }
   }
   cout << "\n";

   for (j=0; j<infile[line].getFieldCount(); j++) {
      track = infile[line].getPrimaryTrack(j);
      cout << Timesig[track];
      if (j<infile[line].getFieldCount()-1) {
         cout << "\t";
      }
   }
   cout << "\n";

   if (hasMetsig == 0) {
      for (j=0; j<infile[line].getFieldCount(); j++) {
         track = infile[line].getPrimaryTrack(j);
         cout << Metsig[track];
         if (j<infile[line].getFieldCount()-1) {
            cout << "\t";
         }
      }
      cout << "\n";
      checkForPrimaryMensurationNeed(infile, line);
   }

}



//////////////////////////////
//
// hasSectionLabel -- true if text start with [Ss]ection:
//

int hasSectionLabel(HumdrumFile& infile, int line) {
   int j;
   PerlRegularExpression pre;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], 
            "^!LO:TX:.*t=\\s*[Ss][Ee][Cc][Tt][Ii][Oo][Nn]\\s*\\:\\s*([^:]*)\\s*$")) {
         return 1;
      } 
   }

   return 0;
}



//////////////////////////////
//
// wrongSideOfBarline -- return true if current line is before a barline
//    (true) or dataline (false)
//

int wrongSideOfBarline(HumdrumFile& infile, int line) {
   int i;
   for (i=line+1; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         return 1;
      } else if (infile[i].isData()) {
         return 0;
      }
   }
   return 0;
}



//////////////////////////////
//
// printSectionLabel --
//

void printSectionLabel(HumdrumFile& infile, int line) {
   int other = 0;
   int j;
   Array<char> string;
   PerlRegularExpression pre;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], 
            "^!LO:TX:.*t=\\s*[Ss][Ee][Cc][Tt][Ii][Oo][Nn]\\s*\\:\\s*([^:]*)\\s*$")) {
         string.setSize(strlen(pre.getSubmatch(1))+1);
         strcpy(string.getBase(), pre.getSubmatch(1));
         pre.sar(string, ":", ":", "g");
         if (wrongSideOfBarline(infile, line)) {
            Section.setSize(strlen(string.getBase()) + 1);
            strcpy(Section.getBase(), string.getBase());
         } else {
            cout << "!!section: " << string << "\n";
            cout << "!!!OMD: " << string << "\n";
         }
         infile[line].setToken(j, "!");
      } else if (strcmp(infile[line][j], "!")) {
         // do nothing
      } else {
         other = 1;
      }
   }

   if (!infile[line].isNull()) {
      cout << infile[line] << "\n";
   }
}




//////////////////////////////
//
// checkAndPrintVox -- look for instrument names, and add an
//    abbreviation underneath it.
//

void checkAndPrintVox(HumdrumFile& infile, int line) {
   int j;
   if (!infile[line].isInterpretation()) {
      return;
   }
   PerlRegularExpression pre;

   int hasInstrument = 0;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], "^\\*I\"")) {
         hasInstrument = 1;
         break;
      }
   }

   if (!hasInstrument) {
      return;
   }
 
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], "^\\*I\"\\s*(.*)\\s*")) {
         cout << "*Ivox";
      } else {
         cout << "*";
      }
      if (j<infile[line].getFieldCount()-1) {
         cout << "\t";
      }
   }
   cout << "\n";
}



//////////////////////////////
//
// checkAndPrintInstAbbr -- look for instrument names, and add an
//    abbreviation underneath it.
//

void checkAndPrintInstAbbr(HumdrumFile& infile, int line) {
   int j;
   if (!infile[line].isInterpretation()) {
      return;
   }
   PerlRegularExpression pre;

   int hasInstrument = 0;
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], "^\\*I\"")) {
         hasInstrument = 1;
         break;
      }
   }

   if (!hasInstrument) {
      return;
   }
 
   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (pre.search(infile[line][j], "^\\*I\"\\s*(.*)\\s*")) {
         printAbbreviation(pre.getSubmatch(1));
      } else {
         cout << "*";
      }
      if (j<infile[line].getFieldCount()-1) {
         cout << "\t";
      }
   }
   cout << "\n";
}



////////////////////////////////////////
//
//  printAbbreviation --
//

void printAbbreviation(const char* fullname) {

   PerlRegularExpression pre;

   if (pre.search(fullname, "^Superius", "i")) {
      cout << "*I'S";
   } else if (pre.search(fullname, "^Soprano", "i")) {
      cout << "*I'S";
   } else if (pre.search(fullname, "^Discantus", "i")) {
      cout << "*I'D";
   } else if (pre.search(fullname, "^Altus", "i")) {
      cout << "*I'A";
   } else if (pre.search(fullname, "^Alto", "i")) {
      cout << "*I'A";
   } else if (pre.search(fullname, "^Tenor", "i")) {
      cout << "*I'T";
   } else if (pre.search(fullname, "^Tenore", "i")) {
      cout << "*I'T";
   } else if (pre.search(fullname, "^Contratenor", "i")) {
      cout << "*I'Ct";
   } else if (pre.search(fullname, "^Contra", "i")) {
      cout << "*I'C";
   } else if (pre.search(fullname, "^Cantus", "i")) {
      cout << "*I'Cn";
   } else if (pre.search(fullname, "^Canto", "i")) {
      cout << "*I'Cto";
   } else if (pre.search(fullname, "^Quinto", "i")) {
      cout << "*I'Qto";
   } else if (pre.search(fullname, "^Bassus", "i")) {
      cout << "*I'B";
   } else if (pre.search(fullname, "^Bass", "i")) {
      cout << "*I'B";
   } else if (pre.search(fullname, "^Vagans", "i")) {
      cout << "*I'V";
   } else {
      cout << "*I'XXX";
      cerr << "WARNING: unknown instrument name: " << fullname << endl;
   }

   if (pre.search(fullname, "(\\d+[^\\s]*)")) {
      cout << pre.getSubmatch(1);
   }

   return;
}



////////////////////////////////////////
//
// handleMensuration --
//

void handleMensuration(HumdrumFile& infile, int line) {
   int& i = line;
   int j;

   PerlRegularExpression pre;

   if (!infile[i].isInterpretation()) {
      return;
   }

   if (i >= infile.getNumLines()-1) {
      // should never get here, but just in case...
      return;
   }

   int hasmeter = 0;
   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (pre.search(infile[i][j], "^\\*M\\d")) {
         hasmeter = 1;
         break;
      }
   }

   if (!hasmeter) {
      return;
   }
  
   if (strncmp(infile[i+1][0], "*met(", 5) == 0) {
      // don't add *met line if there already is one there.
      return;
   }

   /* Mensuration added in another location now ...   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (strcmp(infile[i][j], "*M2/1") == 0) {
         cout << "*met(C|)x";
      } else if (strcmp(infile[i][j], "*M3/1") == 0) {
         cout << "*met(O)x";
      } else {
         if (infile[i].isExInterp(j, "**kern")) {
            cout << "*met()x";
         } else {
            cout << "*";
         }
      }
      if (j<infile[i].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";
   */
}



//////////////////////////////
//
// hasLongRDF -- returns true if a bibliographic record starts with:
//      !!!RDF**kern: l=long
//

int hasLongRDF(HumdrumFile& infile) {
   int i;
   PerlRegularExpression pre;
   for (i=infile.getNumLines()-1; i>=0; i--) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], 
            "^!!!RDF\\*\\*kern\\s*:\\s*l\\s*=\\s*long", "i")) {
         return 1;
      }
   }

   return 0;
}



//////////////////////////////
//
// hadEditorial -- true if any **kern note has an "i" marker on it.
//

int hasEditorial(HumdrumFile& infile) {
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strchr(infile[i][j], 'i') != NULL) {
            return 1;
         }
      }
   }
   return 0;
}



//////////////////////////////
//
// hasEditorialRDF -- returns true if there is a bibliographic record
//       starting with:
//      !!!RDF**kern: i=edit
//

int hasEditorialRDF(HumdrumFile& infile) {
   int i;
   PerlRegularExpression pre;
   for (i=infile.getNumLines()-1; i>=0; i--) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (pre.search(infile[i][0], 
            "^!!!RDF\\*\\*kern\\s*:\\s*i\\s*=\\s*edit", "i")) {
         return 1;
      }
   }

   return 0;
}


////////////////////
//
// fixAccentedText --
//

void fixAccentedText(HumdrumRecord& aRecord) {
   int i;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (!strcmp(aRecord[i], ".")) {
         continue;
      }
      if (aRecord.isExInterp(i, "**text") || aRecord.isExInterp(i, "**silbe")) {
         checkText(aRecord, i);         
      }
   }
}



//////////////////////////////
//
// checkText --
//

void checkText(HumdrumRecord& aRecord, int index) {
   PerlRegularExpression pre;
   if (!pre.search(aRecord[index], "[^a-zA-Z0-9,. ='\"!@#$%^&*(){}\\|?/><-]")) {
      return;
   }
   Array<char> text;
   text.setSize(strlen(aRecord[index] + 1));
   strcpy(text.getBase(), aRecord[index]);
   pre.sar(text, "\xc5\xbd", "é", "g");

   aRecord.setToken(index, text.getBase());
}



//////////////////////////////
//
// checkOptions -- validate and process command-line options.
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b");              // determine bad input line num
   opts.define("author=b");             // author of program
   opts.define("version=b");            // compilation info
   opts.define("example=b");            // example usages
   opts.define("h|help=b");             // short description
   opts.process(argc, argv);
   
   // handle basic options:
   if (opts.getBoolean("author")) {
      cout << "Written by Craig Stuart Sapp, "
           << "craig@ccrma.stanford.edu, Dec 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 15 Dec 2011" << endl;
      cout << "compiled: " << __DATE__ << endl;
      cout << MUSEINFO_VERSION << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

}



//////////////////////////////
//
// example -- example usage of the quality program
//

void example(void) {
   cout <<
   "                                                                         \n"
   << endl;
}



//////////////////////////////
//
// usage -- gives the usage statement for the meter program
//

void usage(const char* command) {
   cout <<
   "                                                                         \n"
   << endl;
}



// md5sum: bf3940a816bf4437a2dc40ac78561845 jrpize.cpp [20130206]