//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Nov 23 05:24:14 PST 2009
// Last Modified: Tue Nov 23 09:54:40 PST 2010 added more features
// Last Modified: Wed Jan 12 07:00:38 PST 2011 added ending note marking
// Last Modified: Tue Jan 18 11:10:38 PST 2011 added --mstart option
// Last Modified: Sun Feb 20 18:38:07 PST 2011 added --percent option
// Last Modified: Thu Feb 24 17:10:34 PST 2011 added --file option
// Filename:      ...sig/examples/all/theloc.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/theloc.cpp
// Syntax:        C++; museinfo
//
// Description:   Identify the location of an index note in a files as 
//                output from "themax --location".
//
// Todo: If a mark is added with -m or --mchar, the program should
// complain and exit if the mark already exists.  But probably allow
// the -m to have the !!!RDF be overwritten with the same marker.

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

#include <string.h>
#include <stdio.h>
#include <unistd.h>

#ifndef OLDCPP
   #include <iostream>
   #include <fstream>
#else
   #include <iostream.h>
   #include <fstream.h>
#endif


// Function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      processData(istream& input);
void      extractDataFromInputLine(Array<char>& filename, 
                                  Array<char>& voicename, int& track, 
                                  int& subtrack, Array<int>& starts, 
                                  Array<int>& endings, char* inputline);
void      prepareSearchPaths(Array<Array<char> >& paths, 
                                 const char* pathlist);
int       fileexists(Array<char>& jointname, Array<char>& filename,
                                 Array<char>& path);
void      getFileAndPath(Array<char>& fileandpath, 
                                 Array<char>& filename, 
                                 Array<Array<char> >& paths);
int       findNote(int nth, HumdrumFile& infile, int& cur, 
                                 int& row, int& col, int track, int subtrack,
                                 int& measure);
void      fillMeterInfo(HumdrumFile& infile, 
                                 Array<RationalNumber>& meterbot, int track);
void      markNotes(HumdrumFile& infile, int row, int col, 
                                 int track, int subtrack, int matchlen, 
                                 const char* marker);
void      processDataLine(HumdrumFile& infile, const char* inputline, 
                                 Array<char>& filename, 
                                 Array<char>& lastfilename, 
                                 Array<char>& voicename, int track, 
                                 int subtrack, Array<int>& starts, 
                                 Array<int>& endings);
void      printDash(void);
void      displayNoteLocationInfo(HumdrumFile& infile, int num, int row, 
                                int col, int measure,
                                Array<RationalNumber>& meterbot);

// User interface variables:
Options   options;
Array<Array<char> > paths;
int         debugQ       = 0;     // used with --debug option
int         percentQ     = 0;     // used with -P option
int         dirdropQ     = 0;     // used with -D option
int         dispLineQ    = 0;     // used with -l option
int         dispColumnQ  = 0;     // used with -c option
int         dispNoteQ    = 1;     // used with -N option
int         dispAbsBeatQ = 0;     // used with -a option
int         dispMeasureQ = 1;     // used with -M option
int         dispQBeatQ   = 0;     // used with -q option
int         dispBeatQ    = 1;     // used with -B option
int         rationalQ    = 0;     // used with -r option
int         markQ        = 0;     // used with --mark option
int         matchlistQ   = 0;     // used with --matchlist option
int         mark2Q       = 0;     // used with --mark2 option
int         tieQ         = 0;     // used with --tie option
int         graceQ       = 1;     // used with -G option
int         doubleQ      = 0;     // used with --mstart option
int         fileQ        = 0;     // used with --file option
const char* Filename     = "";    // used with --file option
int         matchlen     = 1;     // used with --mark option
const char* marker       = "@";   // used with --marker option


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

int main(int argc, char** argv) {
   // process the command-line options
   checkOptions(options, argc, argv);
 
   ifstream input;
   int numinputs = options.getArgumentCount();
   for (int i=0; i<numinputs || i==0; i++) {
      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         processData(cin);
      } else {
         input.open(options.getArg(i+1));
         processData(input);
         input.close();
      }
   }

   return 0;
}

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


