//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Tue Aug  2 13:23:45 PDT 2011
// Last Modified: Tue Aug  2 13:23:48 PDT 2011
// Filename:      ...sig/examples/all/sprialfile.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/sprialfile.cpp
// Syntax:        C++; museinfo
//
// Description:   Convert **kern data into sonorities for sprial analysis.
// 

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

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

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


// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      processFile(ostream& out, HumdrumFile& infile);
void      processDataLine(ostream& out, RationalNumber& rn, 
                              HumdrumFile& infile, int line);
void      addNotes(Array<int>& notes, HumdrumFile& infile, int line, 
                              int spine);
void      printCurrentTime(ostream& out);

// global variables
Options   options;            // database for command-line arguments
int       debugQ;             // used for debugging


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

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

   // process the command-line options
   checkOptions(options, argc, argv);

   const char* filename;
   infile.clear();
   // if no command-line arguments read data file from standard input
   int numinputs = options.getArgCount();
   if (numinputs < 1) {
      infile.read(cin);
   } else {
      filename = options.getArg(1);
      infile.read(options.getArg(1));
   }
   processFile(cout, infile);

}

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


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

void processFile(ostream& out, HumdrumFile& infile) {

   infile.analyzeRhythm();
   RationalNumber rn = infile.getMinTimeBaseR();
   int i;
   PerlRegularExpression pre;

   out << "--LINERHYTHM: " << rn << endl;
   
   for (i=0; i<infile.getNumLines(); i++) {
      if (debugQ) {
         cout << "Processing line " << i + 1 << endl;
      }
      if (infile[i].isBibliographic()) {
         out << "---" << (const char*)(&infile[i][0][3]) << endl;
         continue;
      }
      if (infile[i].isGlobalComment()) {
         out << "--" << (const char*)(&infile[i][0][2]) << endl;
         continue;
      }
      if (infile[i].isMeasure()) {
         out << "------- ";
         if (pre.search(infile[i][0], "==")) {
            out << endl << "------- ";
         } else {
            if (pre.search(infile[i][0], "(\\d+)", "")) {
               int measurenum = atoi(pre.getSubmatch(1));
               out << measurenum;
            }
         }
         out << endl;
         continue;
      }
      if (infile[i].isInterpretation()) {
         if (pre.search(infile[i][0], "^\\*([A-G])([#-]*):(.*)", "i")) {
            const char* ptr = pre.getSubmatch(1);
            int mode = 0;
            char diatonic = ptr[0];
            if (islower(diatonic)) {
               mode = 1;              // minor
            }
            ptr = pre.getSubmatch(3);
            if (strcmp(ptr, "dor") == 0) {
               mode = 2;             // dorian
            } else if (strcmp(ptr, "phr") == 0) {
               mode = 3;             // phrygian
            } else if (strcmp(ptr, "lyd") == 0) {
               mode = 4;             // lydian
            } else if (strcmp(ptr, "mix") == 0) {
               mode = 5;             // mixolydian
            } else if (strcmp(ptr, "loc") == 0) {
               mode = 6;             // locrian
            }
            ptr = pre.getSubmatch(2);
            out << "--MUSICALKEY: " << (char)toupper(diatonic);
            if (strcmp(ptr, "-") == 0) {
               out << "b";
            } else {
               out << ptr;
            }
            switch (mode) {
               case 0:  out << " major"      << endl; break;
               case 1:  out << " minor"      << endl; break;
               case 2:  out << " dorian"     << endl; break;
               case 3:  out << " phrygian"   << endl; break;
               case 4:  out << " lydian"     << endl; break;
               case 5:  out << " mixolydian" << endl; break;
               case 6:  out << " locrian"    << endl; break;
               default: out << endl;
            }
         }
      }
      if (infile[i].isData()) {
         processDataLine(out, rn, infile, i);
         continue;
      }
   }
   printCurrentTime(out);
}


//////////////////////////////
//
// printCurrentTime --
//

void printCurrentTime(ostream& out) {
   out << "---ONB: Converted from Humdrum data on ";
   struct tm *current;
   time_t now;
   time(&now);
   current = localtime(&now);
   out << current->tm_year + 1900 << "/";
   if (current->tm_mon+1 < 10) {
      out << "0";
   }
   out << current->tm_mon + 1     << "/";
   if (current->tm_mday < 10) {
      out << "0";
   }
   out << current->tm_mday        << " ";
  
   if (current->tm_hour < 10) {
      out << "0";
   }
   out << current->tm_hour        << ":";
  
   if (current->tm_min < 10) {
      out << "0";
   }
   out << current->tm_min         << ":";
   if (current->tm_sec < 10) {
      out << "0";
   }
   out << current->tm_sec         << endl;
}



//////////////////////////////
//
// processDataLine --
//

void processDataLine(ostream& out, RationalNumber& rn, HumdrumFile& infile, 
      int line) {

   RationalNumber linedur = infile[line].getDurationR();
   RationalNumber zero(0,1);

   if (debugQ) {
      cout << "LINE DURATION = " << linedur << endl;
   }

   if (linedur == zero) {
      // grace note or other non-durational material.
      return;
   }

   int j;
   int ii, jj;

   Array<int> notes;
   notes.setSize(0);

   RationalNumber repeat = (rn * linedur) / 4;
   if (repeat.getDenominator() != 1) {
      cout << "Something funny happend on repeater: " << repeat << endl;
   }

   for (j=0; j<infile[line].getFieldCount(); j++) {
      if (!infile[line].isExInterp(j, "**kern")) {
         continue;
      }
      ii = line;
      jj = j;
      if (strcmp(infile[line][j], ".") == 0) {
         ii = infile[line].getDotLine(j);
         jj = infile[line].getDotSpine(j);
      }
      addNotes(notes, infile, ii, jj);
   }

   char buffer[1024] = {0};
   int diatonic;
   int accid;
   if (notes.getSize() == 0) {
      for (j=0; j<repeat.getNumerator(); j++) {
         out << "Z";
         out << endl;
      }
   } else {
      for (j=0; j<repeat.getNumerator(); j++) {
         for (int i=0; i<notes.getSize(); i++) {
            diatonic = Convert::base40ToDiatonic(notes[i]);
            accid    = Convert::base40ToAccidental(notes[i]);
            if (debugQ) {
               out << "(" << Convert::base40ToKern(buffer, notes[i]) << ")";
            }
            out << (char) (((diatonic+2) % 7) + 'A');
            if (accid > 0) {
               for (ii=0; ii<accid; ii++) {
                  out << "#";
               }
            } else if (accid < 0) {
               accid = -accid;
               for (ii=0; ii<accid; ii++) {
                  out << "b";
               }
            }
            if (i < notes.getSize() -1) {
               out << " ";
            }
         }
         out << endl;
      }
   }

}



//////////////////////////////
//
// addNotes --
//

void addNotes(Array& notes, HumdrumFile& infile, int line, int spine) {
   int k;
   int base40;
   char buffer[1024] = {0};
   int tokens = infile[line].getTokenCount(spine);
   for (k=0; k<tokens; k++) {
      infile[line].getToken(buffer, spine, k);
      if (strchr(buffer, 'r') != NULL) {
         continue;
      }
      base40 = Convert::kernToBase40(buffer);
      notes.append(base40);
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("p|print|v|visual=b", "convert to printed visual score format");
   opts.define("s|sound=b", "convert to sounding score format");
   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, Aug 2011" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 2 Aug 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");

}



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

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



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

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



// md5sum: b0989e38096d0ff0d371dc1e0dffa74d spiralfile.cpp [20110830]