//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sat May  1 16:20:50 PDT 2004
// Last Modified: Sat May  1 16:20:52 PDT 2004
// Last Modified: Wed May 27 01:00:49 PDT 2009 (fixed out filename in cwd)
// Filename:      ...museinfo/examples/all/thememakerx.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/thememakerx.cpp
// Syntax:        C++; museinfo
//
// Description:   Extract incipits from monophonic Humdrum files which contain
//                phrase markings (preferrably).
//

#include "humdrum.h"

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

// includes needed for file/directory processing:
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>


// function declarations:
void      checkOptions(Options& opts, int argc, char** argv);
void      example(void);
void      usage(const char* command);
void      createIncipit(HumdrumFile& hfile, const char* filename,
                                  const char* sourcebase, int limit);
void      extractPitchSequence(Array<int>& pitches, Array<int>& phrase, 
                                  HumdrumFile& hfile);
int       is_directory           (const char* path);
int       is_file                (const char* path);
void      processArgument(const char* path);
void      createOutputName(char* outfile, const char* filename, 
                                  const char* target, const char* sourcebase);
void      checkTargetDirectory(const char* outfile);
void      getPhrasesAndNoteLines(Array<int>& lines, Array<int>& phrase, 
                                  HumdrumFile& hfile);
int       getEndingLine(Array<int>& lines, Array<int>& phrase, 
                                  HumdrumFile& hfile, int limit);

// User interface variables:
Options   options;
int       debugQ  = 0;         // used with --debug option
int       limitQ  = 0;         // used with -l option
int       limit   = 30;        // used with -l option
int       minval  = 10;        // used with -m option
const char* target = ".";        // used with -b option
char sourcebase[2048] = {0};


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

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

   int i;
   int numinputs = options.getArgCount();
   HumdrumFile hfile;
   strcpy(sourcebase, ".");

   for (i=0; i<numinputs || i==0; i++) {
      hfile.clear();

      // if no command-line arguments read data file from standard input
      if (numinputs < 1) {
         hfile.read(cin);
         createIncipit(hfile, "", sourcebase, limit);
      } else {
         if (is_directory(options.getArg(i+1))) {
            strcpy(sourcebase, options.getArg(i+1));
         }
         processArgument(options.getArg(i+1));
      }
   }

   return 0;
}


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


//////////////////////////////
//
// processArgument -- check if the argument is a file or a directory.
//    If a directory, then process all files/subdirectories in it.
//

void processArgument(const char* path) {
   HumdrumFile hfile;
   DIR* dir = NULL;
   char* fullname;
   struct dirent* entry;
   int namelen = 0;
   int valid = 0;

   if (is_file(path)) {
      namelen = strlen(path);
      valid = 0;
      if (strcmp(&(path[namelen-4]), ".krn") == 0) {
         valid = 1;
      } else if (strcmp(&(path[namelen-4]), ".KRN") == 0) {
         valid = 1;
      }
      if (!valid) {
         return;
      }
      hfile.read(path);
      createIncipit(hfile, path, sourcebase, limit);
   } else if (is_directory(path)) {
      dir = opendir(path);
      if (dir == NULL) {
         return;
      }
      entry = readdir(dir);
      while (entry != NULL) {
         if (strncmp(entry->d_name, ".", 1) == 0) {
            entry = readdir(dir);
            continue;
         }

         fullname = new char[strlen(path) + 1 + strlen(entry->d_name) + 1];
         strcpy(fullname, path);
         strcat(fullname, "/");
         strcat(fullname, entry->d_name);
         processArgument(fullname);
         entry = readdir(dir);
      }
   }

   closedir(dir);
}



//////////////////////////////
//
// is_file -- returns true if the string is a file.
//

int is_file(const char* path) {
   struct stat filestat;
   if (stat(path, &filestat)) {
      return 0;
   }
   return S_ISREG(filestat.st_mode);
}



//////////////////////////////
//
// is_directory -- returns true if the string is a directory.
//

