////////////////////////////////////////////////////////////////////////
//
// Alignment.h - a class containing all data structures for storing a 
//               multiple sequence alignment
//
// $Id: Alignment.C,v 1.8 2002/01/12 11:05:29 goetz Exp $
//
////////////////////////////////////////////////////////////////////////

#include "Alignment.h"

class ostream & operator << ( class ostream &os, const SecStructure &s)
{
  return os << ((s.type == SecStructure::ALPHA) ? "a" :
    (s.type == SecStructure::BETA) ? "b" : " "); 
}

class ostream & operator << ( class ostream &os, const Alchar &a)
{
  return os << a.c;
}

Alignment::Alignment( istream &msf, const string &name )
  throw ( IOException, FFormatException )
  : name(name), hasSecStr(false)
{
  readMsfIntoBuffers( msf );
  parseBuffers();
}

Alignment::Alignment( istream &msf, istream &ftb, istream &refal,
		      const string &name )
  throw ( IOException, FFormatException )
    : name(name), hasSecStr(false)
{
  readMsfIntoBuffers( msf );
  parseBuffers();
  readFtbFile( ftb, refal );
}

Alignment::~Alignment()
{
  int num= numOfSeq();
  for (int i=0; i < num; i++) {
    delete [] sequences[i];
    delete [] positions[i];
  }
  delete [] sequences;
  delete [] positions;
  delete [] origLength;
  delete [] buffers;
}

void Alignment::readMsfIntoBuffers( istream &is )
  throw ( IOException, FFormatException )
{
  string s; // tmp variable

  if (!is)
    throw IOException("msf input file: Stream not open!");

  is >> s;  // read "PileUp" keyword and check ...
  if (s != "PileUp")
    throw FFormatException("msf input file: Keyword \"PileUp\" missing!");

  // read Header (ended by "//")
  do { 
    is >> s;
    if (s == "Name:") {   // found a new line containing a sequence name
      is >> s;               // read that name
      seqnames.push_back(s); // and store it!
      getline(is, s);        // eat up rest of line - not needed!
    }
  } while ( s != "//");      // now we have vector of names and hence number!

  // make space for sequences:
  buffers= new string[ numOfSeq() ];

  // read alignment sequences
  int curSeqNum= 0;          // for checking: sequences have to be in same
                             // order as stated in header
  while (is) {               // read data as long as there is data ... 
    is >> s;                 // try to read sequence name
    if (s != "" && is) {           // if we found a name, check it!
      if (s != seqnames[ curSeqNum ]) // is it the right one ?
	throw FFormatException("Wrong sequence found: "+s+". Expected: "+
			       seqnames[ curSeqNum ]+"!");
      getline(is, s);        // rest of line is Sequence data - get it!
      buffers[ curSeqNum ] += s;    // append to corresponding buffer
      // increase counter (modulo numOfSeq() )
      curSeqNum= (curSeqNum + 1) % numOfSeq(); 
    }
  } // while

  if (curSeqNum != 0) // did we find enough sequences in all loop iterations?
    throw FFormatException("Wrong sequence count in file. Maybe truncated?");

  // remove whitespaces from sequences and convert sequences to upper-case
  // and gap character to "."
  for (unsigned int i=0; i < numOfSeq(); i++) {
    for (unsigned int j=0; j < buffers[i].length(); j++) {
      char c= buffers[i][j];
      
      if ( c == ' ' || c == '\t') { // whitespace found, remove !
	buffers[i].erase(j, 1);
	j--;          // searching position did not change (since a char was 
	              // removed), so avoid increment!
      } else if ( c == '_' || c == '-' ) // some nonstandard gap-char found!
	buffers[i][j]= '.';

      else if ( c >= 'a' && c <= 'z' )   // change lower case to upper case
	buffers[i][j]= toupper(c);
      // else no change needed !
    } // for j
    
    // check if all sequences are of the same length
    if ((i != 0) && (buffers[0].length() != buffers[i].length() ))
      throw FFormatException("Sequence "+ seqnames[i] + " has wrong length!");
  } // for i
} // readMsfIntoBuffers

