/*>>> change to use block_to_matrix(block, 9)  <<<<*/
/*  Copyright 2000 Fred Hutchinson Cancer Research Center
	Implements procedures developed by Jon Cooper & Jim Smothers
    Read a block or a file of frequencies
	narrow <B|F|W> <input_file>
    Make a log-odds matrix in mast or blimps format as follows:
	For col=c, amino acid=a:
	w_ac = log_2[p_ca/p_a]
		If f_ac > 0, p_ca = f_ca => w_ac = log_2[f_ca/p_a]
		If f_ac = 0, p_ca = p_a/1000 => w_ac = log_2[1000]
		f_ac = sequence-weighted frequencies, 0 <= f_ac <= 1
			if block has no sequence weights, use equal weights
		p_a = background frequences, 0 <= p_a <= 1

	This model approximates no pseudo-counts.

	Mast file is assumed to look like this:
ALPHABET= ARND...
log-odds matrix: alength= <na> w= <nw>
nw*na numbers
		or
ARND...
nw*na numbers
----------------------------------------------------------------------*/

#define EXTERN
#define AASALL 26
#define MAXWIDTH 400
#define TOTREAL 10000	/* Total number of presumed real counts if freqs in */
#define PERREAL 10000.0	/* Total number of pseudo per real if block in */

#include <blocksprogs.h>

struct working {	/* Working information for one column */
  double cnt[AASALL];		/* Sequence-weighted counts */
  double totcnt;
  double raw[AASALL];		/* unweighted counts */
  double totraw;
  double reg[AASALL];		/*  pseudo counts */
  double totreg;
};

struct work_pssm {		/* Working PSSM area */
  double value[MAXWIDTH][AASALL];
  double sum[MAXWIDTH];
};


/*		Duplicate static routines in convert.c    */
struct working *make_col();
struct work_pssm *make_pssm();
void counts();
void positive_matrix();
void compute_BZX();
void output_mast_matrix();
void pseudo_alts();
int count_residues();

/*	Routines for this program */
Matrix *make_matrix();
void dummy_counts();
void dummy_pseudos();
void positive100_matrix();

