/*>>> make output in protdist format, /howard2/phylip/examples/outfile.protdist
	prodist values are all between 0 and 1?
  >>> allow other formats than fasta for input alignments? Gaps?
*/
/* BLDIST Copyright 1998 Fred Hutchinson Cancer Research Center
   Read a file of multiple alignments in fasta format and a substitution
	matrix and output a matrix of distances between pairs of sequences.

   NOTES: Requires version 3.2.3 or higher of BLIMPS libraries.

   bldist <input file of fasta alignments> <subst matrix> <output_file> <type>
	Limit of 100 sequences in alignment

--------------------------------------------------------------------
 3/20/98  J. Henikoff
 3/23/98  Added correlation option
11/30/00  Added protdist option
====================================================================*/

#define EXTERN
#define MAXNAME 80	/* Maximum file name length */
#define MAXSEQ 100	/* Maximum number of sequences */

#include <blocksprogs.h>

void compute_dist();
int OutType;

/*-------------------Blimps stuff----------------------------------------*/
extern int ErrorLevelReport;
extern struct float_qij *Qij;
extern struct float_qij *load_qij();
double frequency[MATRIX_AA_WIDTH];    /* for frequency.c */

/*=======================================================================*/
/*
 * main
 *   controls flow of program
 *   Parameters: argc, argv
 *   Error codes:
 */

int main(argc, argv)
     int argc;
     char *argv[];
{
   FILE *ifp, *ofp, *sfp;
   Sequence *seqs[MAXSEQ];
   char sijname[MAXNAME], bdbname[MAXNAME], outname[MAXNAME], *blimps_dir;
   char ctemp[MAXNAME];
   int db_type, seq_type, nseq;

   ErrorLevelReport = 4;	/* BLIMPS error level */

   if (argc < 3)
   {
      printf("BLDIST: Copyright 1998 Fred Hutchinson Cancer Research");
      printf(" Center\nUSAGE: bldist <input> <subst> <out>\n");
      printf("   <input> = file of fasta multiple alignments (blocks)\n");
      printf("   <subst> = substitution matrix\n");
      printf("   <out>   = output distance matrix\n");
      printf("   <type>  = output matrix type:\n");
      printf("			S=similarity\n");
      printf("			D=dissimilarity\n");
      printf("			P=dissimilarity in protdist format\n");
      printf("			C=correlation\n");
   }
   /* ------------1st arg = fasta sequences -------------------------------*/
   if (argc > 1) { strcpy(bdbname, argv[1]); }
   else
   {
      printf("\nEnter name of file of fasta multiple alignments: ");
      gets(bdbname);
   }
   if ( (ifp=fopen(bdbname, "r")) == NULL)
   {
      printf("Cannot open file %s\n", bdbname);
      exit(-1);
   }
   db_type = type_dbs(ifp, DbInfo);
   seq_type = UNKNOWN_SEQ;
   seq_type = seq_type_dbs(ifp, DbInfo, db_type, seq_type);
   if (seq_type == NA_SEQ)
   {
      printf("WARNING: Sequences appear to be DNA but will be treated");
      printf(" as protein\n");
      seq_type = AA_SEQ;
   }
   rewind(ifp);

   /* ------------2nd arg = fasta sequences -------------------------------*/
   blimps_dir = getenv("BLIMPS_DIR");
   if (argc > 2) { strcpy(sijname, argv[2]); }
   else
   {
      printf("\nEnter name of file containing substitution matrix: ");
      gets(sijname);
   }
   if ( (sfp=fopen(sijname, "r")) == NULL)
   {
      printf("Cannot open file %s\n", sijname);
      if (blimps_dir != NULL) 
      {
	sprintf(ctemp, "%s/docs/%s", blimps_dir, sijname);
        if ( (sfp=fopen(ctemp, "r")) == NULL)
        {
           printf("Cannot open file %s\n", ctemp);
	   sprintf(ctemp, "%s/docs/default.iij", blimps_dir);
           if ( (sfp=fopen(ctemp, "r")) == NULL)
           {
              printf("Cannot open file %s\n", ctemp);
              exit(-1);
           }
           else
           {   strcpy(sijname, ctemp);   }
        }
      }
   }
   printf("Using %s\n", sijname);
   /*  Using load_qij() because want the option for floating point */
   /*  Qij->value[aa1][aa2]   */
   Qij = load_qij(sfp);
   fclose(sfp);

   /* ------------3rd arg = output file  -----------------------------------*/
   if (argc > 3) { strcpy(outname, argv[3]); }
   else
   {
      printf("\nEnter name of output file: ");
      gets(outname);
   }
   if ( (ofp=fopen(outname, "w")) == NULL)
   {
      printf("Cannot open file %s\n", outname);
      exit(-1);
   }

   /* ------------4th arg = output type  -----------------------------------*/
   if (argc > 4) { strcpy(ctemp, argv[4]); }
   else
   {
      printf("\nEnter type (S=similarity, D=dissimilarity, C=correlation): ");
      gets(ctemp);
   }
   OutType = 1;			/* similarity is default */
   if      (ctemp[0] == 'd' || ctemp[0] == 'D' || ctemp[0] == '2') OutType = 2;
   else if (ctemp[0] == 'c' || ctemp[0] == 'C' || ctemp[0] == '3') OutType = 3;
   else if (ctemp[0] == 'p' || ctemp[0] == 'P' || ctemp[0] == '4') OutType = 4;

   printf("Writing ");
   switch(OutType)
   {
      case 1:
	printf("similarity ");
	break;
      case 2:
	printf("dissimilarity ");
	break;
      case 3:
	printf("correlation ");
	break;
      case 4:
	printf("protdist ");
	break;
      default:
	break;
   }
   printf("matrix to %s\n", outname);

   /*=====================================================================*/
   nseq = 0;
   /*-----------------------------------------------------------------*/
   /*   First read all the sequences into memory                    */
   if (db_type >= 0)
   {
      while ( nseq < MAXSEQ &&
          (seqs[nseq] = read_a_sequence(ifp, db_type, seq_type)) != NULL)
      {
         nseq++;
      }
   }
   fclose(ifp);


   /*=====================================================================*/
   compute_dist(ofp, OutType, nseq, seqs, Qij);

   /*=====================================================================*/
   /*   Make a "mean" sequence, and compute how far each sequence is from
	it; have to use a different sequence representation since each
	position of the artificial sequence will be a blend of amino acids
   */

   /*=====================================================================*/
   fclose(ofp);
   exit(0);
}   /* end of main */

