//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jan  7 17:32:52 PST 2011
// Last Modified: Fri Jan  7 19:33:31 PST 2011 (added -o and -a options)
// Filename:      ...sig/examples/all/rscale.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/rscale.cpp
// Syntax:        C++; museinfo
//
// Description:   Scale the duration of all **kern rhythms by a constant
//                factor.
//
//

#include "PerlRegularExpression.h"
#include "humdrum.h"
#include <math.h>

#include <string.h>
#include <ctype.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      printOutput(HumdrumFile& infile, RationalNumber& rnum);
void      printKernToken(HumdrumFile& infile, int row, int col, 
                              RationalNumber& factor);
void      printSingleKernSubtoken(const char* buff, RationalNumber& factor);
void      printSpecialRational(RationalNumber& value);
void      processTimeSignature(HumdrumFile& infile, int row, 
                              RationalNumber& factor);
void      handleBibliographic(HumdrumFile& infile, int row, 
                              RationalNumber& num);
void      getOriginalFactor(HumdrumFile& infile, RationalNumber& factor);
void      getAlternateFactor(HumdrumFile& infile, RationalNumber& factor);
void      cleanUpBeams(char* prebuffer, char* postbuffer, int level);

// global variables
Options   options;             // database for command-line arguments
int       debugQ     = 0;      // used with --debug
RationalNumber factor;         // used with -f option
int       meterQ     = 1;      // used with -M option
int       FoundRef   = 0;      // used with !!!rscale: reference record
int       originalQ  = 0;      // used with --original
int       alternateQ = 0;      // used with --alternate
int       longQ      = 0;      // used with -r option
int       rebeamQ    = 0;      // used with -B option

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

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();

   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));
      }
      // analyze the input file according to command-line options
      infile.analyzeRhythm("4");
 
      if (originalQ) {
         getOriginalFactor(infile, factor);
      } else if (alternateQ) {
         getAlternateFactor(infile, factor);
      }

      printOutput(infile, factor);
   }

   if ((!originalQ) && (!FoundRef) && (factor != 1)) {
      cout << "!!!rscale: " << factor << endl;
   }

   return 0;
}


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


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

void printOutput(HumdrumFile& infile, RationalNumber& rnum) {
   int i, j;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].isBibliographic()) {
         handleBibliographic(infile, i, rnum);
         continue;
      }
      if (!infile[i].isData()) {
         if (meterQ && infile[i].isInterpretation()) {
            processTimeSignature(infile, i, rnum); 
         } else {
            cout << infile[i] << "\n";
         }
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!(infile[i].isExInterp(j, "**kern") || 
               infile[i].isExInterp(j, "**recip"))) {
            cout << infile[i][j];
            if (j < infile[i].getFieldCount() - 1) {
               cout << "\t";
            }
            continue;
         }
         printKernToken(infile, i, j, rnum);
         if (j < infile[i].getFieldCount() - 1) {
            cout << "\t";
         }
      }
      cout << "\n";
   }
}



//////////////////////////////
//
// getOriginalFactor -- return the inverse of the value in the ref record:
//    !!!rscale: 1/2
// Would return "2" in the above case.
//

void getOriginalFactor(HumdrumFile& infile, RationalNumber& factor) {
   int i;
   PerlRegularExpression pre;
   RationalNumber newvalue;
   int top;
   int bot;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (!pre.search(infile[i][0], "^!!!rscale\\s*:\\s*(\\d+)(/?)(\\d*)", "")) {
         continue;
      }

      top = atoi(pre.getSubmatch(1));
      bot = 1;
      if (strlen(pre.getSubmatch(2)) != 0) {
         if (strlen(pre.getSubmatch(3)) != 0) {
            bot = atoi(pre.getSubmatch());
         }
      }
      if (top == 0) { top = 1; }
      if (bot == 0) { bot = 1; }
      newvalue.setValue(bot, top);
      factor = newvalue;
      return;
   }

}



///////////////////////////////
//
// getAlternateFactor -- return the factor which will produce the preferred
//     alternate rhythmic values.
//

