// counts");
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Dec 16 13:19:14 PST 2004
// Last Modified: Thu Dec 16 13:19:16 PST 2004
// Last Modified: Wed Apr 17 00:38:30 PDT 2013 Enabled multiple segment input
// Last Modified: Tue Jul 15 11:40:47 PDT 2014 Added written pitch count.
// Filename:      ...sig/examples/all/notecount.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/notecount.cpp
// Syntax:        C++; museinfo
//
// Description:   Counts the number of notes in **kern data.
//

#include "humdrum.h"

#include <sstream>
#include <fstream>
#include <iostream>
#include <string>

class CountInfo {
   public:
      CountInfo(void);
      void clear(void);
      int rests;
      int sounding;
      int written;
      string filename;
      Array<int> base40pitch_sounding;
      Array<int> base40pitchclass_sounding;
      Array<int> base40pitch_written;
      Array<int> base40pitchclass_written;
};

CountInfo::CountInfo(void) {
   clear();
}

void CountInfo::clear(void) {
   base40pitch_sounding.setSize(400);
   base40pitchclass_sounding.setSize(40);
   base40pitch_written.setSize(400);
   base40pitchclass_written.setSize(40);

   base40pitch_sounding.setAll(0);
   base40pitchclass_sounding.setAll(0);
   base40pitch_written.setAll(0);
   base40pitchclass_written.setAll(0);

   base40pitch_sounding.allowGrowth(0);
   base40pitchclass_sounding.allowGrowth(0);
   base40pitch_written.allowGrowth(0);
   base40pitchclass_written.allowGrowth(0);

   rests    = 0;
   sounding = 0;
   written  = 0;

   filename.clear();
}


// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      processFile(HumdrumFile& infile, CountInfo& cinfo);
void      printPitchClassInfo(Array<CountInfo>& counts);
void      printPitchInfo(Array<CountInfo>& counts);

// global variables
Options   options;           // database for command-line arguments
int       summaryQ   = 0;    // used with -s option
int       totalQ     = 1;    // used with -T option
int       pcQ        = 0;    // used with -c option
int       pitchQ     = 0;    // used with -p option
int       writtenQ   = 0;    // used with -w option
int       twelveQ    = 1;    // used with -e option


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

int main(int argc, char* argv[]) {
   HumdrumFileSet infiles;
   checkOptions(options, argc, argv);
   infiles.read(options);
   Array<CountInfo> counts(infiles.getCount());
   string filename;
   int i;

   for (i=0; i<infiles.getCount(); i++) {
      processFile(infiles[i], counts[i]);
   }

   if (summaryQ) {
      int sounding    = 0;
      int written     = 0;
      int rests       = 0;
      for (i=0; i<counts.getSize(); i++) {
         sounding += counts[i].sounding;
         written  += counts[i].written;
         rests    += counts[i].rests;
      }
      cout << sounding << "\t" << written << "\t" << rests << endl;
   } else if (pcQ) {
      printPitchClassInfo(counts);
   } else if (pitchQ) {
      printPitchInfo(counts);
   } else {
      cout << "**file\t**sound\t**print\t**rests\n";
      for (i=0; i<counts.getSize(); i++) {
         filename = counts[i].filename;
         if (filename.compare("") == 0) {
            filename = ".";
         }
         cout << filename           << "\t";
         cout << counts[i].sounding << "\t";
         cout << counts[i].written  << "\t";
         cout << counts[i].rests    << endl;
      }
      if (totalQ && (counts.getSize() > 1)) {
         int sounding    = 0;
         int written     = 0;
         int rests       = 0;
         for (i=0; i<counts.getSize(); i++) {
            sounding += counts[i].sounding;
            written  += counts[i].written;
            rests    += counts[i].rests;
         }
         cout << "!!sounding-notes:\t" << sounding << endl;
         cout << "!!written-notes:\t" << written << endl;
         cout << "!!written-rests:\t" << rests << endl;
      }
      cout << "*-\t*-\t*-\t*-\n";
   }

   return 0;
}


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


void printPitchInfo(Array& counts) {
   // do nothing for now...
}


//////////////////////////////
//
// printPitchClassInfo --
//

