//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Oct 13 21:48:04 PDT 2011
// Last Modified: Thu Oct 13 21:48:07 PDT 2011
// Filename:      ...sig/examples/all/addtempo.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/addtempo.cpp
// Syntax:        C++; museinfo
//
// Description:   Add tempo settings to a Humdrum file with **kern spines.
//

#ifndef OLDCPP
   #include <iostream>
   using namespace std;
#else
   #include <iostream.h>
#endif

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


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

// function declarations:
void  checkOptions(Options& opts, int argc, char** argv);
void  example(void);
void  usage(const char* command);
void  processFile(HumdrumFile& infile, 
                                Array<Array<double> >& tempos);
int   assignTempoLine(Array<double>& tempos, HumdrumFile& infile);
void  printTempo(HumdrumFile& infile, int line, 
                                Array<double>& tempo);
void  setTempoValues(Array<Array<double> >& settings, 
                                const char* string);

// User interface variables:
Options options;
int    debugQ    = 0;                // used with --debug option
Array<Array<double> > TempoSettings; // used with -t option

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

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

   // initial processing of the command-line options
   checkOptions(options, argc, argv);

   if (options.getArgCount() < 1) {
      infile.read(cin);
   } else {
      infile.read(options.getArg(1));
   }

   processFile(infile, TempoSettings);

   return 0;
}


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


//////////////////////////////
//
// processFile -- insert tempo marks into Humdrum file.
//

void processFile(HumdrumFile& infile, Array >& tempos) {
   Array<int> tline;
   tline.setSize(tempos.getSize());
   tline.setGrowth(0);
   tline.setAll(-1);
   infile.analyzeRhythm("4");
   int i;
   for (i=0; i<tempos.getSize(); i++) {
      tline[i] = assignTempoLine(tempos[i], infile);
      if (debugQ) {
         cout << "!! LINE " << i << "\t" << tline[i] << endl;
      }
   }

   int ii = 0;
   for (i=0; i<infile.getNumLines(); i++) {
      cout << infile[i] << "\n";
      if ((ii < tline.getSize()) && (tline[ii] == i)) {
         printTempo(infile, tline[ii], tempos[ii]);
         ii++;
      }
   }

}



//////////////////////////////
//
// printTempo --
//

void printTempo(HumdrumFile& infile, int line, Array& tempo) {
   int& i = line;
   int j;

   if (infile[i].isGlobalComment()) {
      cerr << "Global comment case not handled yet" << endl;
      exit(1);
   }
   if (infile[i].isBibliographic()) {
      cerr << "Reference record case not handled yet" << endl;
      exit(1);
   }
   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (infile[i].isExInterp(j, "**kern")) {
         cout << "*MM" << tempo[0];
      } else {
         cout << "*";
      }
      if (j < infile[i].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";

   // cout << "!!TEMPO: " << tempo[0] << "\tM=" 
   //      << tempo[1] << "\tB=" << tempo[2] << endl;
}



//////////////////////////////
//
// assgignTempoLines --
//

int assignTempoLine(Array& tempos, HumdrumFile& infile) {

   // first find the first data line at the specified position (or just
   // before the next data line which occurs after the target position).
   PerlRegularExpression pre;
  
   int measure = (int)tempos[1];
   double beat = tempos[2];

   int i;
   int exinterp    = -1;
   int dataline    = -1;
   int measureline = -1;
   int meterline   = -1;
   int metline     = -1;
   if (measure == -1) {
      // the tempo should go at the start of the music.  First search for
      // a *M%d/%d line (optionally followed by a *met() line) to place
      // the tempo marking after.  If not found, then place just before 
      // the first data or measure line found in the score.  Also assuming
      // that the meter, measure are in the first column of data.
      for (i=0; i<infile.getNumLines(); i++) {
         if (infile[i].isData()) {
            dataline = i;
            break;
         }
         if (infile[i].isMeasure()) {
            measureline = i;
            continue;
         }
         if (infile[i].isInterpretation()) {
            if (strncmp(infile[i][0], "**", 2) == 0) {
               exinterp = i;
            } else if (pre.search(infile[i][0], "^\\*M(\\d+)/(\\d+)")) {
               meterline = i;
               continue;
            } else if (pre.search(infile[i][0], "^\\*met(.*)")) {
               metline = i;
               continue;
            }
         }
      }
      if (metline > 0) {
         return metline;
      } else if (meterline > 0) {
         return meterline;
      } else if (measureline > 0) {
         return measureline-1;
      } else if (dataline > 0) {
         return dataline-1;
      } else {
         return exinterp;
      }
   }


   // place tempo with a particular measure
   int    curmeasure  = -1;
   double basebeat    = -1;
   double curbeat     = -1;
   int    workingline = -1;
   int j;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isMeasure()) {
         if (pre.search(infile[i][0], "^=(\\d+)")) {
            curmeasure = atoi(pre.getSubmatch(1));
         }
      }
      if (curmeasure == measure) {
         basebeat = infile[i].getAbsBeat();
         for (j=i+1; j<infile.getNumLines(); j++) {
            curbeat = infile[j].getAbsBeat() - basebeat + 1.0;
            if (fabs(curbeat - beat) < 0.01) {
               workingline = j-1;
               break;
            } else if (curbeat > beat) {
               workingline = j-1;
               break;
            }
         }
         // refine working line later...
         return workingline;
      }
   }

   cerr << "GOT TO A FUNNY PLACE" << endl;
   exit(1);
}



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

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("d|debug=b",    "Debugging information");
   opts.define("t|tempo=s:",   "Tempo setting(s)");

   opts.define("author=b",    "Program author");
   opts.define("version=b",   "Program version");
   opts.define("example=b",   "Program examples");
   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, Feb 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 5 Feb 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);
   }

   debugQ     =  opts.getBoolean("debug");
   if (opts.getBoolean("tempo")) {
      setTempoValues(TempoSettings, opts.getString("tempo"));
      if (TempoSettings.getSize() == 0) {
         usage(opts.getCommand());      
      }
   } else {
      usage(opts.getCommand());      
   }

}