int TotReal;
/*====================================================================*/
int main(argc, argv)
int argc;
char *argv[];
{
   FILE *chk, *fout;
   char chkname[80], ctemp[80], line[MAXLINE], *ptr, *ptr1;
   Matrix *pssm, *matrix;
   Block *block;
   int s, pos, aa, alpha[MATRIX_AA_WIDTH], width, alength, itemp, okay, type;
   int mast, nlines;
   double dtemp, dmin, dmax;

   ErrorLevelReport = 3;	/* only serious errors, set to 5 for none */

   if (argc < 3)
   {
      printf("COPYRIGHT 2000 Fred Hutchinson Cancer Research Center\n");
      printf("USAGE:  narrow <file_type> <file_name>\n");
      printf("		file_type = B|F|W	B=block, F=frequencies, W=weights\n");
      printf("					F or W in MAST format\n");
      printf("Writes a positive PSSM to stdout in BLIMPS format\n");
   }
   /*------------1st arg =  type ----------------------------*/
   type = 0;	/* 0=> block, 1=>freqs 2=>weights in MAST format */
   if (argc > 1)
      strcpy(ctemp, argv[1]);
   else
   {
      printf("\nEnter type of input file (Block|Frequencies|Weights): ");
      gets(ctemp);
   }
   if (ctemp[0] == 'F' || ctemp[0] == 'f') type = 1; 
   else if (ctemp[0] == 'W' || ctemp[0] == 'w') type = 2; 

   /*------------2nd arg = input file--------------------*/
   if (argc > 2)
      strcpy(chkname, argv[2]);
   else
   {
      printf("\nEnter name of input file: ");
      gets(chkname);
   }
   if ( (chk=fopen(chkname, "r")) == NULL)
   {
      printf("\nCannot open file %s\n", chkname);
      exit(-1);
   }

   /*----------------------------------------------------------------------*/
   frq_qij();	/* Sets Qij, RTot and frequency[] to default values */
   RTot = 1.0;
   TotReal = TOTREAL;

   for (aa=0; aa < MATRIX_AA_WIDTH; aa++) alpha[aa] = -1;
   alength = width = pos = 0;
   dmin = 9999999.99;
   dmax = -9999999.99;
   pssm = matrix = NULL;
   block = NULL;
   okay = mast = NO;

   
   if (type == 0)
   {
      block = read_a_block(chk);
      if (block == NULL)
      {
         printf("ERROR:No block found in file %s\n", chkname);
	 printf("Please verify input format type\n");
         exit(-1);
      }
      width = block->width;
      for (s=0; s< block->num_sequences; s++) 
	block->sequences[s].weight = 100.0; 
   }
   else		/* read the frequencies or weights into a pssm */
   {
      /*  see if input file is in mast format  */
      mast = NO; nlines = 0;
      while (!mast && !feof(chk) && fgets(line, MAXLINE, chk) != NULL )
      {
         if (strncmp(line, "ALPHABET", 7) == 0) mast = YES;
         nlines++;
      }
      rewind(chk);

      if (mast)
      {
         while (!okay && !feof(chk) && fgets(line, MAXLINE, chk) != NULL )
         {
            if (strncmp(line, "ALPHABET", 7) == 0)
            {
	      ptr = strtok(line, " ");
              if (ptr != NULL)
              {
		ptr = strtok(NULL, " \r\t\n");
		if (ptr != NULL) alength = (long) strlen(ptr);
		else alength = 0;
              }
              if (alength == 0)
              {
		printf("\nProblem finding ALPHABET in %s\n", chkname);
		exit(-1);
              }
	      for (itemp=0; itemp<alength; itemp++)
	      {
		aa = aa_atob[ ptr[itemp] ];
		if (aa >= 0 && aa < MATRIX_AA_WIDTH) alpha[itemp] = aa;
	      }
            }   /* end of ALPHABET */

            /* alength= %d w= %d  */
            if ( (strncmp(line, "log-odds", 8) == 0) ||
                 (strncmp(line, "frequency", 9) == 0)  )
            {
	      ptr1 = strstr(line, "alength=");
              if (ptr1 != NULL)
              {
		sscanf(ptr1, "alength= %d w=%d", &itemp, &width);
		if (itemp != alength)
		{
		   printf("\nALPHABET length (%d) not alength (%d)\n", 
				alength, itemp);
		   exit(-1);
		}
              }
              if (width == 0)
              {
		printf("\nProblem finding width in %s\n", chkname);
		exit(-1);
              }
	      else
	      {
                okay = YES;
	      }
            }  /* end of log-odds */
         }      /* end of while */
      }  /* end of if mast format  */
      else
      {
		printf("Not sure about the format of %s,", chkname);
		printf(" assuming first line contains alphabet\n");
		width = nlines - 1;
		fgets(line, MAXLINE, chk);
		ptr = strtok(line, " \r\t\n");
		if (ptr != NULL) alength = (long) strlen(ptr);
		else alength = 0;
	      for (itemp=0; itemp<alength; itemp++)
	      {
		aa = aa_atob[ ptr[itemp] ];
		if (aa >= 0 && aa < MATRIX_AA_WIDTH) alpha[itemp] = aa;
	      }
              if (width > 0 && alength > 0)  okay = YES;
      }  /* end of not-mast input  */

      /*  Now have width and alphabet; read the weights next */
      if (okay)
      { 
	pssm = new_matrix( (int) width);
	pssm->width = width;
	strcpy(pssm->id, "WEIGHTS");
	strcpy(pssm->ac, "WEIGHTS");
	strcpy(pssm->de, chkname);
	sprintf(pssm->ma, "width=%d;", width);
         while (pos < width && !feof(chk) && fgets(line, MAXLINE, chk) != NULL )
         {
	    ptr = strtok(line, " \t\r\n");
            for (aa=0; aa < alength; aa++)
            {
		if (ptr != NULL)
		{
			dtemp = atof(ptr);
			pssm->weights[ alpha[aa] ][pos] = dtemp;
			if (dtemp < dmin) dmin = dtemp;
			if (dtemp > dmax) dmax = dtemp;
			ptr = strtok(NULL, " \t\r\n");
		}
		else
		{
			printf("\nProblem finding value for pos=%d aa=%c\n",
				pos, aa_btoa[aa]);
		}
            }
	    pos++;
	}
        if (type == 1 && dmin < 0.0)
        {
           printf("ERROR: Negative frequencies read\n");
	   printf("Please verify input format type\n");
           exit(-1);
        }
        if (type > 0 && dmin == 0.0 && dmax == 0.0)
        {
           printf("ERROR: All values read as zero\n");
	   printf("Please verify input format type\n");
           exit(-1);
        }
      } /* end of okay */
      else
      {
	printf("Could not find alphabet or width in %s\n", chkname);
        exit(-1);
      }
   }  /* end of non-zero (non-block) input type */ 

   /*----------------------------------------------------------------------*/
   /*>>>>  what scale? 0=> nats  <<<<*/
   switch(type)
   {
      case 0:  /* block was input; pssm is NULL */
         matrix = make_matrix(block, pssm, 0);
      break;

      case 1:	/* frequencies were input */
         /*  Print the frequencies */
/*>>>>  need to check that freqs add to 1.0 in each column 
         output_matrix_st(pssm, stdout, FLOAT_OUTPUT, AA_SEQ);
<<<<*/
	 /* Create a dummy block for make_matrix() */
         block = new_block( (int) width, 0);
         block->width = width;
         strcpy(block->id, "FREQS");
         strcpy(block->ac, "FREQS");
         strcpy(block->de, chkname);
         sprintf(block->bl, "seqs=%d; width=%d;", TotReal, width);
         block->num_sequences = 0;
	 /*  Create the matrix with almost no pseudo counts */
         matrix = make_matrix(block, pssm, 0);
      break;

      case 2:		/* weights were input */
		matrix = pssm;
      break;

      default:
      break;
   } /* end of switch(type) */

   /*  Print the nat matrix in mast format */
   sprintf(ctemp, "%s.mast", chkname);
   if ( (fout=fopen(ctemp, "w")) == NULL)
   {
      printf("\nCannot open file %s\n", ctemp);
      output_mast_matrix(matrix, stdout);
   }
   else
   { 
      output_mast_matrix(matrix, fout);
      fclose(fout);
   }
   /*  Normalize weights to add to 100 in each position */
   positive100_matrix(matrix);
   /*  Print the weights in Blimps format */
   sprintf(ctemp, "%s.blimps", chkname);
   if ( (fout=fopen(ctemp, "w")) == NULL)
   {
      printf("\nCannot open file %s\n", ctemp);
      output_matrix_st(matrix, stdout, FLOAT_OUTPUT, AA_SEQ);
   }
   else
   { 
      output_matrix_st(matrix, fout, FLOAT_OUTPUT, AA_SEQ);
      fclose(fout);
   }

   exit(0);
}  /* end of main */
/*==========================================================================
    Make a log-odds matrix with as few pseudo-counts as possible
	Expects global variables RTot, Qij, and frequency[]
	RTot  = dummy number of real counts if frequencies entered
	If just frequencies entered, block->num_sequences = 0 and
		frequencies are in colfreqs
===========================================================================*/
Matrix *make_matrix(block, colfreqs, scale)
     Block *block;
     Matrix *colfreqs;
     int scale;
{
  double factor, dtemp, epsilon;
  int pos, aa;
  struct working *col;
  struct work_pssm *work;
  Matrix *matrix;

  matrix = new_matrix( (int) block->width);
  matrix->width = block->width;
  strcpy(matrix->id, block->id);
  strcpy(matrix->ac, block->ac);
  strcpy(matrix->de, block->de);
  strcpy(matrix->ma, block->bl);

  factor = 1.0;
  if (scale > 0 && scale < 10) factor = (double) scale / log(2.0);
  col = make_col();
  work = make_pssm();
  
  /*--------------  Do one position at a time -------------------*/
  for (pos = 0; pos < block->width; pos++) 
  {
    /*-------- count the number of each aa in this position ------------*/
    /*  Might have frequencies already           */
    if (colfreqs == NULL) counts(block, col, pos);
    /* col->cnt[aa] = n_ca = TotReal * f_ca  */
    else dummy_counts(colfreqs, col, TotReal, pos); 
    
    /*-------- determine total number of pseudo-counts in column ------*/
    epsilon = 1.0;
    
    /*---------- get the pseudo counts -------------------------------*/
    if (colfreqs == NULL)
    { 
/*
        epsilon = RTot * (double) count_residues(col);
*/
        /* if a block was input, use 1 pseudo-count per 100 counts */
        epsilon = (double) col->totcnt / PERREAL;
        pseudo_alts(col, Qij, epsilon);
    }
    /* col->reg[aa] = b_ca = epsilon * p_a  */
    else  dummy_pseudos(col, epsilon);
    
    /*---------   Fill in the matrix entries --------------------*/
    work->sum[pos] = 0.0;
    for (aa=1; aa < AAS; aa++)
    {
       work->value[pos][aa] = col->cnt[aa] + col->reg[aa];
       if ( (col->totcnt + col->totreg) > 0.0)
             work->value[pos][aa] /= (col->totcnt + col->totreg);
    
       /*     Odds ratios  */
       if (frequency[aa] > 0.0)
          work->value[pos][aa] /= frequency[aa];

       /*   take the log of the odds ratio  */
       if (work->value[pos][aa] > 0.0) 
          work->value[pos][aa] = log(work->value[pos][aa]);
 
       work->sum[pos] += work->value[pos][aa];

       /*  scale the matrix  */
       dtemp = factor * work->value[pos][aa];
       matrix->weights[aa][pos] = dtemp;

     }  /* end of aa */

/*>>>>??? this is what blimps does
     compute_BZX(matrix, pos); 
*/
  }  /*  end of for pos */

  /*------ Now make the final scores; make log scores non-neg    */
  /*       by subtracting the min. value for all positions ----- */
/*>>>>??? this is what blimps does
  if (!scale) positive_matrix(work, matrix);
*/
  
  free(col);
  free(work);
  return(matrix);
}  /* end of make_matrix */
/*==================================================================*/
void dummy_counts(colfreqs, col, totreal, pos)
Matrix *colfreqs;
struct working *col;
int totreal, pos;
{
   int aa;

   col->totcnt = totreal;
   for (aa=0; aa<AASALL; aa++) col->cnt[aa] = 0.0;
   for (aa=1; aa < AAS; aa++) col->cnt[aa] = totreal * colfreqs->weights[aa][pos];

}  /* end of dummy_counts */
/*==================================================================*/
void dummy_pseudos(col, totpseudo)
struct working *col;
double totpseudo;
{
   int aa;

   col->totreg = totpseudo;
   for (aa=0; aa<AASALL; aa++) col->reg[aa] = 0.0;
   for (aa=1; aa < AAS; aa++) col->reg[aa] = totpseudo * frequency[aa];

}  /* end of dummy_pseudos */
/*==================================================================*/
struct working *make_col()
{
  struct working *col;
  int aa;
  
