//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Sun Aug  5 12:58:51 PDT 2012
// Last Modified: Sun Aug  5 12:58:55 PDT 2012
// Filename:      ...sig/examples/all/sverse.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/score/sverse.cpp
// Syntax:        C++; museinfo
//
// Description:   Display staff information for a SCORE page file.
//

#include "PerlRegularExpression.h"
#include "ScorePageSet.h"
#include "Options.h"

#include <string.h>
#include <stdio.h>
#include <math.h>

// function declarations:
void      checkOptions(Options& opts, int argc, char* argv[]);
void      example(void);
void      usage(const char* command);
void      printLyrics(ScorePageSet& work, Array<int>& staves);
void      printLyricsByLineBreak(ScorePageSet& work, Array<int>& staves);
void      printLyricsInfo(ScorePageSet& work, Array<int>& staves);
void      printVerseWithBreaks(ScorePageSet& work, int staffidx, 
                                 int verseidx);
void      printVerse(ScorePageSet& work, int staffidx, 
                                 int verseidx);
int       getPrettyScoreText(Array<char>& textdata, ScoreRecord& arecord);


// block parsing functions:
void      fillFieldData(Array<int>& field, const char* fieldstring, 
                              int maxval);
void      processFieldEntry(Array<int>& field, const char* string, 
                              int maxval);
void      removeDollarsFromString(Array<char>& buffer, int maxval);
void      printInfo(ScorePage& page);

// interface variables:
Options options;
int     verboseQ    = 0;       // used with -v option
int     debugQ      = 0;       // used with --debug option
int     breakQ      = 0;       // used with -b option
int     infoQ       = 0;       // used with -i option
int     staffQ      = 0;       // used with -s option
int     plainQ      = 0;       // used with -p option
int     htmlQ       = 0;       // used with -h option

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

int main(int argc, char** argv) {

   checkOptions(options, argc, argv);

   int i;
   ScorePageSet work;
   for (i=1; i<=options.getArgCount(); i++) {
      work.appendRead(options.getArg(i), verboseQ);
   }
   work.analyzeContent();

   // determine the extraction staves
   Array<int> versestaves;
   if (staffQ) {
      fillFieldData(versestaves, options.getString("staff-list"),
            work.getPartCount());
      for (i=0; i<versestaves.getSize(); i++) {
         versestaves[i]--;
      }
      if (debugQ) {
         cout << "# SYSTEM LIST: " << versestaves << endl;
      }
   } else {
      versestaves.setSize(work.getPartCount());
      versestaves.setAll(0, 1);
   }

   if (infoQ) {
      printLyricsInfo(work, versestaves);
   } else if (breakQ) {
      printLyricsByLineBreak(work, versestaves);
   } else {
      printLyrics(work, versestaves);
   }

   return 0;
}



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


//////////////////////////////
//
// printLyricsInfo --
//

void printLyricsInfo(ScorePageSet& work, Array& versestaves) {

   int count = 0;
   int pcount = work.getPartCount();
   int i;
   for (i=0; i<pcount; i++) {
      if (work.getVerseCount(i)) {
         count++;
      }
   }
   
   if (count == 0) {
      cout << "Work has no lyrics." << endl;
      return;
   }

   cout << count << " system ";
   if (count == 1) {
      cout << "staff";
   } else {
      cout << "staves";
   }
   cout << " has lyrics (from top to bottom):" << endl;
   for (i=pcount-1; i>=0; i--) {
      if (work.getVerseCount(i)) {
         cout << "\t-- Staff " << pcount-i << " has " << work.getVerseCount(i);
         if (work.getVerseCount(i) > 1) {
            cout << " verses.";
         } else {
            cout << " verse.";
         }
         cout << endl;
      }
   }

}


//////////////////////////////
//
// printLyricsByLineBreak --
//

