//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Mon Oct 27 10:42:08 PST 2008
// Last Modified: Thu Nov 13 15:32:17 PST 2008
// Filename:      ...museinfo/examples/all/themax.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/themax.cpp
// Syntax:        C++; museinfo
//
// Description:   searches index created by themebuilderx.  Themax
//                is a C++ implementation of the original thema command
//                which was written by David Huron in 1996/1998, and
//                modified with a few bug fixes during the implementation 
//                of Themefinder.org by Craig Sapp  (1999-2001).
//
// Classical themebuilder entry order:
//    fileid [Zz] { # : % } j J M
// Additional rhythmic marks:
//    ~ ^ ! & @ ` '
//
// Meaning of the tracer symbols:
//  [Zz] = major/minor key 
//  {    = 12-tone interval
//  #    = pitch refined contour
//  :    = pitch gross contour
//  %    = scale degree
//  }    = musical interval
//  j    = 12-tone pitch
//  J    = absolute pitch
//  M    = metric description
// Added rhythmic markers:
//  ~    = duration gross contour
//  ^    = duration refined contour
//  !    = duration (IOI)
//  &    = beat level
//  @    = metric gross contour
//  `    = metric refined contour
//  '    = metric level
//
// Options:
// -a   anchor to start (implemented)
// -M   major keys      (implemented)
// -m   minor keys      (implemented)
// -t   mode            (implemented)
// -T   meter           (implemented)
//
// -i { 12-tone interval    (implemented)
// -c # refined contour	    (implemented)
// -C : gross contour	    (implemented)
// -d % scale degree        (implemented)
// -I } musical interval    (implemented)
// -P j 12-tone pitch class (implemented)
// -p J pitch-class name    (implamented)
//
// do assign an option letter:
//  ~ duration gross contour
//  ^ duration refined contour
//  ! duration (IOI)
//  & beat level
//  @ metric gross contour
//  ` metric refined contour
//  ' metric level
//

#include "humdrum.h"

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

// character markers in index file:
#define PITCH_TWELVE_TONE_INTERVAL_MARKER '{'
#define PITCH_REFINED_CONTOUR_MARKER      '#'
#define PITCH_GROSS_CONTOUR_MARKER        ':'
#define PITCH_SCALE_DEGREE_MARKER         '%'
#define PITCH_MUSICAL_INTERVAL_MARKER     '}'
#define PITCH_TWELVE_TONE_MARKER          'j'
#define PITCH_CLASS_MARKER                'J'
#define DURATION_GROSS_CONTOUR_MARKER     '~'
#define DURATION_REFINED_CONTOUR_MARKER   '^'
#define DURATION_IOI                      '!'
#define BEAT_LEVEL                        '&'
#define METRIC_GROSS_CONTOUR              '@'
#define METRIC_REFINED_CONTOUR            '`'
#define METRIC_LEVEL                      '\''


// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      appendString(Array<char>& ss, const char* string);
void      appendToSearchString(Array<char>& ss, const char* string, 
                                  char marker, int anchor);

// User interface variables:
Options   options;

int         anchoredQ    = 0;       // used with -a option
int         keyfilterQ   = 0;       // used with -m -M and -t options
int         minorQ       = 0;       // used with -m option
int         majorQ       = 0;       // used with -M option
int         debugQ       = 0;       // used with --debug option
int         tonicQ       = 0;       // used with -t option
const char* tonicstring  = "";      // used with -t option
int         meterQ       = 0;       // used with -T option
const char* meterstring  = "";      // used with -T option

// classical searches
int P12toneintervalQ              = 0;    // used with -i option
int PgrosscontourQ                = 0;    // used with -c option
int PrefinedcontourQ              = 0;    // used with -C option
int PscaledegreeQ                 = 0;    // used with -d option
int PmusicalintervalQ             = 0;    // used with -I option
int P12tonepitchclassQ            = 0;    // used with -P option
int PpitchclassQ                  = 0;    // used with -p option

const char* P12toneinterval       = "";   // used with -i option
const char* Pgrosscontour         = "";   // used with -c option
const char* Prefinedcontour       = "";   // used with -C option
const char* Pscaledegree          = "";   // used with -d option
const char* Pmusicalinterval      = "";   // used with -I option
const char* P12tonepitchclass     = "";   // used with -P option
const char* Ppitchclass           = "";   // used with -p option

