//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Apr 30 22:08:49 PDT 2010
// Last Modified: Fri Apr 30 22:08:55 PDT 2010
// Filename:      ...sig/examples/all/mince.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/mince.cpp
// Syntax:        C++; museinfo
//
// Description:   Extract small segments of music from a **kern score.
//

#include "humdrum.h"
#include "PerlRegularExpression.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);
void      processFile(HumdrumFile& infile, const char* filename);
void      printSegmentSpine(Array<int>& segment, HumdrumFile& infile);
void      generateMinceData(Array<int>& segment, HumdrumFile& infile,
                              const char* name);
void      printSegment(ostream& out, int line, Array<int>& segment, 
                              HumdrumFile& infile, int totalcount, int count);
int       getPreviousSegment(int line, Array<int>& segment, 
                              HumdrumFile& infile);
int       getNextSegment(int line, Array<int>& segment, 
                              HumdrumFile& infile);
int       countSegments(Array<int>& segment);
void      printInitialization(ostream& out, int line, HumdrumFile& infile);
void      printTermination(ostream& out, int line, HumdrumFile& infile);
void      printFilename(ostream& out, const char* name, int printpath);
void      prepareMeterInfo(Array<int>& metertop, Array<int>& meterbot, 
                              HumdrumFile& infile);
void      prepareKeyInfo(Array<int>& key, HumdrumFile& infile);
void      printPreviousPitches(ostream& out, int line, HumdrumFile& infile);


// global variables
Options     options;           // database for command-line arguments
int         segQ     = 0;      // used with -s option
int         measureQ = 1;      // used with -b option
int         meterQ   = 1;      // used with -M option
int         baseQ    = 0;      // used with -B option
int         keyQ     = 1;      // used with -K option
const char* dfilename = NULL;  // used with -f option

Array<int>  metertop;
Array<int>  meterbot;
Array<int>  key;

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

int main(int argc, char* argv[]) {
   HumdrumFile infile;

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

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

   const char* filename = "";

   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);
         filename = "";
      } else {
         filename = options.getArg(i+1);
         infile.read(filename);
      }
      // analyze the input file according to command-line options
      infile.analyzeRhythm();

      if (dfilename != NULL) {
         filename = dfilename;
      }
      processFile(infile, filename);
   }

   return 0;
}


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



//////////////////////////////
//
// processFile --
//

void processFile(HumdrumFile& infile, const char* filename) {
   int i, j, k;
   int tokencount;
   char buffer[1024];
   int state;
   int ii, jj;

   Array<int> segment;
   segment.setSize(infile.getNumLines());
   segment.allowGrowth(0);
   segment.setAll(0);

   for (i=0; i<infile.getNumLines(); i++) {
      state = 1;
      if (!infile[i].isData()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            ii = infile[i].getDotLine(j);
            jj = infile[i].getDotSpine(j);
            if (strchr(infile[ii][jj], 'r') == NULL) {
               state = 0;
               break;
            }
         }
         if (state == 0) {
            break;
         }
         tokencount = infile[i].getTokenCount(j);
         for (k=0; k<tokencount; k++) {
            infile[i].getToken(buffer, j, k);
            if (strcmp(buffer, ".") == 0) {
               // don't know why you would get here...
               continue;
            }
            if (strchr(buffer, ']') != NULL) {
               state = 0; 
               break;
            }
            if (strchr(buffer, '_') != NULL) {
               state = 0; 
               break;
            }
         }
      }
      segment[i] = state;
   }

   if (segQ) { 
      printSegmentSpine(segment, infile);
   } else {
      generateMinceData(segment, infile, filename);
   }
}



//////////////////////////////
//
// generateMinceData --
//