int is_directory(const char* path) {
   struct stat filestat;
   if (stat(path, &filestat)) {
      return 0;
   } 
   return S_ISDIR(filestat.st_mode);
}



//////////////////////////////
//
// createIncipit --
//

void createIncipit(HumdrumFile& hfile, const char* filename, 
      const char* sourcebase, int limit) {
   char outfile[4096] = {0};
   createOutputName(outfile, filename, target, sourcebase);
   cout << "creating incipit for: " << filename << "\toutput: " << outfile << endl;

   fstream output;

   output.open(outfile, ios::out);
   if (!output.is_open()) {
      cout << "Error opening file to write: " << outfile << endl;
      exit(1);
   }

   Array<int> lines;
   Array<int> phrase;
   getPhrasesAndNoteLines(lines, phrase, hfile);
   int endline = getEndingLine(lines, phrase, hfile, limit);

   int i;
   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_global_comment:
            if (i < endline) {
               output << hfile[i] << "\n";
            }
            break;
         case E_humrec_bibliography:
            output << hfile[i] << "\n";
            break;
         case E_humrec_data_comment:
            if (i < endline) {
               output << hfile[i][0] << "\n";
            }
            break;
         case E_humrec_data_kern_measure:
            if (i < endline+1) {
               output << hfile[i][0] << "\n";
            }
            break;
         case E_humrec_interpretation:
            if (i < endline) {
               if ((strcmp(hfile[i][0], "*v") != 0) &&
                   (strcmp(hfile[i][0], "*^") != 0) &&
                   (strcmp(hfile[i][0], "*x") != 0)) {
                  output << hfile[i][0] << "\n";
               }
            }
            break;
         case E_humrec_data:
            if (i < endline) {
               output << hfile[i][0] << "\n";
            }
            break;
         case E_humrec_empty:
         case E_humrec_none:
         default:
            break;
      }
      if (i == endline) {
         output << "*-\n";
      }
   }

   output.close();
}



//////////////////////////////
//
// getEndingLine --
//

int getEndingLine(Array<int>& lines, Array<int>& phrase, HumdrumFile& hfile,
      int limit) {
   int i;
   int start = lines.getSize()-1;
   if (start > limit) {
     start = limit;
   }
   int phraseboundary = start;
   if (phrase[phraseboundary] <= 1) {
      if (phraseboundary < minval) {
         phraseboundary = limit;
      }
      if (phraseboundary >= lines.getSize()) {
         phraseboundary = lines.getSize()-1;
      }
      return lines[phraseboundary];
   }

   for (i=start; i>=0; i--) {
      if (i>0 && (phrase[i] != phrase[i-1])) {
         phraseboundary = i; 
         break;
      }
   }

   if (phraseboundary < minval) {
      phraseboundary = limit;
   }
   if (phraseboundary >= lines.getSize()) {
      phraseboundary = lines.getSize()-1;
   }

   return lines[phraseboundary];
}



//////////////////////////////
//
// getPhrasesAndNoteLines --
//

void getPhrasesAndNoteLines(Array<int>& lines, Array<int>& phrase, 
      HumdrumFile& hfile) {

   lines.setSize(hfile.getNumLines());
   lines.setSize(0);
   phrase.setSize(hfile.getNumLines());
   phrase.setSize(0);

   int pcount = 0;

   int i;
   for (i=0; i<hfile.getNumLines(); i++) {
      if (hfile[i].getType() != E_humrec_data) {
         continue;
      }
      if (strcmp(hfile[i][0], ".") == 0) {
         // ignore null tokens
         continue;
      }
      if (strchr(hfile[i][0], '{') != NULL) {
         pcount++;
      }

      if (strchr(hfile[i][0], 'r') != NULL) {
         // ignore rests
         continue;
      }

      if (strchr(hfile[i][0], ']') != NULL) {
         // ignore tie endings
         continue;
      }

      if (strchr(hfile[i][0], '_') != NULL) {
         // ignore tie continuations
         continue;
      }

      lines.append(i);
      phrase.append(pcount);

   }

}



