//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Jan 20 15:21:48 PST 2011
// Last Modified: Thu Jan 20 15:21:50 PST 2011
// Filename:      ...sig/examples/all/chordmark.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/chordmark.cpp
// Syntax:        C++; museinfo
//
// Description:   Add marks to notes according to their triadic position
//                in a chord.
//

#include <math.h>

#ifndef OLDCPP
   #include <iostream>
   #include <sstream>
   #define SSTREAM stringstream
   #define CSTRING str().c_str()
   using namespace std;
#else
   #include <iostream.h>
   #ifdef VISUAL
      #include <strstrea.h>
   #else
      #include <strstream.h>
   #endif
   #define SSTREAM strstream
   #define CSTRING str()
#endif

#include "humdrum.h"
#include "PerlRegularExpression.h"


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

// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      chordmark(HumdrumFile& infile);
void      printHexColor(double color);
void      printChordMarkInfo(Array<char>& marklevel, 
                                Array<Array<double> >& levelcolor, 
                                Array<int>& markused);
int       getRootTrack(HumdrumFile& infile);
void      markToken(HumdrumFile& infile, int line, int col, 
                                int root);
void      printSubToken(ostream& out, const char* token, int root);
int       updateRoot(HumdrumFile& infile, int line, int root, 
                                int track);

// User interface variables:
Options options;
int    debugQ        = 0;             // used with --debug option
Array<char>           marklevel;   
Array<int>            markused;   
Array<Array<double> > levelcolor;   


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

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

   // initial processing of the command-line options
   checkOptions(options, argc, argv);

   if (options.getArgCount() < 1) {
      infile.read(cin);
   } else {
      infile.read(options.getArg(1));
   }

   chordmark(infile);
   cout << infile;
   printChordMarkInfo(marklevel, levelcolor, markused);

   return 0;
}


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


//////////////////////////////
//
// printChordMarkInfo --
//

void printChordMarkInfo(Array<char>& marklevel, 
      Array<Array<double> >& levelcolor, Array<int>& markused) {
   int i;
   for (i=0; i<marklevel.getSize(); i++) {
      if (marklevel[i] && markused[i]) {
         cout << "!!!RDF**kern: " << marklevel[i] << "= mark color=\"#";
         printHexColor(levelcolor[i][0]);
         printHexColor(levelcolor[i][1]);
         printHexColor(levelcolor[i][2]);
         cout << "\"";
         switch (i) {
            case 0: cout << ", root";              break;
            case 2: cout << ", third";             break;
            case 4: cout << ", fifth";             break;
            case 6: cout << ", seventh";           break;
            case 1: cout << ", ninth/second";      break;
            case 3: cout << ", eleventh/fourth";   break;
            case 5: cout << ", thirteenth/sixth";  break;
         }
         cout << endl;
      }
   }
}



//////////////////////////////
//
// printHexColor --
//

void printHexColor(double color) {
   if (color > 1.0) {
      color = 1.0;
   }
   if (color < 0.0) {
      color = 0.0;
   }
   int icol = (int)(255 * color);

   int digit1 = icol / 16;
   int digit2 = icol % 16;

   char buffer[32] = {0};
   sprintf(buffer, "%x%x", digit1, digit2);
   cout << buffer;
}



//////////////////////////////
//
// chordmark -- add an up/down stem on notes in **kern data which do not
//     already have stem information.
//

void chordmark(HumdrumFile& infile) {
   int roottrack = getRootTrack(infile);

   int curroot = -1;

   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isData()) {
         continue;
      }
      curroot = updateRoot(infile, i, curroot, roottrack);
      if (curroot < 0) {
         // current root is inactive, so don't try to mark notes
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (!infile[i].isExInterp(j, "**kern")) {
            continue;
         }
         markToken(infile, i, j, curroot);
      }
   }



}


//////////////////////////////
//
// markToken --
//

void  markToken(HumdrumFile& infile, int line, int col, int root) {
   int& i = line;
   int& j = col;
   if (strcmp(infile[i][j], ".") == 0) {
      // don't mark null tokens
      return;
   }
   if (strchr(infile[i][j], 'r') != NULL) {
      // don't mark rests
      return;
   }
   if (root < 0) {
      // root is invalid
      return;
   }

   SSTREAM newmark;
   int k;
   char buffer[128] = {0};
   int tcount = infile[i].getTokenCount(j);
   for (k=0; k<tcount; k++) {
       infile[i].getToken(buffer, j, k, 100);
       printSubToken(newmark, buffer, root);
       if (k < tcount - 1) {
          newmark << ' ';
       }
   }

   infile[i].changeField(j, newmark.CSTRING);
}