  CheckMem(
	   col = (struct working *) malloc(sizeof(struct working))
	   );
  
  col->totcnt = col->totreg = 0.0;
  for (aa=0; aa < AASALL; aa++) {
    col->cnt[aa] = col->reg[aa] = 0.0;
  }

  return col;
}  /* end of make_col */


/*=====================================================================*/
struct work_pssm *make_pssm()
{
  struct work_pssm *pssm;
  int pos, aa;
  
  CheckMem(
	   pssm = (struct work_pssm *) malloc(sizeof(struct work_pssm))
	   );

  for (pos = 0; pos < MAXWIDTH; pos++) {
    pssm->sum[pos] = 0.0;
    for (aa=0; aa < AASALL; aa++) {
      pssm->value[pos][aa] = 0.0;
    }
  }

  return pssm;
}  /* end of make_pssm */
/*======================================================================
>>>> What to do with Xs? 
======================================================================*/
void counts(block, col, pos)
     Block *block;
     struct working *col;
     int pos;
{
  int seq, aa, aa1;

  col->totcnt = col->totraw = col->totreg = 0.0;
  for (aa = 0; aa < AASALL; aa++) { 
    col->cnt[aa] = col->raw[aa] = col->reg[aa] = 0.0;
  }

  /*  Only count the real 20 aas, combine B(21) with D & Z(22) with E  */
  for (seq = 0; seq < block->num_sequences; seq++) {
    aa = block->residues[seq][pos];
    if (aa == 21) aa = 4;			/* combine B with D */
    if (aa == 22) aa = 7;			/* combine Z with E */
    if (aa >= 1 && aa < AAS) {
      col->cnt[aa] += block->sequences[seq].weight;
      col->totcnt += block->sequences[seq].weight;
      col->raw[aa] += 1.0;
      col->totraw += 1.0;
    }
    else  
    {
/*>>>>   Want to just ignore the count ???  <<<< */
      /* If not one of the basic aas, divide the count among them */
      for (aa1 = 1; aa1 < AAS; aa1++)
      {
         col->cnt[aa1] += (block->sequences[seq].weight / 20.0);
         col->raw[aa1] += (1.0 / 20.0) ;
      }
      col->totcnt += block->sequences[seq].weight;
      col->totraw += 1.0;
    }
  }
}  /* end of counts */
/*=========================================================================
      Adds negative minval to give all positive matrix,
      then multiplies by 99/maxval to give scores ranging from 0 to 99 
      NOTE: Not 0 to 100 because "output_matrix" routine might not leave
	    enough space.
===========================================================================*/
void positive_matrix(pssm, matrix)
     struct work_pssm *pssm;
     Matrix *matrix;
{
  int pos, aa;
  double factor, maxval, minval, dtemp;

  minval = 9999.9;
  maxval = -9999.9;
  for (pos = 0; pos < matrix->width; pos++) {
    for (aa=1; aa < AAS; aa++) {
      if (pssm->value[pos][aa] < minval) minval = pssm->value[pos][aa];
      if (pssm->value[pos][aa] > maxval) maxval = pssm->value[pos][aa];
    }
  }
   
  if (minval < 0.0) {
    factor = 99.0 / (maxval - minval);
  }
  else {
    factor = 99.0 / maxval;
  }
  if (factor < 1.0) {
    factor = 1.0;
  }
  for (pos = 0; pos < matrix->width; pos++) {
    for (aa=1; aa < AAS; aa++) {
      if (minval < 0.0) {
	dtemp = factor * (pssm->value[pos][aa] - minval);
      }
      else {
	dtemp = factor * pssm->value[pos][aa];
      }
      matrix->weights[aa][pos] = (MatType) dtemp;
    }
    compute_BZX(matrix, pos);
  }  /*  end of for pos */
}  /* end of positive_matrix */
/*=========================================================================
	Normalize 20 aa weights to add to 100 in every column
===========================================================================*/
void positive100_matrix(matrix)
Matrix *matrix;
{
  int pos, aa;
  double maxval, minval, dtemp;

  minval = 9999.9;
  maxval = -9999.9;
  for (pos=0; pos < matrix->width; pos++)
  {
     for (aa=1; aa <= 20; aa++) 
     {
      if (matrix->weights[aa][pos] < minval) minval = matrix->weights[aa][pos];
      if (matrix->weights[aa][pos] > maxval) maxval = matrix->weights[aa][pos];
     }
  }
   
  /* make all weights positive */
  if (minval < 0)
  {
     for (pos=0; pos < matrix->width; pos++)
     {
        for (aa=1; aa <= 20; aa++)
        { matrix->weights[aa][pos] = matrix->weights[aa][pos] - minval; }
     }
  }

  /* make weights sum to 100.0 in each position */
  for (pos=0; pos < matrix->width; pos++)
  {
     dtemp = 0.0;
     for (aa=1; aa <= 20; aa++) 
     { dtemp += matrix->weights[aa][pos]; }
     /*  if all weights = minval to start, dtemp will be zero */
     if (dtemp > 0.0)
     {
        for (aa=1; aa <= 20; aa++) 
        { matrix->weights[aa][pos] = 100.0 * matrix->weights[aa][pos] / dtemp; }
     }

     compute_BZX(matrix, pos);
  }

}  /* end of positive100_matrix */