//////////////////////////////
//
// processData --
//

void processData(istream& input) {
   #define LINESIZE 100000
   Array<char> filename;
   Array<char> lastfilename;
   lastfilename.setSize(1);
   lastfilename[0] = '\0';
   Array<int> starts;
   Array<int> endings;
   Array<char> voicename;
   voicename.setSize(1);
   voicename[0] = '\0';
   int track;
   int subtrack;
   HumdrumFile infile;
   char inputline[LINESIZE] = {0};

   do {
      input.getline(inputline, LINESIZE);
      if (input.eof()) {
         break;
      }

      if (strncmp(inputline, "#NOGRACE", strlen("#NOGRACE")) == 0) {
         graceQ = 0;
         continue;
      } else if (strncmp(inputline, "#GRACE", strlen("#GRACE")) == 0) {
         graceQ = 1;
         continue;
      } else if (strncmp(inputline, "#", strlen("#")) == 0) {
         // unknown control message, just ignore and don't try to process.
         continue;
      }

      extractDataFromInputLine(filename, voicename, track, subtrack, starts, 
            endings, inputline);
      if (track > 0) {
         processDataLine(infile, inputline, filename, lastfilename, voicename,
               track, subtrack, starts, endings);
         lastfilename = filename;
      } else {
         // echo input lines which are not understood (comments?)
         cout << inputline << endl; 
      }

   } while (!input.eof());


   while (!input.eof()) {

   }

   // flush any data needing to be printed (such as for markQ):
   filename.setSize(1);
   filename[0] = '\0';
   processDataLine(infile, "", filename, lastfilename, voicename, track, 
         subtrack, starts, endings);
}



//////////////////////////////
//
// processDataLine --
//