//////////////////////////////
//
// createOutputName --
//

void createOutputName(char* outfile, const char* filename, 
      const char* target, const char* sourcebase) {
   strcpy(outfile, target);
   strcat(outfile, "/");
   char buffer[1024] = {0};
   strcpy(buffer, filename);
   int len2 = strlen(buffer);
   char* dot = strrchr(buffer, '.');
   if (len2 - (int)(dot - buffer) == 4) {
      strcpy(dot, ".thm");
   } else {
      strcat(buffer, ".thm");
   }
   char* ptr;
   ptr = strrchr(buffer, '/');
   if (ptr) {
      strcat(outfile, ptr+1);
   } else {
      strcat(outfile, buffer);
   }
   checkTargetDirectory(outfile);
}



//////////////////////////////
//
// checkTargetDirectory -- create the target directory if it does not
//    exist.
//

void checkTargetDirectory(const char* outfile) {
   char buffer[4096] = {0};
   strncpy(buffer, outfile, 4000);
   char* end = strrchr(buffer, '/');
   if (end == NULL) {
      return;
   }
   end[0] = '\0';
   if (is_directory(buffer)) {
      return;
   }

   char fulldir[4096] = {0};
   if (buffer[0] == '/') {
      strcpy(fulldir, "/");
   }

   char* ptr = strtok(buffer, "/");
   while (ptr != NULL) {
      strcat(fulldir, ptr);
      if (!is_directory(fulldir)) {
         cout << "Creating the directory: " << fulldir << endl;
         if (mkdir(fulldir, 0755)) {
            cout << "ERROR creating the directory: "<< fulldir << endl;
            exit(1);
         }
      }
      strcat(fulldir, "/");      
      ptr = strtok(NULL, "/");
   }
}



//////////////////////////////
//
// extractPitchSequence --
// restrictions:
//   (1) **kern data must be first column in file.
//   (2) chords will be ignored, only first note in chord will be processed.
//

void extractPitchSequence(Array<int>& pitches, HumdrumFile& hfile) {
   pitches.setSize(10000);
   pitches.setGrowth(10000);
   pitches.setSize(0);
   pitches.allowGrowth();
   int pitch = 0;
   int i;

   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_bibliography:
         case E_humrec_global_comment:
         case E_humrec_data_comment:
         case E_humrec_interpretation:
         case E_humrec_data_kern_measure:
            break;
         case E_humrec_data:
            if (strcmp(hfile[i][0], ".") == 0) {
               // ignore null tokens
               break;
            }
            if (strchr(hfile[i][0], '_') != NULL) {
               // ignore continuing ties
               break;
            }
            if (strchr(hfile[i][0], ']') != NULL) {
               // ignore ending ties
               break;
            }
            if (strchr(hfile[i][0], 'r') != NULL) {
               // ignore rests
               break;
            }
            pitch = Convert::kernToBase40(hfile[i][0]);
            if ((pitch < 0) || (pitch > 10000)) {
               // ignore rests and other strange things
               break;
            }
            pitches.append(pitch); 
            if (limitQ) {
               if (pitches.getSize() >= limit) {
                  return;
               }
            }
            break;
         default:
            break;
      }
   }

}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("debug=b",        "print debug information"); 
   opts.define("l|limit=i:30",   "upper limit the number of extracted notes");
   opts.define("m|min=i:10",     "lower limit on number of extracted notes");
   opts.define("t|target=s:.",   "filename target base for recursive output");

   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, May 2004" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 1 May 2004" << 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");
   limitQ      = opts.getBoolean("limit");
   limit       = opts.getInteger("limit");
   minval      = opts.getInteger("min");
   target      = opts.getString("target");

   if (!is_directory(target)) {
      cout << "Error: target directory does not exist: " << target << endl;
      exit(1);
   }

}



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

void example(void) {


}



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

void usage(const char* command) {

}



// md5sum: 607dbdf6817a432762d577e6e551c1c6 thememakerx.cpp [20090527]