void printPitchClassInfo(Array& counts) {
   Array<string> labels;
   Array<int>    totals12;
   Array<int>    totals40;
   if (twelveQ) {
      labels.setSize(12);
   } else {
      labels.setSize(40);
   }
   totals40.setSize(40);
   totals40.setAll(0);
   totals40.allowGrowth(0);
   totals12.setSize(12);
   totals12.setAll(0);
   totals12.allowGrowth(0);
   labels.allowGrowth(0);
   int base12;
   char buffer[1024] = {0};
   int i, j;


   for (i=0; i<counts.getSize(); i++) {
      if (twelveQ) {
         for (j=0; j<40; j++) {
            base12 = (Convert::base40ToMidiNoteNumber(j) + 120) % 12;
            if (writtenQ) {
               totals12[base12] += counts[i].base40pitchclass_written[j];
            } else {
               totals12[base12] += counts[i].base40pitchclass_sounding[j];
            }
         }
      }
      for (j=0; j<40; j++) {
         if (writtenQ) {
            totals40[j] += counts[i].base40pitchclass_written[j];
         } else {
            totals40[j] += counts[i].base40pitchclass_sounding[j];
         }
      }
   }

   if (twelveQ) {
      // 0	2	C
      // 0	38	B#
      // 0	6	D--
      if ((totals40[2] > totals40[38]) && (totals40[2] > totals40[6])) {
         labels[0] = "C";
      } else if (totals40[38] > totals40[6]) {
         labels[0] = "B#";
      } else {
         labels[0] = "D--";
      }

      // 1	3	C#
      // 1	39	B##
      // 1	7	D-
      if ((totals40[3] > totals40[39]) && (totals40[3] > totals40[7])) {
         labels[1] = "C#";
      } else if (totals40[39] > totals40[7]) {
         labels[1] = "B##";
      } else {
         labels[1] = "D-";
      }

      // 2	12	E--
      // 2	4	C##
      // 2	8	D
      if ((totals40[12] > totals40[4]) && (totals40[12] > totals40[8])) {
         labels[2] = "E--";
      } else if (totals40[4] > totals40[8]) {
         labels[2] = "C##";
      } else {
         labels[2] = "D";
      }

      // 3	9	D#
      // 3	13	E-
      // 3	17	F--
      if ((totals40[9] > totals40[13]) && (totals40[9] > totals40[17])) {
         labels[3] = "D#";
      } else if (totals40[13] > totals40[17]) {
         labels[3] = "E-";
      } else {
         labels[3] = "F--";
      }

      // 4	10	D##
      // 4	14	E
      // 4	18	F-
      if ((totals40[10] > totals40[14]) && (totals40[10] > totals40[18])) {
         labels[4] = "D##";
      } else if (totals40[14] > totals40[18]) {
         labels[4] = "E";
      } else {
         labels[4] = "F-";
      }

      // 5	15	E#
      // 5	19	F
      // 5	23	G--
      if ((totals40[15] > totals40[19]) && (totals40[15] > totals40[23])) {
         labels[5] = "E#";
      } else if (totals40[19] > totals40[23]) {
         labels[5] = "F";
      } else {
         labels[5] = "G--";
      }

      // 6	16	E##
      // 6	20	F#
      // 6	24	G-
      if ((totals40[16] > totals40[20]) && (totals40[16] > totals40[24])) {
         labels[6] = "E##";
      } else if (totals40[20] > totals40[24]) {
         labels[6] = "F#";
      } else {
         labels[6] = "G-";
      }

      // 7	21	F##
      // 7	25	G
      // 7	29	A--
      if ((totals40[21] > totals40[25]) && (totals40[21] > totals40[29])) {
         labels[7] = "F##";
      } else if (totals40[25] > totals40[29]) {
         labels[7] = "G";
      } else {
         labels[7] = "A--";
      }

      // 8	26	G#
      // 8	30	A-
      if (totals40[26] > totals40[30]) {
         labels[8] = "G#";
      } else {
         labels[8] = "A-";
      }

      // 9	27	G##
      // 9	31	A
      // 9	35	B--
      if ((totals40[27] > totals40[31]) && (totals40[27] > totals40[35])) {
         labels[9] = "G##";
      } else if (totals40[31] > totals40[35]) {
         labels[9] = "A";
      } else {
         labels[9] = "B--";
      }

      // 10	0	C--
      // 10	32	A#
      // 10	36	B-
      if ((totals40[0] > totals40[32]) && (totals40[0] > totals40[36])) {
         labels[10] = "C--";
      } else if (totals40[32] > totals40[36]) {
         labels[10] = "A#";
      } else {
         labels[10] = "B-";
      }

      // 11	1	C-
      // 11	33	A##
      // 11	37	B
      if ((totals40[1] > totals40[33]) && (totals40[1] > totals40[37])) {
         labels[11] = "C-";
      } else if (totals40[33] > totals40[37]) {
         labels[11] = "A##";
      } else {
         labels[11] = "B-";
      }
   } else {
      for (i=0; i<40; i++) {
         labels[i] = Convert::base40ToKern(buffer, i+40*3);
      }
   }

   string filename;
   Array<int> pc12counts(12);
   if (twelveQ) {
      for (i=0; i<totals12.getSize(); i++) {
         cout << "**count\t";
      }
      cout << "**total\t**file" << endl;
      if (writtenQ) {
         cout << "!! Written note counts by pitch class" << endl;
      } else {
         cout << "!! Sounding note counts by pitch class" << endl;
      }
      for (i=0; i<totals12.getSize(); i++) {
         cout << "!" << labels[i] << "\t";
      }
      cout << "!all\t!filename" << endl;
      for (i=0; i<counts.getSize(); i++ ) {
         pc12counts.setAll(0);
         for (j=0; j<40; j++) {
            base12 = (Convert::base40ToMidiNoteNumber(j) + 120) % 12;
            if (writtenQ) {
               pc12counts[base12] += counts[i].base40pitchclass_written[j];
            } else {
               pc12counts[base12] += counts[i].base40pitchclass_sounding[j];
            }
         }

         for (j=0; j<12; j++) {
            cout << pc12counts[j] << "\t";
         }
         if (writtenQ) {
            cout << counts[i].written;
         } else {
            cout << counts[i].sounding;
         }
         cout << "\t";
         filename = counts[i].filename;
         if (filename.compare("") == 0) {
            filename = ".";
         }
         cout << filename << endl;
      }
      if (counts.getSize() > 1) {
         int sum = 0;
         for (j=0; j<totals12.getSize(); j++) {
            sum += totals12[j];
         }
         for (j=0; j<totals12.getSize(); j++) {
            cout << totals12[j] << "\t";
         }
         cout << sum;
         cout << "\t[TOTAL]";
         cout << endl;
      }
      for (j=0; j<12; j++) {
         cout << "*-\t";
      }
      cout << "*-\t*-";
      cout << endl;
   } else {


   }


}



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

