////////////////////////////////////////////////////////////////////////
//
// PwDistWeight.C - 
//
// $Id: PwDistWeight.C,v 1.10 2002/01/11 17:06:05 goetz Exp $
//
////////////////////////////////////////////////////////////////////////

#include "PwDistWeight.h"

#include <iostream.h>
#include <iomanip.h>
#include <strstream.h>


class ostream &operator << ( class ostream &os, const PwDistWeight &p)
{
  cout << (const string)p << ":" << endl;
  for (unsigned int i=0; i < p.numOfSeq; i++) {
    for (unsigned int j=0; j < p.numOfSeq; j++)
      cout << setprecision(6) << setw(10) << p.d(i,j);
    cout << endl;
  }
  return os;
}

double DefaultPwDist::d(unsigned int i, unsigned int j) const
{
  return 1;
}

double PwIndDist::d(unsigned int i, unsigned int j) const
{
  return abs(i-j);
}

double PwDistDynPrg::d(unsigned int i, unsigned int j) const
{
  if ( (i >= numOfSeq) || (j >= numOfSeq) )
    return 0;
  return pwDist[i][j];
}

PwDistDynPrg::PwDistDynPrg( const Alignment &a, const ScoreMatrix &s, 
		      double scaleMaxTo, bool changeOrder= false)
  : PwDistWeight("PairWiseDynPrg", a.numOfSeq()), scaleMaxTo(scaleMaxTo),
    changeOrder(changeOrder), s(s)
{
  // storage for sequences without gaps (to be aligned pairwise)
  string *seq= new string[ numOfSeq ];
  unsigned int alen= a.aliLength();

  // remove gap chars from all sequence
  for (unsigned int i=0; i < numOfSeq; i++) {
    seq[i]= "";
    for (unsigned int j=0; j < alen; j++)
      if (! a.sequences[i][j].isGap() )
	seq[i] += a.sequences[i][j].c;
  }
  
  // create array of alignments
  pwDist= new (double *)[numOfSeq];
  for (unsigned int i=0; i < numOfSeq; i++)
    pwDist[i]= new double[numOfSeq];

  double pwmin(0),pwmax(0); // max score for scaling if desired
  // this initializiation works, since d(i,i)=0 !!

  // calculate all pairwise alignments
  for (unsigned int i=0; i < numOfSeq; i++) {
    for (unsigned int j=0; j < i; j++) {
      pwDist[i][j]= pwDist[j][i]= pwAlign( seq[i], seq[j] );
	pwmax= max(pwmax, pwDist[i][j]);
	pwmin= min(pwmin, pwDist[i][j]);
    }
    pwDist[i][i]= 0;
  }
  
  // if a scale factor is given, rescale all scores to [0..scaleMaxTo]
  if (scaleMaxTo != 0) {
    if ( pwmax-pwmin > 1e-8 ) // sensible values for scaling interval?
    for (unsigned int i=0; i < numOfSeq; i++) 
      for (unsigned int j=0; j < numOfSeq; j++) {
	pwDist[i][j]= (pwDist[i][j]-pwmin)*scaleMaxTo/(pwmax-pwmin);
	if (changeOrder)
	  pwDist[i][j]= scaleMaxTo - pwDist[i][j];
      }
    else 
      scaleMaxTo= 0;          // set scale to 0, since there is no scale!
  }
  
  delete [] seq;
}

PwDistDynPrg::~PwDistDynPrg()
{
  for (unsigned int i=0; i < numOfSeq; i++)
    delete [] pwDist[i];
  delete [] pwDist;
}

int PwDistDynPrg::pwAlign( const string &s1, const string &s2 )
{
  unsigned int len_s1( s1.length() ), len_s2( s2.length() );
  int score; // buffering the result
  // init dyn programming matrix:
  int **a= new (int *)[len_s1+1];
  for (unsigned int i=0; i <= len_s1; i++)
    a[i]= new int[len_s2+1];
  
  // initialize "border values"
  a[0][0]= 0;
  for (unsigned int i=1; i <= len_s1; i++)
    // just initialize with gap penalties ('A' is an arbitrary char)
    a[i][0]= a[i-1][0] + s.sc( 'A', '.' );
  for (unsigned int j=1; j <= len_s2; j++)
    // just initialize with gap penalties ('A' is an arbitrary char)
    a[0][j]= a[0][j-1] + s.sc( 'A', '.' );
  
  // run through usual dynamic programming process for aligning two seqs
  for (unsigned int i=1; i <= len_s1; i++)
    for (unsigned int j=1; j <= len_s2; j++)
      a[i][j]= max( a[i-1][j  ] + s.sc( s1[i-1], '.'   ),
		    a[i-1][j-1] + s.sc( s1[i-1], s2[j-1] ),
		    a[i  ][j-1] + s.sc( '.'  , s2[j-1] ));
  score= a[len_s1][len_s2];

  for (unsigned int i=0; i <= len_s1; i++)
    delete [] a[i];
  delete [] a;  // clean up tmp array

  return score;
}

double GroupPwCut::d(unsigned int i, unsigned int j) const
{
  return sameGroup[i][j] ? groupWeight : noGroupWeight;
}


GroupPwCut::GroupPwCut( class istream &ifs, const Alignment &al,
	     double groupWeight, double noGroupWeight)
  throw (FFormatException)
  : PwDistWeight( "GroupPwCut", al.numOfSeq() ), 
  groupWeight(groupWeight), noGroupWeight(noGroupWeight),
  sameGroup(0)
{
  string line(""), s(""); // tmp variables for reading a line and parts
  bool newFamily(true); // determine whether a new family line has been reached
  unsigned int curFamily(0); // first member of current family
  unsigned int ind;       // index for iterating over elemets of a family

  // initialize
  sameGroup= new (bool *)[numOfSeq];
  for (unsigned int i=0; i < numOfSeq; i++) {
    sameGroup[i]= new bool[numOfSeq];
    for (unsigned int j=0; j < numOfSeq; j++) 
      sameGroup[i][j]= false;
  }
  
  // read all lines before group information
  while (ifs && s != "Family" )
    ifs >> s;
  
  while (ifs && s == "Family" ) {
    getline(ifs, line); // read everything left in the line    

    newFamily= true;
    istrstream iss(line.c_str());
    while (iss >> s) { // read all fields from that line
      // determine the index of the sequence name
      ind= 0;
      while ( (ind < numOfSeq) && (al.seqnames[ind] != s) )
	ind++;      
      if (ind == numOfSeq) // did not find valid sequence name
	throw FFormatException("Unknown sequence name in groupfile: "+s);

      sameGroup[ind][ind]= true;
      if (newFamily) {       // reset flag: next sequence is not new family, 
	newFamily= false;    // but current
	curFamily= ind;      // "ind" starts a new family!
      } else {
	// set all other members of this familiy to be in same group as "ind"
	for (unsigned int i=0; i < numOfSeq; i++)
	  // is sequence "i" in current family? If yes, add new "relative"
	  if ( sameGroup[i][curFamily] )
	    sameGroup[i][ind]= sameGroup[ind][i]= true;
      }
    }
    ifs >> s;
  }
}

GroupPwCut::~GroupPwCut()
{
  for (unsigned int i=0; i < numOfSeq; i++)
    delete [] sameGroup[i];
  delete [] sameGroup;
}