void getAlternateFactor(HumdrumFile& infile, RationalNumber& factor) {
   PerlRegularExpression pre;
   int i;
   int top;
   int bot;
   RationalNumber value;

   RationalNumber orig(1,1);
   getOriginalFactor(infile, orig);

   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isBibliographic()) {
         continue;
      }
      if (!pre.search(infile[i][0], "^!!!rscale-alt\\s*:\\s*(\\d+)(/?)(\\d*)", "")) {
         continue;
      }

      top = atoi(pre.getSubmatch(1));
      bot = 1;
      if (strlen(pre.getSubmatch(2)) != 0) {
         if (strlen(pre.getSubmatch(3)) != 0) {
            bot = atoi(pre.getSubmatch());
         }
      }
      if (top == 0) { top = 1; }
      if (bot == 0) { bot = 1; }
      value.setValue(top, bot);
      factor = value * orig;
      return;
   }

}



//////////////////////////////
//
// handleBibliographic -- print reference records.  Scan for a reference
//     record in the form:
//        !!!rscale: 1/2
//     which is the scaling factor applied to the file already.
//     Modify this value by the factor.  Suppresss the record if
//     the output scaling is 1/1.
//

void handleBibliographic(HumdrumFile& infile, int row, RationalNumber& num) {
   char buffer[1024] = {0};
   infile[row].getBibKey(buffer, 1000);
   if (strcmp(buffer, "rscale") != 0) {
      cout << infile[row] << endl;
      return;
   }

   RationalNumber onum;
   PerlRegularExpression pre;
   if (!pre.search(infile[row][0], "!!!rscale([^\\d]*)(\\d+)(/?)(\\d*)(.*)", "")) {
      cout << infile[row] << endl;
      return;
   }

   int top = atoi(pre.getSubmatch(2));
   int bot = 1;
   if (strlen(pre.getSubmatch(3)) != 0) {
      if (strlen(pre.getSubmatch(4)) != 0) {   
         bot = atoi(pre.getSubmatch());
      }
   }
   if (bot == 0) {
      bot = 1;
   }
   onum.setValue(top, bot);
   onum *= num;
   FoundRef = 1;
   if (onum == 1) {
      // don't print !!!rscale: entry if scaling is 1.
      return;
   }

   if (originalQ) {
      // original rhythmic values are being generated, don't print ref rec.
      return;
   }
   
   cout << "!!!rscale";
   cout << pre.getSubmatch(1);
   cout << onum;
   cout << pre.getSubmatch(5);
   cout << "\n";

}



//////////////////////////////
//
// processTimeSignature -- look for time signatures, and alter the
//     bottom of the time signature by the scaling factor.
//

void processTimeSignature(HumdrumFile& infile, int row, 
      RationalNumber& factor) {
   int j;
   RationalNumber beat;
   int mtop;
   int top;
   int bot;
   PerlRegularExpression pre;
   for (j=0; j<infile[row].getFieldCount(); j++) {
      if (pre.search(infile[row][j], "^\\*tb(\\d+)(%?)(\\d*)$", "")) {
         // process a timebase interpretation
         top = atoi(pre.getSubmatch(1));
         bot = 1;
         if (strlen(pre.getSubmatch(2)) != 0) {
            if (strlen(pre.getSubmatch(3)) != 0) {
               bot = atoi(pre.getSubmatch());
               if (bot == 0) {
                  bot = 1;
               }
            }
         }
         beat.setValue(top, bot);
         beat /= factor;
         cout << "*tb";
         printSpecialRational(beat);
         if (j < infile[row].getFieldCount() - 1) {
            cout << "\t";
         }
         continue;
      }

      if ((!meterQ) || (!pre.search(infile[row][j], "^\\*M(\\d+)/(\\d+)(%?)(\\d*)(.*)", ""))) {
         cout << infile[row][j];
         if (j < infile[row].getFieldCount() - 1) {
            cout << "\t";
         }
         continue;
      }

      mtop = atoi(pre.getSubmatch(1));
      top = atoi(pre.getSubmatch(2));
      bot = 1;
      if (strlen(pre.getSubmatch(3)) != 0) {
         if (strlen(pre.getSubmatch(4)) != 0) {
            bot = atoi(pre.getSubmatch());
            if (bot == 0) {
               bot = 1;
            }
         }
      }
      beat.setValue(top, bot);
      beat /= factor;
      cout << "*M";
      cout << mtop;
      cout << "/";
      printSpecialRational(beat);
      cout << pre.getSubmatch(5);
      if (j < infile[row].getFieldCount() - 1) {
         cout << "\t";
      }
   }
   cout << "\n";
}