void processFile(HumdrumFile& infile, CountInfo& cinfo) {
   int i, j, k;
   cinfo.clear();
   cinfo.filename = infile.getFilename();
   Array<Array<char> > subtokens;
   int base40   = 0;
   int base40pc = 0;

   for (i=0; i<infile.getNumLines(); i++) {
      if (infile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         if (strcmp(infile[i][j], ".") == 0) {
            continue;
         }

         infile[i].getTokens(subtokens, j);
         
         for (k=0; k<subtokens.getSize(); k++) {
            if (strchr(subtokens[k].getBase(), 'r') != NULL) {
               cinfo.rests++;
               continue;
            }
            cinfo.written++;
            if (pcQ) {
               base40 = Convert::kernToBase40(subtokens[k].getBase());
               base40pc = base40 % 40;
               cinfo.base40pitch_written[base40]++;
               cinfo.base40pitchclass_written[base40pc]++;
            }
            if (strchr(subtokens[k].getBase(), '_') != NULL) {
               continue;
            }
            if (strchr(subtokens[k].getBase(), ']') != NULL) {
               continue;
            }
            cinfo.sounding++;
            if (pcQ) {
               cinfo.base40pitch_sounding[base40]++;
               cinfo.base40pitchclass_sounding[base40pc]++;
            }
         }
      }
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("s|summary=b", "just give the raw numbers");
   opts.define("T|no-total=b", "don't give the total count");
   opts.define("c|pc|pitch-class=b", "Display counts by pitch class");
   opts.define("p|pitch=b", "Display counts by absolute pitch");
   opts.define("e|enharmonic=b", "don't collapse to base12");
   opts.define("w|written=b", "written pitch for pitch(class) counts");
   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, Dec 2004" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: July 2014" << 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);
   }

   writtenQ  =  opts.getBoolean("written");
   summaryQ  =  opts.getBoolean("summary");
   totalQ    = !opts.getBoolean("no-total");
   pcQ       =  opts.getBoolean("pitch-class");
   pitchQ    =  opts.getBoolean("pitch");
   if (pcQ && pitchQ) {
      pitchQ = 0;
   }
   twelveQ   = !opts.getBoolean("enharmonic");
}



//////////////////////////////
//
// 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: 981d7a463391aa16826ddb02b8e1d152 notecount.cpp [20140731]