void printLyricsByLineBreak(ScorePageSet& work, Array& versestaves) {
   int count = 0;
   int pcount = work.getPartCount();
   int i;
   for (i=0; i<pcount; i++) {
      if (work.getVerseCount(i)) {
         count++;
      }
   }
   
   if (count == 0) {
      cout << "Work has no lyrics." << endl;
      return;
   }

   int ii;
   int j;
   for (i=0; i<versestaves.getSize(); i++) {
      ii = pcount - versestaves[i] - 1;
      if (work.getVerseCount(ii)) {
         cout << "@STAFF\t" << versestaves[i]+1 << " has " 
              << work.getVerseCount(ii);
         if (work.getVerseCount(ii) > 1) {
            cout << " verses:";
         } else {
            cout << " verse:";
         }
         cout << endl;
         for (j=0; j<work.getVerseCount(ii); j++) {
            printVerseWithBreaks(work, ii, j);
         }
      }
   }
}


/////////////////////////////
//
// printLyrics -- print on continuous line for each verse.
//

void printLyrics(ScorePageSet& work, Array& versestaves) {
   int count = 0;
   int pcount = work.getPartCount();
   int i;
   for (i=0; i<pcount; i++) {
      if (work.getVerseCount(i)) {
         count++;
      }
   }
   
   if (count == 0) {
      cout << "Work has no lyrics." << endl;
      return;
   }

   int ii;
   int j;
   for (i=0; i<versestaves.getSize(); i++) {
     ii = pcount - versestaves[i] - 1;
     if (work.getVerseCount(ii)) {
         cout << "@STAFF\t" << versestaves[i]+1 << " has " 
              << work.getVerseCount(ii);
         if (work.getVerseCount(ii) > 1) {
            cout << " verses:";
         } else {
            cout << " verse:";
         }
         cout << endl;
         for (j=0; j<work.getVerseCount(ii); j++) {
            printVerse(work, ii, j);
            cout << endl;
         }
      }
   }
}



//////////////////////////////
//
// printVerseWithBreaks --
//

void printVerseWithBreaks(ScorePageSet& work, int staffidx, int verseidx) {
   int i, j, k;
   int tvindex;
   ScoreRecord* srecord;
   Array<char> textdata;
   int endhyphen = 0;

   cout << "\n@Verse: " << verseidx+1 << endl;
   if (verseidx > 0) {
      cout << endl;
   }

   int tcount;
   int charcount = 0;
   Array<char> syllable(100);
   syllable.setSize(0);
   int blankchecking = 0;
   for (i=0; i<work.getPageCount(); i++) {
      for (j=0; j<work.getPage(i).getSystemCount(); j++) {
         tcount = 0;
         blankchecking = 0;
         for (k=0; k<work.getPage(i).getSystemStaffItemCount(j, staffidx); k++){
            srecord = &(work.getPage(i).getSystemStaffItem(j, staffidx, k));
            if (!srecord->isTextItem()) {
               continue;
            }
            tvindex = srecord->getVerseIndex();
            if (tvindex != verseidx) {
               continue;
            }
            charcount = getPrettyScoreText(syllable, *srecord);
            if (charcount == 0) {
               continue;
            }
            if (tcount++ && srecord->isWordStart()) {
               cout << " ";
            }
            cout << syllable;

            if (charcount > 0) {
               blankchecking++;
            }

            endhyphen = !(srecord->isWordEnd());
         }
         if (endhyphen) {
            cout << "-";
         }

         if (blankchecking == 0) {
            cout << "[blank]";
         }
         cout << endl;
      }
      cout << endl;
   }

}



//////////////////////////////
//
// getPrettyScoreText -- returns the number of charcters printed
//

int getPrettyScoreText(Array& textdata, ScoreRecord& arecord) {
   arecord.getTextDataWithoutFonts(textdata);
   if (plainQ) {
      ScoreRecord::convertScoreTextToPlainText(textdata);
   } else if (htmlQ) {
      ScoreRecord::convertScoreTextToHtmlText(textdata);
   } 
   return strlen(textdata.getBase());
}



//////////////////////////////
//
// printVerse --
//