// extended rhythm searches
int RgrosscontourQ                = 0;    // used with -R option
int RrefinedcontourQ              = 0;    // used with -r option
int RdurationQ                    = 0;    // used with -D option
int RbeatlevelQ                   = 0;    // used with -b option
int RmetriclevelQ                 = 0;    // used with -L option
int RmetricrefinedcontourQ        = 0;    // used with -e option
int RmetricgrosscontourQ          = 0;    // used with -E option

const char* Rgrosscontour         = "";   // used with -R option
const char* Rrefinedcontour       = "";   // used with -r option
const char* Rduration             = "";   // used with -D option
const char* Rbeatlevel            = "";   // used with -b option
const char* Rmetriclevel          = "";   // used with -L option
const char* Rmetricrefinedcontour = "";   // used with -e option
const char* Rmetricgrosscontour   = "";   // used with -E option


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

int main(int argc, char** argv) {
   checkOptions(options, argc, argv); // process the command-line options

   Array<char> searchstring;
   Array<char>& ss = searchstring;
   ss.setSize(1000);
   ss.setGrowth(1000);
   ss.setSize(0);

   appendString(ss, "egrep ");

   // start regular expression string
   appendString(ss, "'");

// order of data in index file:
//  [Zz] = major/minor key  //////////////////////////////////////////////////

   if (keyfilterQ) {
     if (majorQ && !minorQ) {
        appendString(ss, "Z");
     } else if (minorQ && !majorQ) {
        appendString(ss, "z");
     } else {
        appendString(ss, "[Zz]");
     }
     // place the tonic search next if given
     if (tonicQ) {
        appendString(ss, tonicstring);
     } else {
        appendString(ss, "[^=]*");
     }
     // place the end of key information marker:
     appendString(ss, "=.*");
   }



//  {    = 12-tone interval //////////////////////////////////////////////////
   if (P12toneintervalQ) {
      appendToSearchString(ss, P12toneinterval, 
         PITCH_TWELVE_TONE_INTERVAL_MARKER, anchoredQ);
   }

//  #    = pitch refined contour /////////////////////////////////////////////
   if (PrefinedcontourQ) {
      appendToSearchString(ss, Prefinedcontour, 
         PITCH_REFINED_CONTOUR_MARKER, anchoredQ);
   }

//  :    = pitch gross contour ///////////////////////////////////////////////
   if (PgrosscontourQ) {
      appendToSearchString(ss, Pgrosscontour, 
         PITCH_GROSS_CONTOUR_MARKER, anchoredQ);
   }

//  %    = scale degree //////////////////////////////////////////////////////
   if (PscaledegreeQ) {
      appendToSearchString(ss, Pscaledegree, 
         PITCH_SCALE_DEGREE_MARKER, anchoredQ);
   }

//  }    = musical interval //////////////////////////////////////////////////
   if (PmusicalintervalQ) {
      appendToSearchString(ss, Pmusicalinterval, 
         PITCH_MUSICAL_INTERVAL_MARKER, anchoredQ);
   }


//  j    = 12-tone pitch class ///////////////////////////////////////////////
   if (P12tonepitchclassQ) {
      appendToSearchString(ss, P12tonepitchclass, 
         PITCH_TWELVE_TONE_MARKER, anchoredQ);
   }

//  J    = pitch class name //////////////////////////////////////////////////
   if (PpitchclassQ) {
      appendToSearchString(ss, Ppitchclass, 
         PITCH_CLASS_MARKER, anchoredQ);
   }

//  M    = metric description ////////////////////////////////////////////////
   if (meterQ) {
      appendString(ss, "M");
      if (!isdigit(meterstring[0])) {
         appendString(ss, "[^\t]*");
      }
      appendString(ss, meterstring);
      appendString(ss, ".*");
   }

// Added rhythmic markers:

//  ~    = duration gross contour  ///////////////////////////////////////////
   if (RgrosscontourQ) {
      appendToSearchString(ss, Rgrosscontour, 
         DURATION_GROSS_CONTOUR_MARKER, anchoredQ);
   }

//  ^    = duration refined contour  /////////////////////////////////////////
   if (RrefinedcontourQ) {
      appendToSearchString(ss, Rrefinedcontour, 
         DURATION_REFINED_CONTOUR_MARKER, anchoredQ);
   }

//  !    = duration (IOI)  ///////////////////////////////////////////////////
   if (RdurationQ) {
      appendToSearchString(ss, Rduration, 
         DURATION_IOI, anchoredQ);
   }

//  &    = beat level  ///////////////////////////////////////////////////////
   if (RbeatlevelQ) {
      appendToSearchString(ss, Rbeatlevel, 
         BEAT_LEVEL, anchoredQ);
   }

//  @    = metric gross contour  /////////////////////////////////////////////
   if (RmetriclevelQ) {
      appendToSearchString(ss, Rmetriclevel, 
         METRIC_LEVEL, anchoredQ);
   }

//  `    = metric refined contour  ///////////////////////////////////////////
   if (RmetricrefinedcontourQ) {
      appendToSearchString(ss, Rmetricrefinedcontour, 
         METRIC_REFINED_CONTOUR, anchoredQ);
   }

//  '    = metric level  /////////////////////////////////////////////////////
   if (RmetricgrosscontourQ) {
      appendToSearchString(ss, Rmetricgrosscontour, 
         METRIC_GROSS_CONTOUR, anchoredQ);
   }

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

   // terminate regular expression string
   appendString(ss, "'");

   // add index filename to search
   appendString(ss, " ");
   appendString(ss, options.getArgument(1));

   // terminate the search string
   char ch = '\0';
   ss.append(ch);

   if (debugQ) {
      cout << "### Search string: " << ss.getBase() << endl;
   }

   if (system(NULL)) {
      if (system(ss.getBase()) == -1) {
         exit(-1);
      }
   } else {
      cerr << "No command processor available\n";
      exit(1);
   }
   
   return 0;
}

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


