//
// Programmer:    Craig Stuart Sapp <craig@ccrma.stanford.edu>
// Creation Date: Thu Nov 22 10:57:44 PST 2001
// Last Modified: Mon Nov 26 18:34:55 PST 2001
// Filename:      ...museinfo/examples/all/notelink.cpp 
// Web Address:   http://sig.sapp.org/examples/museinfo/humdrum/notelink.cpp
// Syntax:        C++; museinfo
//
// Description:   Creates a linked list of humdrum data.
//
//

#include "humdrum.h"

#include <string.h>

class Node {
   public:
      Node(void) {
         lastline.setSize(0);
         lastspine.setSize(0);
         nextline.setSize(0);
         nextspine.setSize(0);
      }
      void add(Node& newnode);
      Array<int> lastline;
      Array<int> lastspine;
      Array<int> nextline;
      Array<int> nextspine;
};


void Node::add(Node& newnode) {
   int i;
   for (i=0; i<newnode.lastline.getSize(); i++) {
      lastline.append(newnode.lastline[i]);
      lastspine.append(newnode.lastspine[i]);
   }
   for (i=0; i<newnode.nextline.getSize(); i++) {
      nextline.append(newnode.nextline[i]);
      nextspine.append(newnode.nextspine[i]);
   }
}


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

typedef Array<Node>  ANode;
typedef Array<ANode> AANode;

void processHumdrumFile(HumdrumFile& hfile, AANode& nodes);
void printAnalysis(HumdrumFile& hfile, AANode& nodes);
int  hasSpineManip(HumdrumRecord& aRecord);
void insertNullNode(int location, ANode& current);
void processTransition(HumdrumRecord& aRecord, ANode& current, int& init, 
                        int& tinit);
void processData(HumdrumRecord& aRecord, ANode& current, int line);
void deleteNode(int index, ANode& current);
int  findLineBack(int currentline, int target);


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

int main(int argc, char** argv) {
   if (argc != 2) {
      cout << "Usage: " << argv[0] << " input-kern-file" << endl;
      exit(1);
   }

   AANode nodes;
   HumdrumFile hfile(argv[1]);

   processHumdrumFile(hfile, nodes);
   printAnalysis(hfile, nodes);

   return 0;
}

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


//////////////////////////////
//
// printAnalysis --
//

void printAnalysis(HumdrumFile& hfile, AANode& nodes) {
   int i, j, k;
   int hline;


   cout << "!! LASTLINE ANALYSIS ---------------" << endl;

   for (i=0; i<nodes.getSize(); i++) {
      hline = i;
      cout << hline << " ::\t";
      for (j=0; j<nodes[i].getSize(); j++) {
         for (k=0; k<nodes[i][j].lastline.getSize(); k++) {
            cout << nodes[i][j].lastline[k] << ":";
            cout << nodes[i][j].lastspine[k];
            if (k<nodes[i][j].lastline.getSize()-1) {
               cout << "+";
            }
         }
         cout << "\t";
      }
      cout << hfile[hline] << endl;
   }


   cout << "!! NEXTLINE ANALYSIS ---------------" << endl;

   for (i=0; i<nodes.getSize(); i++) {
      hline = i;
      cout << hline << " ::\t";
      for (j=0; j<nodes[i].getSize(); j++) {
         for (k=0; k<nodes[i][j].nextline.getSize(); k++) {
            cout << nodes[i][j].nextline[k] << ":";
            cout << nodes[i][j].nextspine[k];
            if (k<nodes[i][j].nextline.getSize()-1) {
               cout << "+";
            }
         }
         cout << "\t";
      }
      cout << hfile[hline] << endl;
   }

}




//////////////////////////////
//
// processHumdrumFile
//

void processHumdrumFile(HumdrumFile& hfile, AANode& nodes) {
   nodes.setSize(0);
   ANode current;
   ANode nullnode(0);
   current.setSize(0);
   nodes.setSize(hfile.getNumLines());
   nodes.setSize(0);
   int i, j, k;
   int m;
   int init = 0;
   int tinit = 0;

   // first: assign last token values
   for (i=0; i<hfile.getNumLines(); i++) {
      switch (hfile[i].getType()) {
         case E_humrec_none:
         case E_humrec_empty:
         case E_humrec_global_comment:
         case E_humrec_bibliography:
            nodes.append(nullnode);
            break;
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
            // treating measure as an entire line
            nodes.append(current);
            break;
         case E_humrec_interpretation:
            nodes.append(current);
            if (hasSpineManip(hfile[i])) {
               processTransition(hfile[i], current, init, tinit);
            }
            break;
         case E_humrec_data:
            nodes.append(current);
            processData(hfile[i], current, i);
            break;
         default:
            break;
      }
   }


   // now go back and assign next token values by going backwards in analysis
   // skipping over null tokens and non-data items
   int nline, nspine;
   int pline, pspine;
   for (i=nodes.getSize()-1; i>=0; i--) {
      if (hfile[i].getType() != E_humrec_data) {
         continue;
      }
      for (j=0; j<nodes[i].getSize(); j++) {
         if (strcmp(hfile[i][j], ".") == 0) {
            continue;
         }
         for (k=0; k<nodes[i][j].lastline.getSize(); k++) {
            nline = i;
            nspine = j;
            pline = nodes[i][j].lastline[k];
            pspine = nodes[i][j].lastspine[k];
            nodes[pline][pspine].nextline.append(nline);
            nodes[pline][pspine].nextspine.append(nspine);
         }
      }
   }

   // now add the next spine information to null tokens, where the
   // next token information is found by going back to the last
   // non-null token and copying its next token information.
   for (i=0; i<nodes.getSize(); i++) {
      for (j=0; j<nodes[i].getSize(); j++) {
         if (strcmp(hfile[i][j], ".") != 0) {
            continue;
         }
         for (k=0; k<nodes[i][j].lastline.getSize(); k++) {
            pline = nodes[i][j].lastline[k];
            pspine = nodes[i][j].lastspine[k];
            for (m=0; m<nodes[pline][pspine].nextline.getSize(); m++) {
               nline = nodes[pline][pspine].nextline[m];
               nspine = nodes[pline][pspine].nextspine[m];
               nodes[i][j].nextline.append(nline);
               nodes[i][j].nextspine.append(nspine);
            }
         }
      }
   }

   // add to measures, data comments and interpretations
   // as done with null tokens
   for (i=0; i<nodes.getSize(); i++) {
      for (j=0; j<nodes[i].getSize(); j++) {
         switch (hfile[i].getType()) {
         case E_humrec_interpretation:
         case E_humrec_data_comment:
         case E_humrec_data_kern_measure:
         for (k=0; k<nodes[i][j].lastline.getSize(); k++) {
            pline = nodes[i][j].lastline[k];
            pspine = nodes[i][j].lastspine[k];
            for (m=0; m<nodes[pline][pspine].nextline.getSize(); m++) {
               nline = nodes[pline][pspine].nextline[m];
               nspine = nodes[pline][pspine].nextspine[m];
               nodes[i][j].nextline.append(nline);
               nodes[i][j].nextspine.append(nspine);
            }
         }
         }
      }
   }


}