void processDataLine(HumdrumFile& infile, const char* inputline, 
      Array<char>& filename, Array<char>& lastfilename, 
      Array<char>& voicename, int track, int subtrack, Array<int>& starts, 
      Array<int>& endings) {

   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   PerlRegularExpression pre3;

   Array<char> fileandpath;
   if (fileQ) {
      filename.setSize(strlen(Filename)+1);
      strcpy(filename.getBase(), Filename);
      pre.sar(filename, ":", ":", "g");
   }
   if (strcmp(filename.getBase(), "") != 0) {
      getFileAndPath(fileandpath, filename, paths);
      if (debugQ) {
         cout << "FOUND FILE: " << fileandpath << endl;
      }
      if (strlen(fileandpath.getBase()) == 0) {
         // print original line without processing content since file not found
         cout << inputline << endl;
         return;
      }
   }

   Array<char> tempstr;
   tempstr.setSize(1);
   tempstr[0] = '\0';
   if (strcmp(filename.getBase(), lastfilename.getBase()) != 0) {
      if (strcmp(lastfilename.getBase(), "") != 0) {
         if (markQ) {
            cout << infile;
            if (pre.search(marker, "^\\s*([^\\s])\\s+", "") ||
                pre.search(marker, "^\\s*([^\\s])$", "")
                     ) {
               cout << "!!!RDF**kern: " << pre.getSubmatch(1);
               cout << "= matched note";
               if (pre.search(marker, 
                     "color\\s*=\\s*\"?([^\\s\"\\)\\(,;]+)\"?", "")) {

                  tempstr.setSize(strlen(marker) + 1);
                  strcpy(tempstr.getBase(), marker);
                  pre3.sar(tempstr, "^\\s*[^\\s]\\s+", "", "");
                  pre3.sar(tempstr, "color\\s*=\\s*\"?[^\\s\"\\)\\(,;]+\"?", "", "g");
                  pre3.sar(tempstr, "^[\\s:,;-=]+", "", "");
                  pre3.sar(tempstr, "[\\s,:;-=]+$", "", "");

                  if (!pre2.search(pre.getSubmatch(1), "#", "")) {
                     if (pre2.search(pre.getSubmatch(1), "^[0-9a-f]+$", "i")) {
                        if (strlen(pre.getSubmatch(1)) == 6) {
                           cout << ", color=\"#" << pre.getSubmatch(1) << "\"";
                        } else {
                           cout << ", color=\"" << pre.getSubmatch(1) << "\"";
                        }
                     } else {
                        cout << ", color=\"" << pre.getSubmatch(1) << "\"";
                     }
                  } else {
                     cout << ", color=\"" << pre.getSubmatch(1) << "\"";
                  }
               }
               if (strlen(tempstr.getBase()) > 0) {
                  cout << " " << tempstr;
               }
               cout << endl;
            }
            if (!mark2Q) {
               cout << "!!!MATCHLEN:\t" << matchlen << endl;
            }
            if (strcmp(filename.getBase(), "") == 0) {
               // empty filename is a dummy to force last line of 
               // input to be processed correctly if markQ or similar is used.
               return;
            }
         }
      }
      if (strcmp(filename.getBase(), "") != 0) {
         infile.clear();
         infile.read(fileandpath.getBase());
         infile.analyzeRhythm("4"); // only by quarter-note beats for now
      }
   }

   if (strcmp(filename.getBase(), "") == 0) {
      // empty filename is a dummy, but shouldn't ever get here.
      return;
   }

   if (matchlistQ && markQ) {
      cout << "!!MATCHES:\t";
   }

   if (fileQ) {
      fileandpath.setSize(strlen(Filename)+1);
      strcpy(fileandpath.getBase(), Filename);
   }

   if (matchlistQ) {
      cout << fileandpath;
      cout << ":";
      cout << voicename;
      cout << ":";
      cout << track;
      cout << "\t";
   }
   
   int row = 0;
   int col = 0;
   int cur = 0;   // current nth numbered note in the given track

   int erow = 0;
   int ecol = 0;
   int ecur = 0;   // current nth numbered note in the given track

   int measure = 1;
   int emeasure = 1;
   if (infile.getPickupDuration() > 0.0) {
      measure = 0;
      emeasure = 0;
   }

   Array<RationalNumber> meterbot;
   meterbot.setSize(0);
   if (dispBeatQ) {
      fillMeterInfo(infile, meterbot, track);
   }

   int i;
   int state;
   int estate;
   for (i=0; i<starts.getSize(); i++) {
      state = findNote(starts[i], infile, cur, row, col, track, 
            subtrack, measure);
      if (state == 0) {
         continue;
      }
      if ((endings.getSize() >0) && (endings[i] >= 0)) {
         estate = findNote(endings[i], infile, ecur, erow, ecol, 
         track, subtrack, emeasure);
      } else {
         estate = 0;
      }
      if (markQ) {
         if ((endings.getSize() > 0) && (endings[i] >= 0)) {
            markNotes(infile, row, col, track, subtrack, 
                  endings[i]-starts[i]+1, marker);
         } else {
            markNotes(infile, row, col, track, subtrack, matchlen, marker);
         }
      }
      if (matchlistQ) {
         displayNoteLocationInfo(infile, starts[i], row, col, measure, meterbot);
         if ((endings.getSize() > 0) && (endings[i] >= 0) && estate) {
            printDash();
            displayNoteLocationInfo(infile, endings[i], erow, ecol, emeasure, 
                  meterbot);
         }
      }

      if (matchlistQ) {
         if (i < starts.getSize()-1) {
            cout << " ";
         }
      }
   }
   if (matchlistQ) {
      cout << endl;
   }

}



//////////////////////////////
//
// printDash -- have to do a complicated system, since markQ would not
// involve a dash being printed.
//

void printDash(void) {
   if (dispNoteQ || dispLineQ || dispColumnQ || dispAbsBeatQ || dispMeasureQ ||
         dispBeatQ || percentQ || dispQBeatQ) {
      cout << "-";  
   }
}