void printVerse(ScorePageSet& work, int staffidx, int verseidx) {
   int i, j, k;
   int tvindex;
   ScoreRecord* srecord;
   Array<char> textdata;
   int endhyphen = 0;

   cout << "\n@Verse: " << verseidx+1 << endl << endl;

   int linebreak = 0;
   Array<char> syllable(100);
   syllable.setSize(0);
   int tcount;
   tcount = 0;
   int lastsys = -1;
   int system = -1;
   for (i=0; i<work.getPageCount(); i++) {
      for (j=0; j<work.getPage(i).getSystemCount(); j++) {
         system++;
         for (k=0; k<work.getPage(i).getSystemStaffItemCount(j, staffidx); 
                  k++) {
            srecord = &(work.getPage(i).getSystemStaffItem(j, staffidx, k));
            if (!srecord->isTextItem()) {
               continue;
            }
            tvindex = srecord->getVerseIndex();
            if (tvindex != verseidx) {
               continue;
            }
            if (!linebreak && tcount++ && srecord->isWordStart()) {
               cout << " ";
            }
            if (lastsys < system-1) {
               cout << "[blank] ";
            }
            // cout << srecord->getTextDataWithoutFonts(textdata);
            getPrettyScoreText(syllable, *srecord);
            lastsys = system;
            cout << syllable;
            linebreak = 0;
            endhyphen = !(srecord->isWordEnd());
         }
      }
      cout << endl;
      linebreak = 1;
   }
}



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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("i|info=b", "display lyric informatio");
   opts.define("v|verbose=b", "verbose display of information");
   opts.define("b|break|line-breaks=b", 
                  "break lyric lines by staff line breaks");
   opts.define("s|staff|staff-list=i", 
                  "give a list system staves from which to extract");
   opts.define("p|plain=b", "convert verse text to plain ASCII text");
   opts.define("html=b", "convert verse text to HTML text");

   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 2012" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 5 Aug 2012" << endl;
      cout << "compiled: " << __DATE__ << endl;
      exit(0);
   } else if (opts.getBoolean("help")) {
      usage(opts.getCommand());
      exit(0);
   } else if (opts.getBoolean("example")) {
      example();
      exit(0);
   }

   staffQ     =  opts.getBoolean("staff-list");
   debugQ     =  opts.getBoolean("debug");
   breakQ     =  opts.getBoolean("break");
   infoQ      =  opts.getBoolean("info");
   plainQ     =  opts.getBoolean("plain");
   htmlQ      =  opts.getBoolean("html");


   if (plainQ && htmlQ) {
      cerr << "ERROR: plain and html text conversions not allowed at the same time." 
           << endl;
      exit(1);
   }
}



//////////////////////////////
//
// 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;
}


///////////////////////////////////////////////////////////////////////////
//
// Spine field list extraction functions
//

//////////////////////////////
//
// fillFieldData --
//

void fillFieldData(Array& field, const char* fieldstring, int maxval) {

   field.setSize(maxval);
   field.setGrowth(0);
   field.setAll(0);

   Array<int> tempfield;
   tempfield.setSize(maxval);
   tempfield.setSize(0);

   PerlRegularExpression pre;
   Array<char> buffer;
   buffer.setSize(strlen(fieldstring)+1);
   strcpy(buffer.getBase(), fieldstring);
   pre.sar(buffer, "\\s", "", "gs");
   int start = 0;
   int value = 0;
   value = pre.search(buffer.getBase(), "^([^,]+,?)");
   while (value != 0) {
      start += value - 1;
      start += strlen(pre.getSubmatch(1));
      processFieldEntry(tempfield, pre.getSubmatch(), maxval);
      value = pre.search(buffer.getBase() + start, "^([^,]+,?)");
   }

   if (debugQ) {
      cout << "# tempfield: " << tempfield << endl;
   }

   field = tempfield;

   //int i;
   //for (i=0; i<tempfield.getSize(); i++) {
   //  field[tempfield[i]-1] = 1; 
   //}
}


//////////////////////////////
//
// processFieldEntry -- 
//   3-6 expands to 3 4 5 6
//   $   expands to maximum block number
//   $-1 expands to maximum spine track minus 1, etc.
//