/*=========================================================================
"Distances" are the segment pair scores.

"Correlations" are really percentages of max possible score of a given pair
of sequences: E.G. for:
	WIVNRA
	WLFKQA

	WW=11, II=4/LL=4, VV=4/FF=6, NN=6/KK=5, RR=5/QQ=5, AA=4 => 
		max score = 11+4+6+6+5+4 = 36
	IL=2, VF=-1, NK=0, RQ=1 =>
		act score = 11+2-1+0+1+4 = 17, 17/36 = 0.47 = "correlation"
Completely conserved segment pair always has corr = 1.0, but segment pair
scores could be negative ...

=========================================================================*/
void compute_dist(ofp, mtype, nseq, seqs, mat)
FILE *ofp;
int mtype;		/* output matrix type */
int nseq;
Sequence *seqs[MAXSEQ];
struct float_qij *mat;
{
   int length, s1, s2, aa1, aa2, pos;
   double dist[MAXSEQ][MAXSEQ], maxdist, mindist, meandist, maxscore, dtemp;
   double range;

   maxdist = -999; mindist = 999;
   meandist = 0.0;

   for (s1=0; s1<nseq; s1++)
   {
      length = seqs[s1]->length;
      for (s2=s1; s2<nseq; s2++)
      {
         if (seqs[s2]->length < length)
         {
            printf("WARNING: sequence segments are of different lengths\n");
            printf("%s %d : %s %d\n", seqs[s1]->name, seqs[s1]->length,
		                     seqs[s2]->name, seqs[s2]->length);
            length = seqs[s2]->length;
         }
         dist[s1][s2] = maxscore = 0.0;
         for (pos=0; pos<length; pos++)
         {
            aa1 = seqs[s1]->sequence[pos];
            aa2 = seqs[s2]->sequence[pos];
            dist[s1][s2] += mat->value[aa1][aa2];
	    dtemp = mat->value[aa1][aa1];
	    if (mat->value[aa2][aa2] > dtemp) dtemp = mat->value[aa2][aa2];
	    maxscore += dtemp;
         }
         if (mtype == 3) 		/* correlation */
         {
            dist[s1][s2] /= maxscore;
            /* This is not a proper "correlation" ... */
            if (dist[s1][s2] >  1.0) dist[s1][s2] =  1.0;
            if (dist[s1][s2] < -1.0) dist[s1][s2] = -1.0;
         }
    
      }
   }

   /*  Now dist is similarity matrix, probably
       with unequal diagonal values => some seqs
       are more similar to themselves than others.
       Subtract all entries from max value to make
       a dissimilarity matrix */
   for (s1=0; s1<nseq; s1++)
   {
      for (s2=s1; s2<nseq; s2++)
      {
         meandist += dist[s1][s2];
         if (dist[s1][s2] > maxdist) maxdist = dist[s1][s2];
         if (dist[s1][s2] < mindist) mindist = dist[s1][s2];
         if (s1 != s2) dist[s2][s1] = dist[s1][s2];
      }
   }
   /*  Average score includes diagonals   */
   dtemp = (double) nseq * (nseq + 1.0) / 2.0;
   meandist /= dtemp;
   printf("Mean dissimilarity distance = %.2f\n",
           (maxdist - meandist) );
   range = maxdist - mindist;

   /*  Print dissim. matrix for SAS, requires full matrix, max linesize=256 */
   /*  Proc varclus requires a covariance matrix as input ... */
   if (mtype == 4) fprintf(ofp, "%d\n", nseq);
   for (s1=0; s1<nseq; s1++)
   {
      fprintf(ofp, "%s ", seqs[s1]->name);
      for (s2=0; s2<nseq; s2++)
      {
         switch(mtype)
         {
            case 1:	/* similarity */
              fprintf(ofp, "%.0f ", dist[s1][s2]);
	      break;
            case 2:	/* dissimilarity */
              fprintf(ofp, "%.0f ", maxdist - dist[s1][s2]);
	      break;
            case 3:	/* correlation coefficient */
              fprintf(ofp, "%.2f ", dist[s1][s2]);
	      break;
            case 4:	/* dissimilarity in protdist format */
/*>>>all diagonal values s.b. zero <<<*/
              fprintf(ofp, "%.5f ", (maxdist - dist[s1][s2])/range );
	      break;
            default:
	      break;
         }
         if ((s2 > 0) && (s2%25 == 0)) fprintf(ofp, "\n");
      }
      if (s2 == nseq) fprintf(ofp, "\n");
   }


}  /* end of compute_dist */