/////////////////////////////
//
// printKernToken --
//

void printKernToken(HumdrumFile& infile, int row, int col, 
      RationalNumber& factor) {
   int k;
   static char buffer[1024] = {0};

   if (strcmp(infile[row][col], ".") == 0) {
      // print null tokens and return
      cout << infile[row][col];
      return;
   }
   if (strchr(infile[row][col], 'q') != NULL) {
      // print notes/chords which have grace notes (chord rhythms 
      // should not mix and always be equal).
      cout << infile[row][col];
      return;
   }

   PerlRegularExpression pre;
   int tcount = infile[row].getTokenCount(col);
   for (k=0; k<tcount; k++) {
      infile[row].getToken(buffer, col, k, 1000);
      printSingleKernSubtoken(buffer, factor);
      if (k < tcount - 1) {
         cout << " ";
      }
   }
}



//////////////////////////////
//
// printSingleKernSubtoken --
//

void printSingleKernSubtoken(const char* buff, RationalNumber& factor) {
   PerlRegularExpression pre;
   RationalNumber value;

   int level = 0;
   if (factor == 2) {
      level = 1;
   } else if (factor == 4) {
      level = 2;
   }
   if (!rebeamQ) {
      level = 0;
   }
  
   char prebuffer[1024] = {0};
   char postbuffer[1024] = {0};

   if (pre.search(buff, "([^\\d]*)(\\d+)(%?)(\\d*)(.*)", "")) {
      int top = atoi(pre.getSubmatch(2));
      int bot;
      if (strlen(pre.getSubmatch(4)) != 0) {
         bot = atoi(pre.getSubmatch());
         if (bot == 0) {
            bot = 1;
         }
      } else {
         bot = 1; 
      }
      if (top == 0) {
         // handle cases where breve=0; long=00; maxima=000
         int tcount = 0;
         int i;
         int len = strlen(buff);
         for (i=0; i<len; i++) {
            if (buff[i] == '0') {
               tcount++;
            }
         }
         top = 1;
         bot = int(pow(2.0,tcount));
      }
      value.setValue(top, bot);
      value /= factor;           // factor is duration so inverse

      strcpy(prebuffer, pre.getSubmatch(1));
      strcpy(postbuffer, pre.getSubmatch(5));

      cleanUpBeams(prebuffer, postbuffer, level);

      cout << prebuffer;

      printSpecialRational(value);

      // print stuff after numeric part of rhythm
      cout << postbuffer;

   } else {
      cout << buff;
   }
}



//////////////////////////////
//
// cleanUpBeams -- remove k, K, L, J as needed from beam information.
//