/*========================================================================
	Computes scores for B, Z, X, -(gap) and *(stop) in a column
	of a PSSM using other scores in the column
	Assumes global frequency[]
==========================================================================*/
void compute_BZX(matrix, col)
Matrix *matrix;
int col;
{
  int aa;
  double dmean, dmin;
  double part_D;		/* the partition of D for B. */
				/* = freq[D] / ( freq[D] + freq[N] ) */
  double part_N;		/* the partition of N for B. */
				/* = freq[N] / ( freq[D] + freq[N] ) */
  double part_E;		/* the partition of E for Z. */
				/* = freq[E] / ( freq[E] + freq[Q] ) */
  double part_Q;		/* the partition of Q for Z. */
				/* = freq[Q] / ( freq[E] + freq[Q] ) */
  /*
   * find the partitions of D, N, E, and Q for B and Z
   */
  part_D = frequency[aa_atob['D']] / 
    ( frequency[aa_atob['D']] + frequency[aa_atob['N']] );
  part_N = frequency[aa_atob['N']] / 
    ( frequency[aa_atob['D']] + frequency[aa_atob['N']] );
  part_E = frequency[aa_atob['E']] / 
    ( frequency[aa_atob['E']] + frequency[aa_atob['Q']] );
  part_Q = frequency[aa_atob['Q']] / 
    ( frequency[aa_atob['E']] + frequency[aa_atob['Q']] );

    /* fill in the matrix for B, Z, X, gap, stop and non */
    matrix->weights[aa_atob['B']][col] = 
      (part_D * matrix->weights[aa_atob['D']][col] +
       part_N * matrix->weights[aa_atob['N']][col]);
    matrix->weights[aa_atob['Z']][col] =
      (part_E * matrix->weights[aa_atob['E']][col] +
       part_Q * matrix->weights[aa_atob['Q']][col]);

    /*   X or unk gets the weighted average score; - and * get the min score */
    dmin = 999.99;
    dmean = 0.0;
    for (aa=1; aa<20; aa++)
    {
       dmean += frequency[aa] * matrix->weights[aa][col];
       if (matrix->weights[aa][col] < dmin) dmin = matrix->weights[aa][col];
    }
    matrix->weights[aa_atob['X']][col] = dmean;
    matrix->weights[aa_atob[25]][col] = dmean;		/* unknown res */
    matrix->weights[aa_atob['-']][col] = dmin;
    if (dmin > 0.0) matrix->weights[aa_atob['*']][col] = 0.0;
    else            matrix->weights[aa_atob['*']][col] = dmin;

}  /* end of compute_BZX */
/*=======================================================================
    0- 1A 2R 3N 4D 5C 6Q 7E 8G 9H 10I 11L 12K 13M 14F 15P 16S 17T 18W 19Y
   20V 21B 22Z 23X 24* 25J,O,U 
========================================================================*/
void output_mast_matrix(matrix, ofp)
Matrix *matrix;
FILE *ofp;
{
   int pos, aa;

   fprintf(ofp, "ALPHABET= ARNDCQEGHILKMFPSTWYVBZX\n");
   fprintf(ofp, "log-odds matrix: alength= 23 w= %d\n", matrix->width);
   for (pos=0; pos < matrix->width; pos++)
   {
      /*  Force U == X */
      matrix->weights[25][pos] = matrix->weights[23][pos];
      for (aa=1; aa <= 23; aa++)
      {
         fprintf(ofp, "% 6.3f ", matrix->weights[aa][pos]);
      }
      fprintf(ofp, "\n");
   }

}  /* end of output_mast_matrix */
/*=======================================================================*/
void pseudo_alts(col, qij, epsilon)
     struct working *col;
     struct float_qij *qij;
     double epsilon;
{
  int aa, row;
  
  /*---------- get the pseudo counts -------------------------------*/
  for (aa=1; aa < AAS; aa++) {
    col->reg[aa] = 0.0;
    for (row = 1; row < AAS; row++) {
      col->reg[aa] += (col->cnt[row] * qij->value[aa][row] / qij->marg[row]);
    }
    col->reg[aa] *= epsilon;
    if (col->totcnt > 0.0) col->reg[aa] /= col->totcnt;
    col->totreg += col->reg[aa];
  }
}  /* end of pseudo_alts */
/*=====================================================================*/
int count_residues(col)
     struct working *col;
{
  int aa, nr;

  nr = 0;
  for (aa = 1; aa < AAS; aa++) {
    if (col->cnt[aa] > 0.0) nr++;
  }

  return nr;
}  /*  end of count_residues */