//////////////////////////////
//
// displayNoteLocationInfo --
//

void displayNoteLocationInfo(HumdrumFile& infile, int num, int row, int col,
      int measure, Array<RationalNumber>& meterbot) {

   RationalNumber four(4, 1);

   if (dispNoteQ) {
      cout << num;
   }
   if (dispLineQ) {
      cout << "L" << row+1;
   }
   if (dispColumnQ) {
      cout << "C" << col+1;
   }
   if (dispAbsBeatQ) {
      if (rationalQ) {
         cout << "A" << infile[row].getAbsBeatR();
      } else {
         cout << "A" << infile[row].getAbsBeat();
      }
   }
   if (percentQ) {
      double percent = infile.getTotalDuration();
      if (percent > 0.0) {
         percent = int(infile[row].getAbsBeat() / percent * 1000.0 
               + 0.5) / 10.0;
      }
      cout << "P" << percent;
   }
   if (dispMeasureQ) {
      cout << "=" << measure;
   }
   if (dispBeatQ) {
      if (rationalQ) {
         RationalNumber tval = (infile[row].getBeatR()-1) * 
                                  (meterbot[row] / four) + 1;
         cout << "B";
         tval.printTwoPart(cout);
      } else {
         cout << "B" << (infile[row].getBeat()-1) * 
                         (meterbot[row].getFloat() / 4.0) + 1;
      }
   }
   if (dispQBeatQ) {
      if (rationalQ) {
         cout << "Q" << infile[row].getBeatR();
      } else {
         cout << "Q" << infile[row].getBeat();
      }
   }
}



//////////////////////////////
//
// markNotes -- mark notes in match sequence (for possible later output as HTML
//     with colors).
//

void markNotes(HumdrumFile& infile, int row, int col, int track, int subtrack,
      int matchlen, const char* marker) {

   PerlRegularExpression pre;
   char markchar[2] = {0};
   if (pre.search(marker, "^\\s*([^\\s])\\s+", "") ||
         pre.search(marker, "^\\s*([^\\s])$", "")) {
      markchar[0] = pre.getSubmatch(1)[0];
   }

   int tiestate = 0;
   Array<char> newdata;
   newdata.setSize(1000);
   newdata.setGrowth(1000);
   newdata.setSize(0);
   int foundcount = 0;
   int i, j;
   int scount = 0;
   for (i=row; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      if (foundcount >= matchlen) {
         break;
      }
      scount = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (track != infile[i].getPrimaryTrack(j)) {
            // ignore the current spine if not the correct one
            continue;
         }
         scount++;
         if (subtrack != scount) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            // don't count null tokens
            break;
         }
         if (strchr(infile[i][j], 'r') != NULL) {
            // don't count rests
            break;
         }
         if ((!graceQ) && ((strchr(infile[i][j], 'q') != NULL) ||
                              (strchr(infile[i][j], 'Q') != NULL))
               ) {
            // ignore grace notes if not supposed to count
            break;
         }

         if (strchr(infile[i][j], '[') != NULL) {
            if (tieQ) {
               foundcount--;  // suppress from count later on
               tiestate = 1;
            } 
         } else if (strchr(infile[i][j], ']') != NULL) {
            if (tieQ) {
               // don't subtract one from foundcount (this is the last note)
               tiestate = 1;
            } else {
               // don't color tied note unless asked to
               break;
            }
         } else if (strchr(infile[i][j], '_') != NULL) {
            if (tieQ) {
               foundcount--;  // suppress from count later on
               if (tiestate == 0) {
                  // a weird case where the tie starts with a medial
                  // tie.  This is can legally occur when there is a
                  // multiple ending which a slur crosses.
                  foundcount++;
               }
               tiestate = 1;
            } else {
               // don't color tied note unless asked to
               continue;
            }
         } else {
            tiestate = 0;
         }

         foundcount++;

         if (strstr(infile[i][j], markchar) != NULL) {
            // already marked (perhaps overlapping match)
            break;
         }
         newdata.setSize(strlen(infile[i][j]) + strlen(markchar) + 1);
         strcpy(newdata.getBase(), infile[i][j]);
         strcat(newdata.getBase(), markchar);
         if (doubleQ && (foundcount == 1)) {
            strcat(newdata.getBase(), markchar);
         }
         infile[i].changeField(j, newdata.getBase());
         break;

         if (foundcount >= matchlen) {
            goto veryend;
            // if (tieQ && (strchr(infile[i][j], '[') != NULL)) {
            //    foundcount--;
            // } else if (tieQ && (strchr(infile[i][j], '_') != NULL)) {
            //    foundcount--;
            // }
         } 
      }
   }