//////////////////////////////
//
// findLineBack -- find the previous analysis line based on the previous
//     data line.
//

int findLineBack(int currentline, int target, Array& lines) {
   int i = currentline - 1;
   while (i>0 && lines[i] != target) {
      i--;
   }
   return i;
}



//////////////////////////////
//
// processData --
//

void processData(HumdrumRecord& aRecord, ANode& current, int line) {
   if (current.getSize() != aRecord.getFieldCount()) {
      cout << "ERROR in processData" << endl;
      exit(1);
   }

   int i;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (strcmp(aRecord[i], ".") != 0) {
         current[i].lastline.setSize(1);
         current[i].lastline[0] = line;
         current[i].lastspine.setSize(1);
         current[i].lastspine[0] = i;
      }
   } 
}



//////////////////////////////
//
// processTransition --
//

void processTransition(HumdrumRecord& aRecord, ANode& current, int& init, 
      int& tinit) {
   int i, j;
   int correction = 0;
   int tempcorrection = 0;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (strncmp(aRecord[i], "**", 2) == 0) {
         if (init == 0) {
            insertNullNode(i+correction, current);
            tinit = 1;
         } else {
            // do nothing: the *+ operator has already added a new spine
         }
      } else if (strcmp(aRecord[i], "*-") == 0) {
         deleteNode(i+correction, current);
      } else if (strcmp(aRecord[i], "*x") == 0) {
         if (i < aRecord.getFieldCount() - 1) {
            Node temp;
            temp = current[i+correction];
            current[i+correction] = current[i+1+correction];
            current[i+1+correction] = temp;
            i++;
         }
      } else if (strcmp(aRecord[i], "*^") == 0) {
         insertNullNode(i+correction+1, current);
         current[i+correction+1] = current[i+correction];
         correction++;
      } else if (strcmp(aRecord[i], "*v") == 0) {
         tempcorrection = 0;
         for (j=i+1; j<aRecord.getFieldCount(); j++) {
            if (strcmp(aRecord[j], "*v") != 0) {
               break;
            }
            current[i+correction].add(current[i+1+correction]);
            deleteNode(i+correction+1, current);
            tempcorrection++;
         }
         correction += tempcorrection;
         i += tempcorrection;
      } else if (strcmp(aRecord[i], "*+") == 0) {
         insertNullNode(i+correction+1, current);
         correction++;
      }
   }
   if (tinit > 0) {
      init = 1;
   }
}



//////////////////////////////
//
// deleteNode --
//

void deleteNode(int index, ANode& current) {
   if (index == current.getSize() - 1) {
      current.setSize(current.getSize() - 1 );
      return;
   }

   int i;
   for (i=index; i<current.getSize()-1; i++) {
      current[i] = current[i+1];
   }
   current.setSize(current.getSize() - 1);
}



//////////////////////////////
//
// insertNullNode --
//

void insertNullNode(int location, ANode& current) {
   Node node;
   if (current.getSize() == 0) {
      current.append(node);
      return;
   }
   if (current.getSize() == location) {
      current.append(node);
      return;
   }
   if (current.getSize() > location) {
      int i;
      current.append(current[current.getSize()-1]);
      for (i=current.getSize()-2; i>location; i--) {
          current[i] = current[i-1];
      }
      current[location] = node;
      return;
   }

   cout << "Error in insert NULL" << endl; 
   cout << "Location = " << location << endl;
   cout << "Size     = " << current.getSize() << endl;
   exit(1);
}



//////////////////////////////
//
// hasSpineManip --
//

int hasSpineManip(HumdrumRecord& aRecord) {
   int i;
   for (i=0; i<aRecord.getFieldCount(); i++) {
      if (strncmp(aRecord[i], "**", 2) == 0) {
         return 1;
      } else if (strcmp(aRecord[i], "*^") == 0) {
         return 1;
      } else if (strcmp(aRecord[i], "*v") == 0) {
         return 1;
      } else if (strcmp(aRecord[i], "*x") == 0) {
         return 1;
      } else if (strcmp(aRecord[i], "*-") == 0) {
         return 1;
      } else if (strcmp(aRecord[i], "*+") == 0) {
         return 1;
      }
   }
   return 0;
}



// md5sum: 6b4baeb51c2b32f5d1e20c9a0498288f humlink.cpp [20050403]