void generateMinceData(Array<int>& segment, HumdrumFile& infile, 
      const char* name) {
   int i;
   int segmentcount = countSegments(segment);
   int counter = 1;
   if (meterQ) {
      prepareMeterInfo(metertop, meterbot, infile);
   }
   if (keyQ) {
      prepareKeyInfo(key, infile);
   }
   for (i=0; i<segment.getSize(); i++) {
      if (segment[i]) {
         if (counter > 1) {
            cout << endl;
         }
         cout << "!!!filename:\t";
	 printFilename(cout, name, baseQ);
         cout << endl;
         cout << "!!!segment:\t" << counter << "/" << segmentcount << endl;
         printSegment(cout, i, segment, infile, segmentcount, counter++);
      }
   }
}



//////////////////////////////
//
// prepareKeyInfo --
//

void prepareKeyInfo(Array& key, HumdrumFile& infile) {
   key.setSize(infile.getNumLines());
   key.allowGrowth(0);
   key.setAll(-1);
   int lkey = -1;
   PerlRegularExpression pre;
   int i, j;

   for (i=0; i<infile.getNumLines(); i++) {
      key[i] = lkey;
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*([A-Ga-g]):")) {
            lkey = Convert::kernToBase40(pre.getSubmatch(1));
	    key[i] = lkey;
	    break;
         }
      }
   }
}



//////////////////////////////
//
// prepareMeterInfo --
//

void prepareMeterInfo(Array<int>& metertop, Array<int>& meterbot, 
      HumdrumFile& infile) {  
   metertop.setSize(infile.getNumLines());
   meterbot.setSize(infile.getNumLines());
   metertop.allowGrowth(0);
   meterbot.allowGrowth(0);
   metertop.setAll(-1);
   meterbot.setAll(-1);

   int mettop = -1;
   int metbot = -1;

   PerlRegularExpression pre;

   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      metertop[i] = mettop;
      meterbot[i] = metbot;
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (pre.search(infile[i][j], "^\\*M(\\d+)/(\\d+)$")) {
            mettop = int(atof(pre.getSubmatch(1))+0.5);
            metbot = int(atof(pre.getSubmatch(2))+0.5);
	    metertop[i] = mettop;
	    meterbot[i] = metbot;
	    break;
         }
      }
   }
}



//////////////////////////////
//
// printFilename --
//

void printFilename(ostream& out, const char* name, int printpath) {
   if (printpath) {
      out << name;
   } else {
      const char* ptr = strrchr(name, '/');
      if (ptr != NULL) {
         ptr++;
         out << ptr;
      } else {
         out << name;
      }
   }
}



//////////////////////////////
//
// printSegment --
//

void printSegment(ostream& out, int line, Array<int>& segment, 
      HumdrumFile& infile, int totalcount, int count) {
   int lastseg = getPreviousSegment(line, segment, infile);
   int nextseg = getNextSegment(line, segment, infile);
   char buffer[128] = {0};

   double sbeat = infile[line].getBeat();
   double nbeat = infile[nextseg].getBeat();
   double abeat = infile[line].getAbsBeat();

   out << "!!!sbeat:\t" << sbeat << endl;
   out << "!!!nbeat:\t" << nbeat << endl;
   out << "!!!abeat:\t" << abeat << endl;
   out << "!!!duration:\t" << infile[nextseg].getAbsBeat() - abeat << endl;
   if (meterQ && (metertop[line] > 0) && (meterbot[line] > 0)) {
      out << "!!!meter:\t" << metertop[line] << "/" << meterbot[line] << endl;
   }
   if (keyQ && (key[line] > 0)) {
      out << "!!!key:\t\t" << Convert::base40ToKern(buffer, key[line]) << endl;
   }

   printInitialization(out, lastseg, infile);

   printPreviousPitches(out, line, infile);
   // print spine adjustments if necessary to get to next segment here.
   int i;
   for (i=line; i<nextseg; i++) {
      if (!measureQ && infile[i].isMeasure()){ 
         continue;
      }
      out << infile[i] << endl;
   }
   printTermination(out, nextseg, infile);
}



//////////////////////////////
//
// printInitialization --
//