veryend: ;
}



//////////////////////////////
//
// fillMeterInfo --
//

void fillMeterInfo(HumdrumFile& infile, Array<RationalNumber>& meterbot, 
      int track) {

   int top;
   int bot;

   meterbot.setSize(infile.getNumLines());

   RationalNumber current(4, 1);
   RationalNumber compound(3, 2);
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         meterbot[i] = current;         
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (track != infile[i].getPrimaryTrack(j)) {
            continue;
         }
         if (sscanf(infile[i][j], "*M%d/%d", &top, &bot) == 2) {
            current = bot;
            if ((top != 3) && ((top % 3) == 0)) {
               current *= compound;
            }
         }
         meterbot[i] = current;
         break;
      }
   }
}



//////////////////////////////
//
// findNote -- Search for the row and column of the nth note in the
//     track.  The current position of row and col is on the cur'th
//     note in the track (if all are 0, then haven't started looking).
//     returns 0 if nth note cannot be found in track.
//

int findNote(int nth, HumdrumFile& infile, int& cur, int& row, int& col, 
      int track, int subtrack, int& measure) {
   int direction = 1;

   if (nth > cur) {
      direction = 1;
   } else if (nth < cur) {
      direction = -1;
   } else {
      // Already have the match (for some strange reason) so just return.
      return 1;
   }

   int scount;
   int mval;
   int i, j;
   for (i=row+direction; (i<infile.getNumLines()) && (i>=0); i+=direction) {
      if (infile[i].isMeasure()) {
         if (sscanf(infile[i][0], "=%d", &mval)) {
            measure = mval;
            if (direction < 0) {
               measure--;
            }
         }
      }
      if (!infile[i].isData()) {
         continue;
      }
      scount = 0;
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (track != infile[i].getPrimaryTrack(j)) {
            continue;
         }
         scount++;
         if (subtrack == scount) {
            if (strcmp(infile[i][j], ".") == 0) { 
               // skip null tokens (could make search faster
               // if null token references were utilized).
               break;
            }
            // currently only considering tracks to be **kern data,
            // but should be generalized later (so don't exit from "r"
            // or "]" or "_" for non **kern data.
            if (strchr(infile[i][j], 'r') != NULL) { 
               // skip null tokens (could make search faster
               // if null token references were utilized).
               break;
            }
            if ((!graceQ) && ((strchr(infile[i][j], 'q') != NULL) ||
                              (strchr(infile[i][j], 'Q') != NULL))
                  ) {
               // ignore grace notes if requested
               break;
            }
            // the following statements are not quite right (consider
            // chords with only some notes being tied?)
            // but this will be dependent on tindex's behavior.
            if (strchr(infile[i][j], ']') != NULL) { 
               // skip endings of ties.
               break;
            }
            if (strchr(infile[i][j], '_') != NULL) { 
               // skip continuation ties.
               break;
            }
            // now have a note which is to be counted:
            cur += direction;
            if (cur == nth) {
               row = i;
               col = j;
               return 1;
            } 
            break;
         }
      }
   }

   // if get here, then note not found:
   row = 0;
   col = 0;
   cur = 0;
   return 0;
}