//////////////////////////////
//
// appendToSearchString
//

void appendToSearchString(Array<char>& ss, const char* string, 
      char marker, int anchor) {
   char ch;

   // add back quote for certain markers
   switch (marker) {
      case '{':
      case '}':
      case '^':
         ch = '\\'; ss.append(ch);
   }
   ss.append(marker);

   // add [^\t]* if not anchored:
   if (!anchor) {
      appendString(ss, "[^\t]*");
   }
   appendString(ss, string);

   // for pitch-class names, the next character after the search
   // string must be a space or a tab to prevent accidentals
   // from matching on natural-note search endings.
   if (marker == PITCH_CLASS_MARKER) {
      appendString(ss, "[ \t]");
   }

   appendString(ss, ".*");
}



//////////////////////////////
//
// appendString --
//

void appendString(Array& ss, const char* string) {
   int i;
   char ch;
   int length = strlen(string);
   for (i=0; i<length; i++) {
      ch = string[i];
      ss.append(ch);
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("a|A|anchored=b",     "anchored search at start");
   opts.define("m|minor=b",          "search only minor keys");
   opts.define("M|major=b",          "search only minor keys");
   opts.define("t|tonic=s:",         "search only given tonic");
   opts.define("T|meter=s:",         "search only given meters");

   opts.define("i|pitch-12tone-interval=s:",   "12-tone interval");
   opts.define("C|pitch-gross-contour=s:",     "pitch gross contour");
   opts.define("c|pitch-refined-contour=s:",   "pitch refined contour");
   opts.define("d|pitch-scale-degree=s:",      "pitch scale degree");
   opts.define("I|pitch-musical-interval=s:",  "musical interval");
   opts.define("P|pitch-12tonepc=s:",          "12-tone pitch class");
   opts.define("p|pitch-class=s:",             "pitch class");

   opts.define("R|duration-gross-contour=s:",   "duration gross contour");
   opts.define("r|duration-refined-contour=s:", "duration refined contour");
   opts.define("D|duration=s:",                 "duration (IOI)");
   opts.define("b|beat-level=s:",               "beat level");
   opts.define("L|metric-level=s:",             "metric level");
   opts.define("e|metric-refined-contour=s:",   "metric refined contour");
   opts.define("E|metric-gross-contour=s:",     "metric-gross contour");

   //opts.define("i|pitch-twelvetone-interval=s:", "12-tone interval");

   opts.define("debug=b",                       "debugging statements");

   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, Oct 2008" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 24 Nov 2008" << 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);
   }
   
   ////////////////////////////////////////////////
   
   anchoredQ              = opts.getBoolean("anchored");
   debugQ                 = opts.getBoolean("debug");
   majorQ                 = opts.getBoolean("major");
   minorQ                 = opts.getBoolean("minor");
   tonicQ                 = opts.getBoolean("tonic");
   tonicstring            = opts.getString("tonic");
   keyfilterQ             = majorQ || minorQ || tonicQ;
   meterQ                 = opts.getBoolean("meter");
   meterstring            = opts.getString("meter");

   P12toneintervalQ       = opts.getBoolean("pitch-12tone-interval");
   PgrosscontourQ         = opts.getBoolean("pitch-gross-contour");
   PrefinedcontourQ       = opts.getBoolean("pitch-refined-contour");
   PscaledegreeQ          = opts.getBoolean("pitch-scale-degree");
   PmusicalintervalQ      = opts.getBoolean("pitch-musical-interval");
   P12tonepitchclassQ     = opts.getBoolean("pitch-12tonepc");
   PpitchclassQ           = opts.getBoolean("pitch-class");

   P12toneinterval        = opts.getString("pitch-12tone-interval");
   Pgrosscontour          = opts.getString("pitch-gross-contour");
   Prefinedcontour        = opts.getString("pitch-refined-contour");
   Pscaledegree           = opts.getString("pitch-scale-degree");
   Pmusicalinterval       = opts.getString("pitch-musical-interval");
   P12tonepitchclass      = opts.getString("pitch-12tonepc");
   Ppitchclass            = opts.getString("pitch-class");

   RgrosscontourQ         = opts.getBoolean("duration-gross-contour");
   RrefinedcontourQ       = opts.getBoolean("duration-refined-contour");
   RdurationQ             = opts.getBoolean("duration");
   RbeatlevelQ            = opts.getBoolean("beat-level");
   RmetriclevelQ          = opts.getBoolean("metric-level");
   RmetricrefinedcontourQ = opts.getBoolean("metric-refined-contour");
   RmetricgrosscontourQ   = opts.getBoolean("metric-gross-contour");

   Rgrosscontour          = opts.getString("duration-gross-contour");
   Rrefinedcontour        = opts.getString("duration-refined-contour");
   Rduration              = opts.getString("duration");
   Rbeatlevel             = opts.getString("beat-level");
   Rmetriclevel           = opts.getString("metric-level");
   Rmetricrefinedcontour  = opts.getString("metric-refined-contour");
   Rmetricgrosscontour    = opts.getString("metric-gross-contour");

}