void printInitialization(ostream& out, int line, HumdrumFile& infile) {

   // print data start markers.  Deal with sub-spines later at this point.

   int i;
   for (i=0; i<infile[line].getFieldCount(); i++) {
      out << infile[line].getExInterp(i);
      if (i < infile[line].getFieldCount() - 1) {
         out << "\t";
      }
   }
   out << endl;



   // deal with spine changes between previous chord segment and current one
	 

   // print pitches at the start of the previous segment:
   int j, k;
   int tokencount;
   char buffer[128] = {0};
   char buffer2[128] = {0};

   if (infile[line].isData()) {
      for (j=0; j<infile[line].getFieldCount(); j++) {
         if (!infile[line].isExInterp(j, "**kern")) {
            out << "!<<" << infile[line][j];
            if (j < infile[line].getFieldCount()-1) {
               out << "\t";
            }
            continue;
         }
         tokencount = infile[line].getTokenCount(j);
         out << "!<<";
         for (k=0; k<tokencount; k++) {
            infile[line].getToken(buffer, j, k);
            if (strchr(buffer, 'r') != NULL) {
               out << "r";
            } else {
               Convert::base40ToKern(buffer2, Convert::kernToBase40(buffer));
               out << buffer2;
            }
            if (k < tokencount - 1) {
               out << " ";
            }
         }
         if (j < infile[line].getFieldCount()-1) {
            out << "\t";
         }
      }
      out << "\n";
   }

   // print spine adjustments if necessary to get to next segment here.


}


//////////////////////////////
//
// printPreviousPitches --
//

void printPreviousPitches(ostream& out, int line, HumdrumFile& infile) {

   int i;
   for (i=line-1; i>0; i--) {
      if (infile[i].isData()) {
         break;
      }
   }
   if (i >= line) {
      return;
   }
   if (!infile[i].isData()) {
      // no data lines found before current line;
      return;
   }
   line = i;

   int j, k;
   int ii, jj;
   int tokencount;
   char buffer[128] = {0};
   char buffer2[128] = {0};
   int paren;
   // print pitches at the end of the previous segment:
   if (infile[line].isData()) {
      for (j=0; j<infile[line].getFieldCount(); j++) {
         if (!infile[line].isExInterp(j, "**kern")) {
            out << "!<" << infile[line][j];
            if (j < infile[line].getFieldCount()-1) {
               out << "\t";
            }
            continue;
         }
         if (strcmp(infile[line][j], ".") == 0) {
            ii = infile[line].getDotLine(j);
            jj = infile[line].getDotSpine(j);
            paren = 1;
         } else {
            ii = line;
            jj = j;
            paren = 0;
         }

         tokencount = infile[ii].getTokenCount(jj);
         out << "!<";
         for (k=0; k<tokencount; k++) {
            infile[ii].getToken(buffer, jj, k);
            if (strchr(buffer, 'r') != NULL) {
               out << "r";
            } else {
               Convert::base40ToKern(buffer2, Convert::kernToBase40(buffer));
               if (paren) {
                  out << "(";
               } 
               out << buffer2;
               if (paren) {
                  out << ")";
               } 
            }
            if (k < tokencount - 1) {
               out << " ";
            }
         }
         if (j < infile[line].getFieldCount()-1) {
            out << "\t";
         }
      }
      out << "\n";
   }


}



//////////////////////////////
//
// printTermination -- print end of data marker for particular segment.
//

void printTermination(ostream& out, int line, HumdrumFile& infile) {
   if (strcmp(infile[line][0], "*-") == 0) {
      out << infile[line] << endl;
      return;
   }

   // print pitches which follow in next segment:
   int j, k;
   int tokencount;
   char buffer[128] = {0};
   char buffer2[128] = {0};
   if (infile[line].isData()) {
      for (j=0; j<infile[line].getFieldCount(); j++) {
         if (!infile[line].isExInterp(j, "**kern")) {
            out << "!>" << infile[line][j];
            if (j < infile[line].getFieldCount()-1) {
               out << "\t";
            }
            continue;
         }
         tokencount = infile[line].getTokenCount(j);
         out << "!>";
         for (k=0; k<tokencount; k++) {
            infile[line].getToken(buffer, j, k);
            if (strchr(buffer, 'r') != NULL) {
               out << "r";
            } else {
               Convert::base40ToKern(buffer2, Convert::kernToBase40(buffer));
               out << buffer2;
            }
            if (k < tokencount - 1) {
               out << " ";
            }
         }
         if (j < infile[line].getFieldCount()-1) {
            out << "\t";
         }
      }
   }
   out << "\n";


   // just terminate data lines for now, fix split spines later...
   int i;
   for (i=0; i<infile[line].getFieldCount(); i++) {
      out << "*-";
      if (i < infile[line].getFieldCount()-1) {
         out << "\t";
      }
   }
   out << endl;

}