//////////////////////////////
//
// getFileAndPath -- given a particular filename and a list of directory
//    paths to search, return the first file which is found which matches
//    the filename in the list of directory paths.  First search using
//    the complete filename.  Then if the filename with any attached 
//    directory information is not found, then remove the directory
//    information and search again.  
//

void getFileAndPath(Array<char>& fileandpath, Array<char>& filename, 
   Array<Array<char> >& paths) {
   PerlRegularExpression pre;

   pre.sar(filename, ":", ":", "g");
   if (pre.search(filename, "://")) {
      // either a URL or a URI, so no path.
      fileandpath = filename;
      paths.setSize(1);
      paths[0] = '\0';
      return;
   }

   int i;
   for (i=0; i<paths.getSize(); i++) {
      if (fileexists(fileandpath, filename, paths[i])) {
         return;
      }
   }

   if (!pre.search(filename, "/")) {
      fileandpath.setSize(1);
      fileandpath[0] = '\0';
   }

   // check to see if removing the directory name already attached 
   // to the filename helps:

   Array<char> tempfilename = filename;
   pre.sar(tempfilename, ".*/", "", "");

   for (i=0; i<paths.getSize(); i++) {
      if (fileexists(fileandpath, tempfilename, paths[i])) {
         return;
      }
   }

   fileandpath.setSize(1);
   fileandpath[0] = '\0';
}



//////////////////////////////
//
// JoinDirToPath -- The temporary buffer used to
//    create the filename is given back to the calling function.
//

void JoinDirToPath(Array<char>& jointname, Array<char>& path, 
      Array<char>& filename) {

   PerlRegularExpression pre;
   int len = strlen(filename.getBase());
   if (pre.search(path, "^\\./*$")) {
      // if searching in the current directory, then don't
      // add the current directory marker.
      jointname.setSize(len+1);
      strcpy(jointname.getBase(), filename.getBase());
   } else {
      // append "/" to directory name and then filename
      len += strlen(path.getBase());
      if (pre.search(path, "/$", "")) {
         // don't need to add "/" to separate dir and file names.
         jointname.setSize(len+1);
         strcpy(jointname.getBase(), path.getBase());
         strcat(jointname.getBase(), filename.getBase());
      } else {
         // need to add "/" to separate dir and file names.
         jointname.setSize(len+2);
         strcpy(jointname.getBase(), path.getBase());
         strcat(jointname.getBase(), "/");
         strcat(jointname.getBase(), filename.getBase());
      }
   }
}



//////////////////////////////
//
// fileexists --
//

int fileexists(Array<char>& jointname, Array<char>& filename, 
      Array<char>& path) {
   JoinDirToPath(jointname, path, filename);
   if (access(jointname.getBase(), F_OK) != -1) {
      return 1;
   } else {
      return 0;
   }

   // or another way:
   //    struct stat buffer;
   //    return stat(filename, &buffer) == 0;
}



//////////////////////////////
//
// extractDataFromInputLine --
//