void Alignment::parseBuffers()
{
  // set up two-dimensional dynamical array: "1st coordinate"
  sequences= new (Alchar *)[ numOfSeq() ];
  positions= new (Alcharpos *)[ numOfSeq() ];
  origLength= new Alcharpos[ numOfSeq() ];

  // repeat for each sequence ...
  for (unsigned int i=0; i < numOfSeq(); i++) {
    // set up "2nd coordinate" - positions array is much too long - don't care!
    sequences[i]= new Alchar[ aliLength() ];
    positions[i]= new Alcharpos[ aliLength() ];
    
    int actPos= 0; // actual positions in sequence s_i (ignoring gaps)
    for (unsigned int j=0; j < aliLength(); j++) {
      if ( buffers[i][j] == '.' )           // found gap ?
	sequences[i][j]= Alchar('.', -1);   // copy char to new storage
      else {
	sequences[i][j]= Alchar(buffers[i][j], actPos); 
	positions[i][actPos]= j;            // remember pos of char in al(s_i)
	actPos++;
      }
    } // for j
    origLength[i]= actPos;                  // store length of s_i
  } // for i
} // parseBuffers

void Alignment::readFtbFile( istream &ftb, istream &refAl )
  throw ( IOException, FFormatException )
{
  // create reference alignment for conversion of position data in .ftb file
  Alignment refali( refAl );
  
  string       s;           // tmp for parsing the ftb file
  int          tmpInt;      // tmp for parsing the ftb file
  SecStructure sstr;        // tmp for parsing the ftb file
  
  // read structural data from .ftb file
  int curSeqNum= -1;          // for checking: sequences have to be in same
                             // order as in .msf file
  while (ftb >> s) {               // read data as long as there is data ... 
    if (ftb && (s == "name")) { // "name" token parsed
      // parse name and check if it is the expected one:
      if ((ftb >> s) && (s == seqnames[ curSeqNum + 1])) 
	curSeqNum++;
      else
	throw FFormatException("ftb - Wrong sequence found: "+s+". Expected: "+
			       seqnames[ curSeqNum + 1 ]+"!");

    } else if (ftb && (s == "feature")) { // found structure data
      
      if (! (ftb >> s)) 
	throw FFormatException("ftb - Parse error in: "+s+" for sequence: "+
			       seqnames[ curSeqNum ]+"!");
      if (s == "H") 
	sstr.type= SecStructure::ALPHA;
      else if (s == "E")
	sstr.type= SecStructure::BETA;

      // try to parse an integer value ...
      if (! (ftb >> tmpInt) 
	  || (unsigned int)tmpInt > refali.aliLength() || tmpInt < 0) 
	throw FFormatException("ftb - Parse error in: int 1 for sequence: "+
			       seqnames[ curSeqNum ]+"!");

      // unfortunately there are entries with both positions 0,
      // skip them!
      if (tmpInt == 0) {
	ftb >> tmpInt;  // read 2nd entry
	continue;
      }

      // convert to absolute sequence positions (from reference alignment pos)
      // unfortunately, positions in .ftb file are in range 1..max, not
      // in 0..max, hence tmpInt-1
      sstr.beg= refali.sequences[curSeqNum][tmpInt-1].pos;

      // try to parse an integer value ...
      if (! (ftb >> tmpInt) 
	  || (unsigned int)tmpInt > refali.aliLength() || tmpInt < 0) 
	throw FFormatException("ftb - Parse error in: int 2 for sequence: "+
			       seqnames[ curSeqNum ]+"!");

      // convert to absolute sequence positions (from reference alignment pos)
      sstr.end= refali.sequences[curSeqNum][tmpInt-1].pos;

      // problematic case: secondary structure may begin in gap or end in gap
      // since this case cannot be handeled, ignore it (instead of crashing
      // into used memory!!)
      if ( (sstr.end == -1) || (sstr.beg == -1) )
	continue;

      // fill up positions in this alignment with new sec. structure data
      // positions is used to translate absolute sequence positions to 
      // local positions in this alignment
      for (int j= positions[curSeqNum][ sstr.beg ];
	   j <= positions[curSeqNum][ sstr.end ]; j++)
	sequences[curSeqNum][j].secstr= sstr;
    } // if feature
  } // while

  if (curSeqNum != (int)numOfSeq()-1) // did we find all sequences ?
    throw FFormatException("Wrong sequence count in .ftb file. "
			   "Maybe truncated?");
  
} // readFfbFile

class ostream & operator << ( class ostream &os, const Alignment &a)
{
  os << "Alignment: " << a.name << " Num of seqs: " << a.numOfSeq() << endl;
  for (unsigned int i=0; i < a.numOfSeq(); i++) {
    cout << "Sequence: " << a.seqnames[i] << endl;
    for (unsigned int j=0; j < a.aliLength(); j++)
      cout << a.sequences[i][j].c 
	   << a.sequences[i][j].secstr;
    cout << endl;
  }	
  return os << "done." << endl;
} // operator <<

