//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Fri Jul 27 21:54:14 PDT 2012
// Last Modified: Fri Jul 27 21:54:18 PDT 2012
// Filename:      ...sig/examples/all/decom.cpp
// Web Address:   http://sig.sapp.org/examples/museinfo/score/decom.cpp
// Syntax:        C++; museinfo
//
// Description:   Extract staves/systems from a SCORE page.
//

#include "PerlRegularExpression.h"
#include "ScorePage.h"
#include "Options.h"

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


// function declarations
void      checkOptions(Options& opts, int argc, char* argv[]);
void      printSystem(int sysidx, ScorePage& page, int loweststaff);
void      printStaff(int sysidx, int staffidx, ScorePage& page);
void      example(void);
void      usage(const char* command);
int       getLowestStaff(ScorePage& page, Array<int>& list);

// 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       countQ     = 0;    // used with -c option
int       keepstaffQ = 0;    // used with -k option
int       debugQ     = 0;    // used with --debug option
int       infoQ      = 0;    // used with -i option
int       sysstaffQ  = 0;    // used with --ss option
Array<int> list;             // used with -s option

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

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

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

   if (options.getArgCount() == 0) {
      cout << "Usage: " << argv[0] << " input.mus " << endl;
      exit(1);
   }

   ScorePage page;
   page.readFile(options.getArg(1));
   page.analyzeContent();
   page.packBase40Pitch();

   int i, j;
   if (options.getBoolean("system-list")) {
      fillFieldData(list, options.getString("system-list"),
            page.getSystemCount());
      if (debugQ) {
         cout << "# SYSTEM LIST: " << list << endl;
      }
   } else if (options.getBoolean("system-staff-list")) {
      fillFieldData(list, options.getString("system-staff-list"),
            page.getSystemStaffCount(0));
      if (debugQ) {
         cout << "# SYSTEM STAFF LIST: " << list << endl;
      }
   } else {
      list.setSize(0);
   }

   if (infoQ) {
      printInfo(page);
      return 0;
   }

   int loweststaff = getLowestStaff(page, list);
   if (countQ) {
      cout << page.getSystemCount() << endl;
   } else if (sysstaffQ && (list.getSize() > 0)) {
      for (i=0; i<page.getSystemCount(); i++) {
         for (j=0; j<list.getSize(); j++) {
            printStaff(i, list[j]-1, page);
         }
      }
   } else if (list.getSize() > 0) {
      for (i=0; i<list.getSize(); i++) {
         if (debugQ) {
            cerr << "# i=" << i << "\tsyslist[i] = " << list[i] << endl;
         }
         printSystem(list[i]-1, page, loweststaff);
      }
   }

   return 0;
}


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


//////////////////////////////
//
// printInfo ==
//

void printInfo(ScorePage& page) {
   int syscount = page.getSystemCount();
   cout << "SYSTEM COUNT: " << syscount << endl;
   int i, j;
   for (i=0; i<syscount; i++) {
      cout << "   System " << i+1 << " staves:\t";
      for (j=0; j<page.getSystemStaffCount(i); j++) {
         cout << page.getSystemStaffNumber(i, j);
         if (j < page.getSystemStaffCount(i)-1) {
            cout << ", ";
         }
      }
      cout << endl;
   }
}


//////////////////////////////
//
// getLowestStaff -- return the lowest staff if the given systems are extracted.
//

int getLowestStaff(ScorePage& page, Array& list) {
   int i;
   int output = 999;
   int low;
   int sysidx;
   for (i=0; i<list.getSize(); i++) {
      sysidx = list[i] - 1;
      low = page.getLowestStaffOnSystem(sysidx);
      if (low < output) {
         output = low;
      }
   }

   return output;
}



//////////////////////////////
//
// printStaff -- print the specified staff on the specified system.
//    The bottom staff of the system is staff 0, 
//

void printStaff(int sysidx, int staffidx, ScorePage& page) {
   Array<int> list;
   page.getStaffObjectIndexes(sysidx, staffidx, list);
   int i;
   for (i=0; i<list.getSize(); i++) {
      page[list[i]].printAscii(cout);
      cout << "\n";
   }
   for (i=0; i<list.getSize(); i++) {
      page[list[i]].printAscii(cout);
      cout << "\n";
   }
}


//////////////////////////////
//
// printSystem -- warning: destructive function, alters page.
//

void printSystem(int sysidx, ScorePage& page, int loweststaff) {
   Array<int> list;
   page.getSystemObjectIndexes(sysidx, list);
   int i;
   int staff;
   for (i=0; i<list.getSize(); i++) {
      if (!keepstaffQ) {
         staff = page[list[i]].getValue(P2);
         staff = staff - loweststaff + 1;
         page[list[i]].setValue(P2, staff);
      }
      page[list[i]].printAscii(cout);
      cout << "\n";
   }
}


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

void checkOptions(Options& opts, int argc, char* argv[]) {
   opts.define("k|keep|keep-staff-numbers=b", 
                  "do not move the lowest staff to bottom of page");
   opts.define("c|count=b", "count the number of systems on the page");
   opts.define("i|info=b", "display page information");
   opts.define("s|system-list=s", "system numbers to extract");
   opts.define("ss|sysstaff|sys-staff|system-staff-list=s", 
                 "system numbers to extract");

   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, Jul 2012" << endl;
      exit(0);
   } else if (opts.getBoolean("version")) {
      cout << argv[0] << ", version: 27 Jul 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);
   }

   countQ     = opts.getBoolean("count");
   debugQ     = opts.getBoolean("debug");
   infoQ      = opts.getBoolean("info");
   sysstaffQ  = opts.getBoolean("system-staff-list");
   keepstaffQ = opts.getBoolean("keep");
   
}



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

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



//////////////////////////////
//
// usage -- gives the usage statement for the 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: 1bbc9a1e1f2e88763d17b4ea979a8861 decom.cpp [20120811]