//////////////////////////////
//
// printSubToken --
//

void printSubToken(ostream& out, const char* token, int root) {
   int diatonic = Convert::kernToDiatonicPitch(token) % 7;
   int position = ((diatonic + 70) - root) % 7;
   out << token;
   if (marklevel[position]) {
      markused[position]++;
      out << marklevel[position];
   }
}



//////////////////////////////
//
// updateRoot --
//

int updateRoot(HumdrumFile& infile, int line, int root, int track) {
   int& i = line;
   int j;
   for (j=0; j<infile[i].getFieldCount(); j++) {
      if (track != infile[i].getPrimaryTrack(j)) {
         continue;
      }
      if (strcmp(infile[i][j], ".") == 0) {
         // nothing to update with
         return root;
      }
      if (strchr(infile[i][j], 'R') != NULL) {
         // rest, return root off marker
         return -1;
      }
      return Convert::kernToDiatonicPitch(infile[i][j]) % 7;
   }

   return root;
}



//////////////////////////////
//
// getRootTrack --
//

int getRootTrack(HumdrumFile& infile) {
   int i, j;
   for (i=0; i<infile.getNumLines(); i++) {
      if (!infile[i].isInterpretation()) {
         continue;
      }
      for (j=0; j<infile[i].getFieldCount(); j++) {
         if (infile[i].isExInterp(j, "**root")) {
            return infile[i].getPrimaryTrack(j);
         }
      }
   }
   
   cerr << "Could not find root spine\n" << endl;
   exit(1);
}



//////////////////////////////
//
// checkOptions -- 
//

void checkOptions(Options& opts, int argc, char** argv) {
   opts.define("d|debug=b",    "Debugging information");

   opts.define("author=b",    "Program author");
   opts.define("version=b",   "Program version");
   opts.define("example=b",   "Program examples");
   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, December 2010" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 26 December 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);
   }

   debugQ        = opts.getBoolean("debug");

   marklevel.setSize(7);
   marklevel.allowGrowth(0);
   marklevel.setAll(0);   

   markused.setSize(7);
   markused.allowGrowth(0);
   markused.setAll(0);   

   levelcolor.setSize(7);
   levelcolor.allowGrowth(0);
   int i;
   for (i=0; i<levelcolor.getSize(); i++) {
      levelcolor[i].setSize(3);
      levelcolor[i].allowGrowth(0);
      levelcolor[i].setAll(0);
   }

   // default coloring:
   marklevel[0] = 'N';
   marklevel[2] = 'U';
   marklevel[4] = 'Z';
  
   // Root = red   
   levelcolor[0][0] = 1.0;
   levelcolor[0][1] = 0.0;
   levelcolor[0][2] = 0.0;

   // third = green   
   levelcolor[2][0] = 0.0;
   levelcolor[2][1] = 0.7;
   levelcolor[2][2] = 0.0;

   // fifth = blue   
   levelcolor[4][0] = 0.0;
   levelcolor[4][1] = 0.7;
   levelcolor[4][2] = 0.0;

   // seventh = fuchsia   
   levelcolor[6][0] = 1.0;
   levelcolor[6][1] = 0.0;
   levelcolor[6][2] = 1.0;

   // ninth/second = orange   
   levelcolor[1][0] = 1.0;
   levelcolor[1][1] = 0.4;
   levelcolor[1][2] = 0.0;

   // eleventh/fourth = light blue   
   levelcolor[3][0] = 0.0;
   levelcolor[3][1] = 1.0;
   levelcolor[3][2] = 1.0;

   // thirteenth/sixth = purple
   levelcolor[5][0] = 0.5;
   levelcolor[5][1] = 0.0;
   levelcolor[5][2] = 0.6;

}



//////////////////////////////
//
// example -- example function calls to the program.
//

void example(void) {


}



//////////////////////////////
//
// usage -- command-line usage description and brief summary
//

void usage(const char* command) {

}


// md5sum: c8dee3aebc50cc0e6bf41b03d4397fad chordmark.cpp [20110206]