//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat Jun  9 14:48:41 PDT 2001
// Last Modified: Sun Jun 20 13:35:41 PDT 2010 added rational rhythms
// Last Modified: Wed Jan 26 18:14:20 PST 2011 added --debug
// Last Modified: Tue Apr 12 12:04:23 PDT 2011 fixed findlcm for rational rhys.
// Last Modified: Mon Apr 15 20:20:18 PDT 2013 Enabled multiple segment input
// Filename:      ...museinfo/examples/all/minrhy.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/minrhy.cpp
// Syntax:        C++; museinfo
//
// Description:   calculates the minimum timebase which is the least common
//                multiple of all rhythms in the file.
//
// Todo: some things to clean up in rhythmic representation due to conversions
// from ints to RationalNumber class for rhythms/durations...

#include "humdrum.h"

// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
int       findlcm(Array<int>& list);
int       findlcmR(Array<RationalNumber>& list);
int       GCD(int a, int b);
void      insertRhythm(Array<RationalNumber>& allrhythms, 
                                 RationalNumber value);
void      sortArray(Array<RationalNumber>& rhythms);
int       ratcomp(const void* a, const void* b);

template<class type>
void uniqArray(Array<type>& array);

// global variables
Options   options;             // database for command-line arguments
int       listQ = 0;           // used with -l option
int       debugQ = 0;          // used with --debug


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


int main(int argc, char** argv) {
   checkOptions(options, argc, argv);

   HumdrumFileSet infiles;

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

   if (numinputs < 1) {
      infiles.read(cin);
   } else {
      for (i=0; i<numinputs; i++) {
         infiles.readAppend(options.getArg(i+1));
      }
   }

   Array<RationalNumber> timebase;
   RationalNumber zeroR(0, 1);
   timebase.setSize(infiles.getCount());
   timebase.setAll(zeroR);
   timebase.allowGrowth(0);
   Array<RationalNumber> rhythms;
   Array<RationalNumber> allrhythms;
   allrhythms.setSize(100);
   allrhythms.setSize(0);
   allrhythms.setGrowth(1000);

   // can't handle standard input yet
   for (i=0; i<infiles.getCount(); i++) {
      infiles[i].analyzeRhythm();
      if (infiles.getCount() > 1) {
         cout << infiles[i].getFilename() << ":\t";
      }
      if (listQ) {
         infiles[i].getRhythms(rhythms);
         sortArray(rhythms);
         uniqArray(rhythms);
         for (j=0; j<rhythms.getSize(); j++) {
            cout << rhythms[j];
            if (j<rhythms.getSize()-1) {
               cout << " ";
            }
            insertRhythm(allrhythms, rhythms[j]);
         }
	 cout << "\n";
      } else {
         cout << infiles[i].getMinTimeBaseR() << "\n";
      }
      timebase[i] = infiles[i].getMinTimeBaseR();
   }

   if (infiles.getCount() > 1) {
      if (listQ) {
         cout << "all:\t";
         for (j=0; j<allrhythms.getSize(); j++) {
            cout << allrhythms[j];
            if (j < allrhythms.getSize()-1) {
               cout << " ";
            }
         }
	 cout << "\n";
      } else {
         if (infiles.getCount() > 1) {
            cout << "all:\t" << findlcmR(timebase) << endl;
         }
      }
   }
}



//////////////////////////////
//
// sortRatArray --
//

void sortArray(Array& rhythms) {
   qsort(rhythms.getBase(), rhythms.getSize(), sizeof(RationalNumber), ratcomp);
}



//////////////////////////////
//
// uniqArray -- filter out adjacent duplicate elements.
//

template<class type>
void uniqArray(Array& array) {
   Array<type> newarray(array.getSize());
   newarray.setSize(0);
   if (array.getSize() <= 1) {
      // nothing to do
      return;
   }
   newarray.append(array[0]);
   int i;
   for (i=1; i<array.getSize(); i++) {
      if (array[i] ==  array[i-1]) {
         continue;
      }
      newarray.append(array[i]);
   }

   array = newarray;
}



//////////////////////////////
//
// ratcomp -- compare two rational numbers for ordering
//

int ratcomp(const void* a, const void* b) {
   if (*((RationalNumber*)a) < *((RationalNumber*)b)) {
      return -1;
   } else if (*((RationalNumber*)a) > *((RationalNumber*)b)) {
      return 1;
   } else {
      return 0;
   }
}



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



//////////////////////////////
//
// insertRhythm --
//

void insertRhythm(Array& allrhythms, RationalNumber value) {
   int i;
   for (i=0; i<allrhythms.getSize(); i++) {
      if (value == allrhythms[i]) {
         return;
      }
   }
   allrhythms.append(value);
}



//////////////////////////////
//
// findlcm -- find the least common multiple between rhythms
//

int findlcm(Array& rhythms) {
   if (rhythms.getSize() == 0) {
      return 0;
   }
   int output = rhythms[0];
   for (int i=1; i<rhythms.getSize(); i++) {
      output = output * rhythms[i] / GCD(output, rhythms[i]);
   }

   return output;
}



//////////////////////////////
//
// findlcmR -- find the least common multiple between rhythms
//     rational number version.
//

int findlcmR(Array& rhythms) {
   if (rhythms.getSize() == 0) {
      return 0;
   }

   Array<int> plain;
   plain.setSize(rhythms.getSize());
   int i;
   for (i=0; i<rhythms.getSize(); i++) {
      plain[i] = rhythms[i].getNumerator() * rhythms[i].getDenominator();
   }
   return findlcm(plain);
}

 

//////////////////////////////
//
// GCD -- greatest common divisor
//
 
int GCD(int a, int b) {
   if (b == 0) {
      return a;
   }
   int z = a % b;
   a = b;
   b = z;
   int output = GCD(a, b);
   if (debugQ) {  
      cout << "GCD of " << a << " and " << b << " is " << output << endl;
   }
   return output;
}    



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("l|list=b", "list time units used in file");  

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

   if (opts.getArgCount() < 1) {
      cout << "Usage: " << opts.getCommand() << " input-kern-file" << endl;
      exit(1);
   }

   listQ  = opts.getBoolean("list");
   debugQ = opts.getBoolean("debug");
}



//////////////////////////////
//
// 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: 2e2081f3c9c269c9e012127aba61ec02 minrhy.cpp [20111004]