void extractDataFromInputLine(Array<char>& filename, 
      Array<char>& voicename, int& track, int& subtrack, Array<int>& starts, 
      Array<int>& endings, char* inputline) {
   filename.setSize(1);
   filename[0] = '\0';
   track = 0;
   subtrack = 1;

   starts.setSize(1000);
   starts.setGrowth(1000);
   starts.setSize(0);

   endings.setSize(1000);
   endings.setGrowth(1000);
   endings.setSize(0);

   int negone = -1;

   char* ptr = inputline;
   int len;
   int value;
   PerlRegularExpression pre;
   PerlRegularExpression pre2;
   PerlRegularExpression prefilename;

   if (pre.search(ptr, "^([^\\t:]+)[^\\t]*:(\\d+)\\.?(\\d+)?\\t(\\d+)([^\\s]*\\s*)")) {
      if (fileQ) {
         len = strlen(Filename);
         filename.setSize(len + 1);
         strcpy(filename.getBase(), Filename);
      } else {
         len = strlen(pre.getSubmatch(1));
         filename.setSize(len + 1);
         strcpy(filename.getBase(), pre.getSubmatch());
      }
      track = atoi(pre.getSubmatch(2));
      if (strlen(pre.getSubmatch(3)) > 0) {
         subtrack = atoi(pre.getSubmatch(3));
      } else {
         subtrack = 1;
      }
      value = atoi(pre.getSubmatch(4));
      starts.append(value);
      if (pre2.search(pre.getSubmatch(5), "-(\\d+)", "")) {
         value = atoi(pre2.getSubmatch(1));
         endings.append(value);
      } else {
         endings.append(negone);
      }
      ptr = ptr + pre.getSubmatchEnd(5);
      while (pre.search(ptr, "^(\\d+)([^\\s]*\\s*)")) { 
         value = atoi(pre.getSubmatch(1));
         starts.append(value);
         if (pre2.search(pre.getSubmatch(2), "-(\\d+)", "")) {
            value = atoi(pre2.getSubmatch(1));
            endings.append(value);
         } else {
            endings.append(negone);
         }
         ptr = ptr + pre.getSubmatchEnd(2);
      }
      if (pre.search(inputline, "^[^\\t:]*:([^\\t:]*):")) {
         voicename.setSize(strlen(pre.getSubmatch(1))+1);
         strcpy(voicename.getBase(), pre.getSubmatch());
      }
   } else if (pre.search(ptr, "^([^\\t:]+)\\t(\\d+)([^\\s]*\\s*)")) {
      // monophonic label (no spine information available).
      // search only on the first **kern column in the file.
      if (fileQ) {
         len = strlen(Filename);
         filename.setSize(len + 1);
         strcpy(filename.getBase(), Filename);
      } else {
         len = strlen(pre.getSubmatch(1));
         filename.setSize(len + 1);
         strcpy(filename.getBase(), pre.getSubmatch());
      }
      track = 1;
      value = atoi(pre.getSubmatch(2));
      starts.append(value);
      if (pre2.search(pre.getSubmatch(3), "-(\\d+)", "")) {
         value = atoi(pre2.getSubmatch(1));
         endings.append(value);
      } else {
         endings.append(negone);
      }
      ptr = ptr + pre.getSubmatchEnd(3);
      while (pre.search(ptr, "^(\\d+)([^\\s]*\\s*)")) { 
         value = atoi(pre.getSubmatch(1));
         starts.append(value);
         if (pre2.search(pre.getSubmatch(2), "-(\\d+)", "")) {
            value = atoi(pre2.getSubmatch(1));
            endings.append(value);
         } else {
            endings.append(negone);
         }
         ptr = ptr + pre.getSubmatchEnd(2);
      }
      if (pre.search(inputline, "^[^\\t:]*:([^\\t:]*):")) {
         // won't occur in this case
         voicename.setSize(strlen(pre.getSubmatch(1))+1);
         strcpy(voicename.getBase(), pre.getSubmatch());
      }
   }

   if(dirdropQ) {
      // remove directory names from filename if the -D option was used.
      pre.sar(filename, ".*/", "", "");
   }

   prefilename.sar(filename, ":", ":", "g");

}



