//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Dec  6 23:09:26 PST 2001
// Last Modified: Sun Dec  9 17:32:12 PST 2001
// Filename:      ...sig/examples/all/attacksum.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/attacksum.cpp
// Syntax:        C++; museinfo
//
// Description:   Generate a rhythmic summary of each measure by
//                generating a binary number based on note attacks.
//
//                This program could also be modified to handle doubles
//                for the summation, then non integer attack locations
//                could be linearly distributed between positions.
// 
// Options:       -f field numbers separated by dash for range and/or comma
//                   Gives the fields to use for analysis.
//                -d Divisions per quarter note duration to provied analysis.
//                -b binary form only -- do not sum number of attacks.
//                -s sum all metric positions.
//                -r raw summation data only (rid -GLId applied).
//                -a append summation data to input data.
//                -c code table version
//                -C display code table
//
// Example analysis:  400321530043204
//

#include "humdrum.h"

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

typedef Array<int>  ArrayInt;
typedef Array<char> ArrayChar;

// function declarations
void   checkOptions(Options& opts, int argc, char* argv[]);
void   example(void);
void   usage(const char* command);
void   ZeroArray(Array<int>& array, int value = 0);
void   printAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
                            Array<int>& line, Array<double>& beats, 
                            double divisions);
void   generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis,
                            Array<int>& line, Array<double>& beats, 
                            double divisions);
void   addLineToSum(HumdrumFile& infile, int line, 
                            Array<int>& sumarray, Array<int>& fields,
                            double divisions);
void   generateFieldList(Array<int>& fieldList,const char* fieldstring);
char   printDigit(int number);
void   printSum(Array<int>& analysis);
void   clearAnalysis(Array<ArrayInt>& array);
int    stringcompare(const void* a, const void* b);
int    findInList(Array<ArrayChar>& codeTable, 
                            Array<char> searchstring);
void   switchArrays(Array<ArrayChar>& codeTable, int currentpos,
                            int founditem);
void   replaceWithCode(Array<char>& tableitem, int founditem);
void   generateCodeTable(Array<ArrayChar>& codeTable,  
                            Array<ArrayInt>& analysis);
void   printBase26(int i);


// global variables
Options      options;            // database for command-line arguments
int          debugQ      = 0;    // used with the --debug option int
int	     appendQ     = 0;    // used with the -a option
int          rawQ        = 0;    // used with the -r option
int          sumQ        = 0;    // used with the -s option
int          binaryQ     = 0;    // display only binary attacks
double       qdiv        = 4.0;  // number of divisions per quarter note
int          maxfield    = 0;    // for -f option
int          fieldsQ     = 0;    // for -f option
Array<int>   fields;             // **kern fields to decide attacks
Array<int>   totalsum;           // for -s option
const char*  CurrentFile = "";   // for -s option
int          codeQ       = 0;    // for -c option
int          codeTableQ  = 0;    // for -C 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();

   Array<ArrayInt> analysis;
   Array<double> beats;
   Array<int> line;
   analysis.setGrowth(500);
   analysis.setSize(0);
   beats.setSize(500);
   beats.setGrowth(500);
   beats.setSize(0);
   line.setSize(500);
   line.setGrowth(500);
   line.setSize(0);

   int i;
   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 {
         CurrentFile = options.getArg(i+1);
         infile.read(options.getArg(i+1));
      }

      clearAnalysis(analysis);
      generateAnalysis(infile, analysis, line, beats, qdiv);
      printAnalysis(infile, analysis, line, beats, qdiv);
   }

   if (sumQ && numinputs > 1) {
      cout << "TOTAL:\t";
      for (i=0; i<totalsum.getSize(); i++) {
         cout << totalsum[i];
         if (i < totalsum.getSize() - 1) {
            cout << " ";
         }
      }
      cout << endl;
   }

   return 0;
}


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


//////////////////////////////
//
// clearAnalysis --
// 

void clearAnalysis(Array& array) {
   int i;
   for (i=0; i<array.getSize(); i++) {
      array[i].setSize(0);
   }
   array.setSize(0);
}



//////////////////////////////
//
// generateAnalysis --
//

void generateAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis, 
      Array<int>& line, Array<double>& beats, double divisions) {
   line.setSize(infile.getNumLines());
   line.setSize(0);
   line.allowGrowth(1);
   infile.analyzeRhythm("4");
   Array<int> measuresum;
   measuresum.setSize(5);
   ZeroArray(measuresum);
   int currentLine = -1;
   double meterbeats = 4.0;
   int top = 4;
   int bottom = 4;
   int newsize;

   int i;
   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "Line " << i + 1 << ": " << infile[i] << endl;
      }
      switch (infile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
         case E_humrec_data_comment:
            break;
         case E_humrec_data_kern_measure:      // process current sum
            // store old array
            if (currentLine < 0) {
               currentLine = i;
            }
            line.append(currentLine);
            analysis.append(measuresum);
            beats.append(meterbeats);
            currentLine = -1;
            newsize = (int)(divisions * meterbeats);
            if (newsize == 0) {
               newsize = 1;
            }
            measuresum.setSize(newsize);
            ZeroArray(measuresum);
            break;
         case E_humrec_interpretation:
            if (strncmp(infile[i][0], "*M", 2) == 0) {
               int count = sscanf(infile[i][0], "*M%d/%d", &top, &bottom);
               if (count == 2) {
                  meterbeats = 4.0 * top / bottom;
                  if (currentLine == -1) {
                     newsize = (int)(divisions * meterbeats);
                     if (newsize == 0) {
                        newsize = 1;
                     }
                     measuresum.setSize(newsize);
                     ZeroArray(measuresum);
                  }
               }
            }
            break;
         case E_humrec_data:                   // store attacks
            if (currentLine == -1) {
               currentLine = i;
            }
            addLineToSum(infile, i, measuresum, fields, divisions);
            break;
         default:
            break;
      }
   }
       
   line.allowGrowth(0);
}



//////////////////////////////
//
// addLineToSum --
//

void addLineToSum(HumdrumFile& infile, int line, Array<int>& sumarray, 
   Array<int>& fields, double divisions) {
   int i, j, k;
   int tokencount;
   int pitch = 0;
   int position;
   char buffer[1024] = {0};
   if (fieldsQ) {

      for (i=0; i<fields.getSize(); i++) {
         for (j=0; j<infile[line].getFieldCount(); j++) {
            if (infile[line].getPrimaryTrack(j) == fields[i]) {
               tokencount = infile[line].getTokenCount(j);
               if (strcmp(infile[line].getExInterp(j), "**kern") != 0) {
                  // ignore non-kern spines
                  continue;
               }
               for (k=0; k<infile[line].getTokenCount(j); k++) {
                  infile[line].getToken(buffer, j, k);
                  if (strcmp(buffer, ".") == 0) {
                     continue;    // ignore null tokens
                  }
                  if (strchr(buffer, '_') != NULL) {
                     continue;    // ignore continued tie notes
                  }
                  if (strchr(buffer, ']') != NULL) {
                     continue;    // ignore ending tie notes
                  }
                  position = (int)((infile[line].getBeat()-1)*divisions+0.001);
                  pitch = Convert::kernToBase40(buffer);
                  if (pitch > 0) {
                     sumarray[position]++;
                  } // otherwise a rest
               }
            }
         }
      }

   } else {


      for (j=0; j<infile[line].getFieldCount(); j++) {
         tokencount = infile[line].getTokenCount(j);
         if (strcmp(infile[line].getExInterp(j), "**kern") != 0) {
            // ignore non-kern spines
            continue;
         }
         for (k=0; k<infile[line].getTokenCount(j); k++) {
            infile[line].getToken(buffer, j, k);
            if (strcmp(buffer, ".") == 0) {
               continue;    // ignore null tokens
            }
            if (strchr(buffer, '_') != NULL) {
               continue;    // ignore continued tie notes
            }
            if (strchr(buffer, ']') != NULL) {
               continue;    // ignore ending tie notes
            }
            position = (int)((infile[line].getBeat()-1)*divisions+0.001);
            pitch = Convert::kernToBase40(buffer);
            if (pitch > 0) {
               sumarray[position]++;
            } // otherwise a rest
         }
      }
   }
}



//////////////////////////////
//
// ZeroArray -- set all values in array to zero.
//