//////////////////////////////
//
// countSegments --
//

int countSegments(Array& segment) {
   int counter = 0;
   int i;
   for (i=0; i<segment.getSize(); i++) {
      if (segment[i]) {
         counter++;
      }
   }

   return counter;
}



//////////////////////////////
//
// getPreviousSegment -- return the line of the previous segment, or the
//   start of data line if no previous segment point.
//

int getPreviousSegment(int line, Array& segment, HumdrumFile& infile) {
   int i;
   for (i=line-1; i>0; i--) {
      if (infile[i].isInterpretation() && 
            (strncmp(infile[i][0], "**", 2) == 0)) {
         return i;
      }
      if (segment[i] != 0) {
         return i;
      }
   }

   // this line should not be reached.
   return 0;
}



//////////////////////////////
//
// getNextSegment --
// 

int getNextSegment(int line, Array& segment, HumdrumFile& infile) {
   int i;
   for (i=line+1; i<infile.getNumLines(); i++) {
      if (strcmp(infile[i][0], "*-") == 0) {
         return i;
      }
      if (segment[i]) {
         return i;
      }
      
   }

   // shouldn't get to this point.
   return infile.getNumLines()-1;
}



//////////////////////////////
//
// printSegmentSpine --
//

void printSegmentSpine(Array& segment, HumdrumFile& infile) {
   int i;
   int counter = 1;
   for (i=0; i<infile.getNumLines(); i++) {
      switch (infile[i].getType()) {
         case E_humrec_data_comment:
            cout << "!\t" << infile[i] << endl;
            break;
         case E_humrec_data_kern_measure:
            cout << infile[i][0] << "\t" << infile[i] << endl;
            break;
         case E_humrec_interpretation:
	    if (strncmp(infile[i][0], "**", 2) == 0) {
               cout << "**segment";
	    } else if (strcmp(infile[i][0], "*-") == 0) {
               cout << "*-";
	    } else if (strncmp(infile[i][0], "*>", 2) == 0) {
               cout << infile[i][0];
            } else {
               cout << "*";
            }
            cout << "\t" << infile[i] << endl;
            break;
         case E_humrec_data:
            if (segment[i]) {
	       cout << counter++;
            } else {
               cout << ".";
            }
            cout << "\t" << infile[i] << endl;
            break;
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         default:
            cout << infile[i] << endl;
      }
   }


}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("K|no-key=b",       "don't list current key");
   opts.define("B|no-directory=b", "don't list file pathname");
   opts.define("s|segment=b",      "output original data with segment spine");
   opts.define("f|filename=s:",    "text to place in !!!filename: field");
   opts.define("b|no-barlines=b",  "don't preserve barlines in output data");
   opts.define("M|no-meter=b",     "don't display current meter");
	
   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, April 2010" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 30 April 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);
   }

   baseQ     = !opts.getBoolean("no-directory");
   keyQ      = !opts.getBoolean("no-key");
   segQ      =  opts.getBoolean("segment");
   measureQ  = !opts.getBoolean("no-barlines");
   meterQ    = !opts.getBoolean("no-meter");
   if (opts.getBoolean("filename")) {
      dfilename = opts.getString("filename");
   }

}



//////////////////////////////
//
// 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: 075feba4c6cbb42049565f87c6cfb3e2 mince.cpp [20100505]