void cleanUpBeams(char* prebuffer, char* postbuffer, int level) {
   if (level < 1) {
      return;
   }

   int kcount1 = 0;
   int Kcount1 = 0;
   int Lcount1 = 0;
   int Jcount1 = 0;

   int kcount2 = 0;
   int Kcount2 = 0;
   int Lcount2 = 0;
   int Jcount2 = 0;

   int len1 = strlen(prebuffer);
   int len2 = strlen(postbuffer);

   int i;

   for (i=0; i<len1; i++) {
      switch (prebuffer[i]) {
         case 'k': kcount1++; break;
         case 'K': Kcount1++; break;
         case 'L': Lcount1++; break;
         case 'J': Jcount1++; break;
      }
   }

   for (i=0; i<len2; i++) {
      switch (postbuffer[i]) {
         case 'k': kcount2++; break;
         case 'K': Kcount2++; break;
         case 'L': Lcount2++; break;
         case 'J': Jcount2++; break;
      }
   }

   Array<char> prestring;
   Array<char> poststring;

   prestring.setSize(len1+1);
   strcpy(prestring.getBase(), prebuffer);

   poststring.setSize(len1+1);
   strcpy(poststring.getBase(), postbuffer);

   PerlRegularExpression pre;

   if ((level == 1) && (Lcount1 > 0)) {
      pre.sar(prestring, "L", "");
      level--;
   } else if ((level == 1) && (Jcount1 > 0)) {
      pre.sar(prestring, "J", "");
      level--;
   } else if ((level == 1) && (Lcount2 > 0)) {
      pre.sar(prestring, "L", "");
      level--;
   } else if ((level == 1) && (Jcount2 > 0)) {
      pre.sar(prestring, "J", "");
      level--;
   } else if ((level == 2) && (Lcount1 > 1)) {
      pre.sar(prestring, "L", "");
      pre.sar(prestring, "L", "");
      level -= 2;
   } else if ((level == 2) && (Jcount1 > 1)) {
      pre.sar(prestring, "J", "");
      pre.sar(prestring, "J", "");
      level -= 2;
   } else if ((level == 2) && (Lcount2 > 1)) {
      pre.sar(prestring, "L", "");
      pre.sar(prestring, "L", "");
      level -= 2;
   } else if ((level == 2) && (Jcount2 > 1)) {
      pre.sar(prestring, "J", "");
      pre.sar(prestring, "J", "");
      level -= 2;
   }

   // deal with k and K later...

   strcpy(prebuffer, prestring.getBase());
   strcpy(postbuffer, poststring.getBase());
}



//////////////////////////////
//
// printSpecialRational -- print rational number rhythm, but use 0 
//    for 1/2 (breve), 00 for 1/4 (long), or 000 for 1/8 (maxima).
//

void printSpecialRational(RationalNumber& value) {
   static RationalNumber half(1,2);      // breve
   static RationalNumber quarter(1,4);   // long
   static RationalNumber eighth(1,8);    // maxima

   if (longQ) {
      // don't print 0 for breve, 00 for long or 000 for maxima.
      cout << value.getNumerator();
      if (value.getDenominator() != 1) {
         cout << '%' << value.getDenominator();
      }

   } else {

      if (value == half) {               // breve alternate
         cout << "0";
      } else if (value == quarter) {     // long alternate
         cout << "00";
      } else if (value == eighth) {      // maxima alternate
         cout << "000";
      } else {
         cout << value.getNumerator();
         if (value.getDenominator() != 1) {
            cout << '%' << value.getDenominator();
         }
      }

   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("d|f|factor=s:1/1", "Factor to multiply durations by");
   opts.define("M|T|nometer=b", "Do not alter time signatures");
   opts.define("o|original=b", "Revert to original rhythms");
   opts.define("a|alternate=b", "Change to alternate rhythm set");
   opts.define("r|long=b", "Do not use short form for breve,long,maxima");
   opts.define("B|adjust-beams=b", "Adjust beaming for powers of 2 rscaling");

   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, Jan 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 6 Jan 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);
   }

   PerlRegularExpression pre;
   if (pre.search(opts.getString("factor"), "(\\d+)\\/?(\\d*)", "")) {
      int top = 1;
      int bot = 1;
      top = atoi(pre.getSubmatch(1));
      if (strlen(pre.getSubmatch(2)) != 0) {
         bot = atoi(pre.getSubmatch());
         if (bot == 0) {
            bot = 1;
         }
      } else {
         bot = 1;
      }
      factor.setValue(top, bot);
   } else {
      factor.setValue(1,1);
      cerr << "Scaling factor set to " << factor << endl;
   }

   debugQ = opts.getBoolean("debug");

   if (debugQ) {
      cerr << "Scaling factor set to " << factor << endl;
   }

   meterQ      = !opts.getBoolean("nometer");
   originalQ   =  opts.getBoolean("original");
   alternateQ  =  opts.getBoolean("alternate");
   longQ       =  opts.getBoolean("long");
   rebeamQ     =  opts.getBoolean("adjust-beams");
}



//////////////////////////////
//
// 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: 2257c7799a9606009bf8cd5663342c1b rscale.cpp [20120727]