void processFieldEntry(Array& field, const char* string, int maxval) {

   PerlRegularExpression pre;
   Array<char> buffer;
   buffer.setSize(strlen(string)+1);
   strcpy(buffer.getBase(), string);

   // remove any comma left at end of input string (or anywhere else)
   pre.sar(buffer, ",", " ", "g");
   pre.sar(buffer, "\\s+$", "");
   pre.sar(buffer, "^\\s+", "");

   if (debugQ) {
      cout << "MAXBLOCK = " << maxval << endl;
      cout << "INPUT BLOCK STRING TO    DOLLAR: " << buffer << endl;
   }
   // first remove $ symbols and replace with the correct values
   removeDollarsFromString(buffer, maxval);
   if (debugQ) {
      cout << "OUTPUT BLOCK STRING FROM DOLLAR: " << buffer << endl;
   }

   if (pre.search(buffer.getBase(), "^(\\d+)-(\\d+)$")) {
      int firstone = strtol(pre.getSubmatch(1), NULL, 10);
      int lastone  = strtol(pre.getSubmatch(2), NULL, 10);

      if ((firstone < 1) && (firstone != 0)) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains too small a number at start: " << firstone << endl;
         cerr << "Minimum number allowed is " << 1 << endl;
         exit(1);
      }
      if ((lastone < 1) && (lastone != 0)) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains too small a number at end: " << lastone << endl;
         cerr << "Minimum number allowed is " << 1 << endl;
         exit(1);
      }
      if (firstone > maxval) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains number too large at start: " << firstone << endl;
         cerr << "Maximum number allowed is " << maxval << endl;
         exit(1);
      }
      if (lastone > maxval) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains number too large at end: " << lastone << endl;
         cerr << "Maximum number allowed is " << maxval << endl;
         exit(1);
      }

      int i;
      if (firstone > lastone) {
         for (i=firstone; i>=lastone; i--) {
            field.append(i);
         }
      } else {
         for (i=firstone; i<=lastone; i++) {
            field.append(i);
         }
      }
   } else if (pre.search(buffer.getBase(), "^(\\d+)")) {
      int value = strtol(pre.getSubmatch(1), NULL, 10);
      if ((value < 1) && (value != 0)) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains too small a number at end: " << value << endl;
         cerr << "Minimum number allowed is " << 1 << endl;
         exit(1);
      }
      if (value > maxval) {
         cerr << "Error: range token: \"" << string << "\"" 
              << " contains number too large at start: " << value << endl;
         cerr << "Maximum number allowed is " << maxval << endl;
         exit(1);
      }
      field.append(value);
   }
}



//////////////////////////////
//
// removeDollarsFromString -- substitute $ sign for maximum track count.
//

void removeDollarsFromString(Array& buffer, int maxval) {
   PerlRegularExpression pre;
   char buf2[128] = {0};
   int value2;
   if (debugQ) {
      cout << "IN DOLLAR STRING MAXBLOCK = " << maxval << endl; 
   }

   if (pre.search(buffer.getBase(), "\\$$")) {
      sprintf(buf2, "%d", maxval);
      pre.sar(buffer, "\\$$", buf2);
   }

   if (debugQ) {
      cout << "IN DOLLAR STRING = " << buffer << endl; 
   }

   if (pre.search(buffer.getBase(), "\\$(?![\\d-])")) {
      // don't know how this case could happen, however...
      sprintf(buf2, "%d", maxval);
      pre.sar(buffer, "\\$(?![\\d-])", buf2, "g");
   }

   if (pre.search(buffer.getBase(), "\\$0")) {
      // replace $0 with maxval (used for reverse orderings)
      sprintf(buf2, "%d", maxval);
      pre.sar(buffer, "\\$0", buf2, "g");
   }

   while (pre.search(buffer.getBase(), "\\$(-?\\d+)")) {
      value2 = maxval - (int)fabs(strtol(pre.getSubmatch(1), NULL, 10));
      sprintf(buf2, "%d", value2);
      pre.sar(buffer, "\\$-?\\d+", buf2);
   }

}


//
// Spine field list extraction functions
//
///////////////////////////////////////////////////////////////////////////




// md5sum: b3ffb1c66363946fb3bd519900302055 sverse.cpp [20120811]