//////////////////////////////
//
// setTempoValues -- read the tempo values from the 
//

void setTempoValues(Array >& settings, const char* string) {
   PerlRegularExpression pre;
   Array<Array<char> > tokens;
   pre.getTokens(tokens, "[\\s,:;]+", string);

   settings.setSize(tokens.getSize());
   settings.allowGrowth(0);
   int i;

   for (i=0; i<settings.getSize(); i++) {
      settings[i].setSize(3);
      settings[i].setAll(-1);
      settings[i].allowGrowth(0);
   }

   double tempo;
   double measure;
   double beat;
   for (i=0; i<tokens.getSize(); i++) {
      if (pre.search(tokens[i], "([\\d.]+)@m(\\d+)b([\\d.]+)")) {
         // read tempo for specific measure and beat
         sscanf(pre.getSubmatch(1), "%lf", &tempo);
         sscanf(pre.getSubmatch(2), "%lf", &measure);
         sscanf(pre.getSubmatch(3), "%lf", &beat);
      } else if (pre.search(tokens[i], "(\\d+)@m(\\d+)")) {
         // read tempo for a measure, set beat to 1
         sscanf(pre.getSubmatch(1), "%lf", &tempo);
         sscanf(pre.getSubmatch(2), "%lf", &measure);
         beat = 1;
      } else if (pre.search(tokens[i], "(\\d+)")) {
         // read default tempo, set measure to -1 and beat to -1
         sscanf(pre.getSubmatch(1), "%lf", &tempo);
         measure = -1;
         beat = -1;
      } else {
         continue;
      }
      settings[i][0] = tempo;
      settings[i][1] = measure;
      settings[i][2] = beat;
   }

   if (debugQ) {
      for (i=0; i<settings.getSize(); i++) {
         cout << "!! " << i << "\t" << settings[i][0]
              << "\t" << settings[i][1]
              << "\t" << settings[i][2]
              << endl;
      }
   }
}



//////////////////////////////
//
// example -- example function calls to the program.
//

void example(void) {


}



//////////////////////////////
//
// usage -- command-line usage description and brief summary
//

void usage(const char* command) {
   cout << "Usage: " << command << " -t tempostring file.in > file.out\n";
   exit(1);
}


// md5sum: bc8e4e25dd5abf9b3b79bbaa0f4a69cd addtempo.cpp [20111020]