//////////////////////////////
//
// checkOptions -- 
//

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("p|path=s:.", "colon-separated search patch for data files");
   opts.define("D|nodir=b", "remove directory information from input data");
   opts.define("l|L|line=b", "display line in file on which match found");
   opts.define("c|col=b", "display column in file on which match found");
   opts.define("N|nonth=b", "don't display nth note number in file");
   opts.define("P|percent=b", "display percentage into file");
   opts.define("M|nomeasure=b", "don't display measure number in file");
   opts.define("a|abs=b", "display absolute beat number of note in file");
   opts.define("q|qbeat=b", "display quarter note duration for start of bar");
   opts.define("B|nobeat=b", "don't display beat of start of match");
   opts.define("r|rational=b", "display metric info as rational numbers");
   opts.define("m|mark=b", "mark matches in first input file");
   opts.define("mstart|double=b", "double-mark first note in match");
   opts.define("G|no-grace|nograce=b", "do not count grace notes");
   opts.define("fixedmark=i:1", "mark matches in first input file");
   opts.define("matchlist=b", "list matches in output Humdurm file");
   opts.define("file=s:", "filename to use as basis for search information");
   opts.define("mchar|markchar=s:@", "character to mark matches with");
   opts.define("all=b", "display all location formats");
   opts.define("tie|ties=b", "display search markers on tie middle/end notes");

   opts.define("debug=b",  "author of program"); 
   opts.define("author=b",  "author of program"); 
   opts.define("version=b", "compilation info");
   opts.define("example=b", "example usages");   
   opts.define("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, Nov 2010" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 10 Nov 2010" << 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);
   }

   dispLineQ   = opts.getBoolean("line");
   dispAbsBeatQ= opts.getBoolean("abs");
   dispColumnQ = opts.getBoolean("col");
   dispNoteQ   =!opts.getBoolean("nonth");
   dispMeasureQ=!opts.getBoolean("nomeasure");
   dispQBeatQ  = opts.getBoolean("qbeat");
   dispBeatQ   =!opts.getBoolean("nobeat");
   rationalQ   = opts.getBoolean("rational");
   dirdropQ    = opts.getBoolean("nodir");
   debugQ      = opts.getBoolean("debug");
   percentQ    = opts.getBoolean("percent");
   tieQ        = opts.getBoolean("tie");
   doubleQ     = opts.getBoolean("mstart");
   fileQ       = opts.getBoolean("file");
   if(fileQ) {
      Filename = opts.getString("file");
   }
   markQ       = opts.getBoolean("fixedmark");
   matchlen    = opts.getInteger("fixedmark");
   matchlistQ  = opts.getBoolean("matchlist");
   if (opts.getBoolean("mark")) {
      markQ = 1;
      matchlen = 1;
      mark2Q = 1;
   } else {
      mark2Q = 0;
   }

   if(!markQ) {
      matchlistQ = 1;
   }
   marker =  opts.getString("markchar");
   graceQ = !opts.getString("no-grace");

   if (opts.getBoolean("all")) {
      dispLineQ    = 1;  // used with -l option
      dispColumnQ  = 1;  // used with -c option
      dispNoteQ    = 1;  // used with -N option
      dispAbsBeatQ = 1;  // used with -a option
      percentQ     = 1;  // used with -P option
      dispMeasureQ = 1;  // used with -M option
      dispQBeatQ   = 1;  // used with -q option
      dispBeatQ    = 1;  // used with -B option
   }
   
   prepareSearchPaths(paths, opts.getString("path"));
}



//////////////////////////////
//
// prepareSearchPaths -- get a list of search paths for looking for data files.
//     The list of paths is colon separated.
//

void prepareSearchPaths(Array<Array<char> >& paths, const char* pathlist) {
   paths.setSize(100);
   paths.setSize(0);
   paths.setGrowth(1000);
   PerlRegularExpression pre;
   int len;
   const char* ptr = pathlist;
   while (pre.search(ptr, "([^:]+):?")) {
      len = strlen(pre.getSubmatch(1));
      paths.setSize(paths.getSize()+1);
      paths.last().setSize(len+1);
      strcpy(paths[paths.getSize()-1].getBase(), pre.getSubmatch());
      ptr = ptr + pre.getSubmatchEnd(1);
   }

   if(debugQ) {
      cout << "Search Paths:" << endl;
      for (int i=0; i<paths.getSize(); i++) {
         cout << "search path " << i + 1 << ":\t" << paths[i] << endl;
      }
   }


}



//////////////////////////////
//
// example --
//

void example(void) {


}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {

}



// md5sum: 964b0c692a0188b02222267b966e494e theloc.cpp [20130420]