//////////////////////////////
//
// example --
//

void example(void) {

}



//////////////////////////////
//
// usage --
//

void usage(const char* command) {

cout << 
"THEMA     : Search thematic database; identify the composer and origin.\n"
"\n"
"     This command searches a thematic database according to user-defined\n"
"     search keys.  Themes that match the search key are identified\n"
"     by composer, title, instrumentation, opus, movement, etc.\n"
"\n"
"     Search keys (\"incipits\") may be defined in several different ways, in-\n"
"     cluding rough up/down contour (-C), up/down contour with step and leap\n"
"     intervals distinguished (-c), by scale degree (-d), by semitone interval\n"
"     (-i), by interval-class (-I), by pitch (-p), by relative rhythm (-r),\n"
"     and by absolute rhythm (R).  Several search keys can be combined in a\n"
"     single search, and searches can be limited to structural tones only (-s).\n"
"\n"
"Syntax:\n"
"\n"
"     thema [-m|M] [-s] [-C incipit] [-c incipit] [-d incipit] [-i incipit]\n"
"      [-I incipit] [-p incipit] [-r incipit] [-R incipit] [-t tonic]\n"
"\n"
"Options:\n"
"\n"
"\n"
"  -C incipit  : identify according to up/down/unison(same) contour [UDS]\n"
"  -c incipit  : identify according to up/down/unison step/leap contour [UDuds]\n"
"  -d incipit  : identify according to scale degree [1234567]\n"
"  -i incipit  : identify according to semitone intervals [+-1234567890]\n"
"  -m          : limit search to themes in minor keys\n"
"  -M          : limit search to themes in major keys\n"
"  -P incipit  : identify according to pitch-class [#-abcdefg]\n"
"  -p incipit  : identify according to absolute pitch [#-ABCDEFGabcdefg]\n"
"  -r incipit  : identify according to longer/shorter/same rhythm [<>=]\n"
"  -R incipit  : identify according to long/short/medium rhythm [LSM]\n"
"  -s          : limit search to structural tones only\n"
"  -t tonic    : limit search to themes in key of \"tonic\" (major or minor)\n"
"\n"
"  Refer to reference manual for further details." 
<<endl;

}



// md5sum: c3738863aae4c35f19bec9ff165b3249 themax.cpp [20090525]