void ZeroArray(Array& array, int value) {
   for (int i=0; i<array.getSize(); i++) {
      array[i] = value;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|append=b",         "append analysis to data in output");   

   opts.define("b|binary=b",         "count presence/absence of beats only");   
   opts.define("f|fields=s",         "field selection list");   
   opts.define("d|divisions=d:4.0",  "divisions per quarter note");   
   opts.define("r|raw=b",            "raw analysis output");   
   opts.define("s|sum=b",            "add attack pattern for all measures");   

   opts.define("c|codes=b",          "convert measure sums to codes");   
   opts.define("C|table=b",          "display the code table translations");   

   opts.define("debug=b",  "trace input parsing");   
   opts.define("author=b",  "author of the program");   
   opts.define("version=b", "compilation information"); 
   opts.define("example=b", "example usage"); 
   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, Dec 2000" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 6 Dec 2000" << 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");
   appendQ = opts.getBoolean("append");
   binaryQ = opts.getBoolean("binary");
   rawQ    = opts.getBoolean("raw");
   sumQ    = opts.getBoolean("sum");
   codeQ   = opts.getBoolean("codes");
   codeTableQ = opts.getBoolean("table");
   qdiv    = opts.getDouble("divisions");
   generateFieldList(fields, opts.getString("fields"));
   fieldsQ = opts.getBoolean("fields");

}



//////////////////////////////
//
// example -- example usage of the maxent program
//

void example(void) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// printAnalysis -- 
//

void printAnalysis(HumdrumFile& infile, Array<ArrayInt>& analysis, 
      Array<int>& line, Array<double>& beats, double divisions) {
   int i, j;
   int curr = 0;

   Array<ArrayChar> codeTable;
   if (codeQ || codeTableQ) {
      generateCodeTable(codeTable, analysis);
   }


   if (appendQ) {

      for (i=0; i<infile.getNumLines(); i++) {
         switch (infile[i].getType()) {
            case E_humrec_none:
            case E_humrec_empty:
            case E_humrec_global_comment:
            case E_humrec_bibliography:
               cout << infile[i] << "\n";
               break;
            case E_humrec_data_comment:
               cout << infile[i] << "\t!\n";
               break;
            case E_humrec_data_kern_measure:
               cout << infile[i] << "\t=\n";
               break;
            case E_humrec_interpretation:
               if (strncmp(infile[i][0], "**", 2) == 0) {
                  cout << infile[i] << "\t**asum\n";
               } else if (infile[i].equalFieldsQ() && strcmp(infile[i][0], "*-") == 0) {
                  cout << infile[i] << "\t*-\n";
               } else if (strncmp(infile[i][0], "*M", 2) == 0) {
                  cout << infile[i] << "\t" << infile[i][0] << "\n";
               } else {
                  cout << infile[i] << "\t*\n";
               }
               break;
            case E_humrec_data:
               if (curr < analysis.getSize() && i >= line[curr]) {
                  cout << infile[i] << "\t";
                  if (codeQ) {
                     cout << codeTable[curr].getBase();
                  } else {
                     printSum(analysis[curr]);
                  }
                  cout << "\n";
                  curr++;
               } else {
                  cout << infile[i] << "\t.\n";
               }
               break;
            default:
               cout << infile[i] << "\n";
               break;
         }
      }

   } else if (rawQ) {
      for (i=0; i<analysis.getSize(); i++) {
         if (codeQ) {
            cout << codeTable[i].getBase() << "\n";
         } else {
            for (j=0; j<analysis[i].getSize(); j++) {
               cout << printDigit(analysis[i][j]);
            }
            cout << "\n";
         }
      }
   } else if (sumQ) {
      int maxsize = 0;
      for (i=0; i<analysis.getSize(); i++) {
         if (analysis[i].getSize() > maxsize) {
            maxsize = analysis[i].getSize();
         } 
      }
      Array<int> summation(maxsize);
      ZeroArray(summation);
      int j;
      for (i=0; i<analysis.getSize(); i++) {
         for (j=0; j<analysis[i].getSize(); j++) {
            summation[j] += analysis[i][j];
         }
      }
      if (options.getArgCount() > 1) {
         cout << CurrentFile << ":\t";
      }
      for (i=0; i<summation.getSize(); i++) {
         cout << summation[i];
         if (i < summation.getSize() - 1) {
            cout << " ";
         }
      }
      cout << endl;

      // store counts in totalsum
      for (i=0; i<summation.getSize(); i++) {
         if (i < totalsum.getSize()) {
            totalsum[i] += summation[i];
         } else {
            totalsum.append(summation[i]);
         }
      }

   } else {

      for (i=0; i<infile.getNumLines(); i++) {
         switch (infile[i].getType()) {
            case E_humrec_global_comment:
            case E_humrec_bibliography:
               cout << infile[i] << "\n";
               break;
            case E_humrec_data_kern_measure:
               cout << infile[i][0] << "\n";
               break;
            case E_humrec_interpretation:
               if (strncmp(infile[i][0], "**", 2) == 0) {
                  cout << "**asum\n";
               } else if (infile[i].equalFieldsQ() && strcmp(infile[i][0], "*-") == 0) {
                  cout << "*-\n";
               } else if (strncmp(infile[i][0], "*M", 2) == 0) {
                  cout << infile[i][0] << "\n";
               } else {
                  // do nothing
               }
               break;
            case E_humrec_data:
               if (curr < line.getSize() && i >= line[curr]) {
                  if (codeQ) {
                     printSum(analysis[curr]);
                  } else {
                     cout << codeTable[curr].getBase();
                  }
                  cout << "\n";
                  curr++;
               } else {
                  // do nothing
               }
               break;
            case E_humrec_data_comment:
            case E_humrec_none:
            case E_humrec_empty:
            default:
               // do nothing
               break;
         }
      }

   }
 
}



//////////////////////////////
//
// generateCodeTable -- convert analysis to a list of unique sequence codes.
//

void generateCodeTable(Array& codeTable, Array& analysis) {
   int i, j; 

   char empty = '\0';
   char element;
   Array<ArrayChar> temptable;
   temptable.setSize(analysis.getSize());
   codeTable.setSize(analysis.getSize());
   for (i=0; i<analysis.getSize(); i++) {
      codeTable[i].setSize(analysis[i].getSize());
      temptable[i].setSize(analysis[i].getSize());
      for (j=0; j<analysis[i].getSize(); j++) {
         element = printDigit(analysis[i][j]);
         if (binaryQ && (element != '0')) {
            element = '1';
         }
         codeTable[i][j] = element;
         temptable[i][j] = element;
      }
      codeTable[i].append(empty);
      temptable[i].append(empty);
   }

   qsort(temptable.getBase(), temptable.getSize(), sizeof(ArrayChar), 
         stringcompare);

   // count how many unique strings there are:
   int count = 0;
   if (temptable.getSize() > 0) {
      count = 1;
   }
   for (i=1; i<temptable.getSize(); i++) {
      if (strcmp(temptable[i].getBase(), temptable[i-1].getBase()) != 0) {
         count++;
      }
   }
//   cout << "!! Unique codes: " << count << endl;

   // fill in the codeTable 
   Array<ArrayChar> uniqtable;

   uniqtable.setSize(count);
   uniqtable.allowGrowth(0);

   int ncount = 0;
   if (temptable.getSize() > 0) {
      uniqtable[0] = temptable[0];
      ncount = 1;
   }

   for (i=1; i<temptable.getSize(); i++) {
      if (strcmp(temptable[i].getBase(), temptable[i-1].getBase()) != 0) {
         uniqtable[ncount] = temptable[i];
         ncount++;
      }
   }

   // now that the codes are uniqued, sort them by the order in which
   // they appear in the music.
   int currentpos = 0;
   int founditem = -1;
   for (i=0; i<codeTable.getSize(); i++) {
      founditem = findInList(uniqtable, codeTable[i]);
      if (founditem > currentpos) {
         switchArrays(uniqtable, currentpos, founditem);
         founditem = currentpos;
         currentpos++;
      }
      replaceWithCode(codeTable[i], founditem);
   }

   // print the uniq table if user requests it:
   if (codeTableQ) {
      for (i=0; i<uniqtable.getSize(); i++) {
         cout << "!!!code:\t";
         printBase26(i);
         cout << "\t" << uniqtable[i].getBase() << "\n";
      }
   }
}


//////////////////////////////
//
// printBase26 --
//

void printBase26(int i) {
   int digit;

   if (i >= 26*26*26) {
      digit = i/26*26*26;
      cout << (char)('A' + digit - 1);
      i -= digit * 26 * 26 * 26;
   }

   if (i >= 26*26) {
      digit = i/26*26;
      cout << (char)('A' + digit - 1);
      i -= digit * 26 * 26;
   }

   if (i >= 26) {
      digit = i/26;
      cout << (char)('A' + digit - 1);
      i -= digit * 26;
   }

   digit = i;
   cout << (char)('A' + digit);
}



//////////////////////////////
//
// replaceWithCode --
//

void replaceWithCode(Array& tableitem, int founditem) {
   char buffer[128] = {0};
   int digit;
   char schar[2] = {0};

   if (founditem >= 26*26*26) {
      digit = founditem/26*26*26;
      schar[0] = 'A' + digit - 1;
      strcat(buffer, schar);
      founditem -= digit * 26 * 26 * 26;
   }

   if (founditem >= 26*26) {
      digit = founditem/26*26;
      schar[0] = 'A' + digit - 1;
      strcat(buffer, schar);
      founditem -= digit * 26 * 26;
   }

   if (founditem >= 26) {
      digit = founditem/26;
      schar[0] = 'A' + digit - 1;
      strcat(buffer, schar);
      founditem -= digit * 26;
   }

   digit = founditem;
   schar[0] = 'A' + digit;
   strcat(buffer, schar);

   int length = strlen(buffer);
   tableitem.setSize(length+1);
   int i;
   for (i=0; i<length+1; i++) {
      tableitem[i] = buffer[i];
   }
}



//////////////////////////////
//
// switchArrays --
//

void switchArrays(Array& codeTable, int currentpos, int founditem) {
   if (founditem < 0) {
      return;
   }
   ArrayChar temp = codeTable[currentpos];
   codeTable[currentpos] = codeTable[founditem];
   codeTable[founditem] = temp;
}



//////////////////////////////
//
// findInList --
//

int findInList(Array& codeTable, Array searchstring) {
   int i;
   for (i=0; i<codeTable.getSize(); i++) { 
      if (strcmp(codeTable[i].getBase(), searchstring.getBase()) == 0) {
         return i;
      } 
   }
   return -1;
}



//////////////////////////////
//
// printSum --
//

void printSum(Array& analysis) {
   int i;
   for (i=0; i<analysis.getSize(); i++) {
      cout << printDigit(analysis[i]);
   }
}



//////////////////////////////
//
// usage -- gives the usage statement for the quality program
//

void usage(const char* command) {
   cout <<
   "                                                                        \n"
   << endl;
}



//////////////////////////////
//
// generateFieldList -- taken from eised
//

void generateFieldList(Array& fieldList, const char* fieldstring) {
   fieldList.setSize(100);
   fieldList.setGrowth(100);
   fieldList.setSize(0);
   maxfield = 0;
   int length = strlen(fieldstring);
   char* buffer = new char[length+1];
   strcpy(buffer, fieldstring);
   int starti, stopi;
   int temp;
   int num;
   char* ptr = strtok(buffer, " ,;:");
   while (ptr != NULL) {
      if (strchr(ptr, '-') != NULL) {
         sscanf(ptr, "%d-%d", &starti, &stopi);
         if (starti > stopi) {
            temp = starti;
            starti=stopi;
            stopi = temp;
         } 
         for (num=starti; num<=stopi; num++) {
            fieldList.append(num);
         }
         if (num > maxfield) {
            maxfield = num;
         }
      } else {
         sscanf(ptr, "%d", &num);
         fieldList.append(num);
         if (num > maxfield) {
            maxfield = num;
         }
      }
      ptr = strtok(NULL, " ,;:");
   }

   fieldList.allowGrowth(0);
}



//////////////////////////////
//
// printDigit -- print digit in base 36 with cut off if too large
//

char printDigit(int number) {
   if (number <= 0) {
      return '0';
   } 
   if (binaryQ) {
      return '1';
   }

   if (number < 10) {
      return '0' + number;
   } 
   number = number - 10;
   if (number >= 25) {
      return 'Z';
   }
   return 'A' + number;
}


//////////////////////////////
//
// stringcompare --
//

int stringcompare(const void* a, const void* b) {
   ArrayChar& A = *((ArrayChar*)a);
   ArrayChar& B = *((ArrayChar*)b);

   return strcmp(A.getBase(), B.getBase());
}



// md5sum: 1bf8337b37053a233ac3cf33d209e112 attacksum.cpp [20050403]