/*  Copyright 2000-2006 Fred Hutchinson Cancer Research Center
    addseqs.c   Reads a file of blocks and a sequence not in the
			blocks.
		  Aligns the sequence to the blocks.

           addseqs <in_blocks_file> <in_seq_file> <out_file> [<blks|seqs|simi>]

blksonly: (for protomat) Align sequences with blocks & add them to
	  blocks only if ALL are in order and ALL score above a cutoff.
          Use to output blocks with sequences added to all blocks.

For seqsonly & simionly, all <in_seq_file> sequences are added to the blocks,
but then assembled & out of order segments flagged:
seqsonly: Align sequences with blocks and print out <in_seq_file> sequences
	  from min block pos to max block pos in fasta format.
          Use to output sequences added to some blocks,
	  not necessarily to all.
          
simionly: Assumes only one sequence in <in_seq_file>; it will be
	  added to the blocks scoring above cutoff and in order.
          Use to output blocks with sequences added to some blocks,
	  not necessarily to all.
--------------------------------------------------------------------
 9/30/99 J. Henikoff
11/29/99 Put in btest for tree_add.csh
12/15/99.1 Added Version.
 3/13/00.1 List blocks to which sequence was added if simionly
 3/22/00.1 Added compute_dist(); distance matrix calculation
 5/ 2/00.1 Don't add seqs if they're off the ends of the blocks (blksonly)
		or are documented FRAGMENTs
10/ 1/02.1 Fixed bug checking sequence names. More output info.
10/11/02.1 Allow 1 aa overlap between adjacent blocks
10/11/02.2 Pad if starts before first block or ends after last block
 6/ 8/03.1 Compare multi-part sequence names - compare_names()
 1/ 2/06.1 Match Q9KWE2 with Q9KWE2_AGRRH, etc. - compare_names()
====================================================================*/

#define ADDSEQS_C_
#define EXTERN
#define MAXNAME 80	/* Maximum file name length */
#define MAXWIDTH 60     /* Max block width */
#define MAXAA 26	/* alphabet size */
#define AASALL 26	/* alphabet size */
#define AAS 21	
#define MAXWIDTH 60
#define CUTOFF 800	/* Min. calibrated cutoff score */
#define MAXSEQ 1000

/*		Stuff for pssmdist()   */
#define TPS 100			/* number of known TPs */
#define Search 185371		/* number of seqs in database */
#define SearchAA 58639837	/* number of aas in database */
#define MAXSCORE 6000

#include <blocksprogs.h>

struct blocks_list {
   int nblock;		/* number of blocks in family */
   int nseq;		/* number of seqs originally in blocks */
   int nadd;		/* number of seqs added to blocks */
   int mindist;		/* min dist from last block */
   int maxdist;		/* max dist from last block */
   int bestpos;		/* for current sequence */
   int endpos;		/* for current sequence */
   int bestscore;	/* for current sequence */
   Block *block;
   Matrix *pssm, *pssm_frq;
   struct blocks_list *next;
};

struct seqseq {		/* to reorder sequences */
   int seq, minseq, clump;
   double maxscore, minscore;
};

struct btemp {		/* sort hit structure */
   double score;
   int bnum;			/* order of blist->block in family */
   struct blocks_list *blist;
};

void addseq();
void assemble_hit();
void best_pos();
void scale_weights();
void fix_ac();
void add_cluster();
void write_seq();
void order_seq();
int btempcmp();
void compute_dist();
int compare_names();

/*		List of blocks routines  */
struct blocks_list *make_blist();
void insert_blist();
void free_blist();
/*		Block calibration routine */
struct score_struct {
	double ways, prob;
};
int pssmdist();

char Version[12] = " 1/ 2/06.1"; 		/* Version number */
int NumSeqs, TN995, TPabove, CutOff;
double QMax;				/* Max possible query similarity */

/*=======================================================================*/
/*
 * main
 */

int main(argc, argv)
     int argc;
     char *argv[];
{
   FILE *fseq, *fblk, *fout, *fqij, *fdat;
   Block *block;
   Sequence *sequence;
   struct blocks_list *blist, *bcur, *bprev;
   char frqname[MAXNAME], qijname[MAXNAME], siminame[MAXNAME];
   char infile[MAXNAME], outfile[MAXNAME], seqsfile[MAXNAME];
   char  *blimps_dir;
   int i, j, not_in, in_order, above_cut, endpos;
   int fragment, iblock, pad1, pad2;
   int maxpos, minpos;
   int seqsonly, simionly, blksonly;

   ErrorLevelReport = 2;	/* 5 for No blimps errors */

   printf("ADDSEQS Version %s\n", Version);
   if (argc < 3)
   {
      printf("ADDSEQS: Copyright 1999 by the Fred Hutchinson Cancer");
      printf(" Research Center\n");
      printf("Adds sequences to blocks.\n");
      printf("USAGE:  addseqs <in_block_file> <seqs_file> <out_blocks_file>\n");
      printf("                <in_block_file> = input blocks\n");
      printf("		      <seqs_file>     = fasta format sequences\n");
      printf("                <out_file>      = output file\n");
      printf("                <blks|seqs|simi>= processing option\n");
      printf("                   blks = add sequences to blocks & output blocks\n");
      printf("                   seqs = add sequences to blocks & output sequences\n");
      printf("                   simi = add sequences to blocks & do similarity analysis\n");
   }
 
/*----------------Get the input blocks------------------------------*/
   if (argc > 1)
      strcpy(infile, argv[1]);
   else
   {
      printf("\nEnter name of file containing blocks: ");
      gets(infile);
   }
   if ( (fblk=fopen(infile, "r")) == NULL)
   {
      printf("\nCannot open file %s\n", infile);
      exit(-1);
   }

/* ------------2nd arg = sequence file -----------------------*/
   if (argc > 2)
      strcpy(seqsfile, argv[2]);
   else
   {
      printf("\nEnter name of sequence file: ");
      gets(seqsfile);
   }
   if ( (fseq=fopen(seqsfile, "r")) == NULL)
   {
      printf("\nCannot open file %s\n", seqsfile);
      exit(-1);
   }
   /* ------------3rd arg = output seqs -----------------------*/
   if (argc > 3)
      strcpy(outfile, argv[3]);
   else
   {
      printf("\nEnter name of output seqs file: ");
      gets(outfile);
   }
   if ( (fout=fopen(outfile, "w")) == NULL)
   {
      printf("\nCannot open file %s\n", outfile);
      exit(-1);
   }
   /* ------------4th arg = optional processing----------------*/
   blksonly = YES; seqsonly = NO; simionly = NO;
   if (argc > 4)
   {
      if (strcasecmp(argv[4], "seqs") == 0)
      {
         blksonly = NO; seqsonly = YES; simionly = NO;
      }
      else if (strcasecmp(argv[4], "simi") == 0)
      {
         blksonly = NO; seqsonly = NO; simionly = YES;
      }
   }
   CutOff = CUTOFF;
/*
   if (blksonly) CutOff = CUTOFF;
   else          CutOff = 0;
*/

   /*--------- Get the information to make PSSMs ---------------------*/
   blimps_dir = getenv("BLIMPS_DIR");
   frqname[0] = '\0';
   if (blimps_dir != NULL) sprintf(frqname, "%s/docs/", blimps_dir);
   strcat(frqname, "default.amino.frq");
   /*  Creates global array frequency[]  */
   load_frequencies(frqname);
   qijname[0] = '\0';
   if (blimps_dir != NULL) sprintf(qijname, "%s/docs/", blimps_dir);
   strcat(qijname, "default.qij");
   Qij = NULL;
   if ( (fqij=fopen(qijname, "r")) != NULL) Qij = load_qij(fqij);
   fclose(fqij);
   RTot = LOCAL_QIJ_RTOT;

   /*=======================================================================*/

   /*  Read all of the blocks into memory  */
   blist = make_blist();
   while ((block = read_a_block(fblk)) != NULL)
   {
       pb_weights(block);		  /* add position-based weights */
       scale_weights(block, 1);	           /* weights add to # of seqs */
       insert_blist(blist, block);	 /* also computes PSSMs & calibrates */
   }
   fclose(fblk);
   if (blist->nblock == 0)
   {
      printf("No blocks found in %s .\n", infile);
      exit(-1);
   }

   /*======================================================================*/
   /*---- Process each block for every sequence ---------------------------*/
   NumSeqs = 0;
   while (!feof(fseq) &&
      (sequence = read_a_sequence(fseq, FASTA, AA_SEQ)) != NULL)
   {
     NumSeqs += 1;
     fragment = FALSE;
     if (strstr(sequence->info, "FRAGMENT") != NULL) fragment = TRUE;
     /*---------------Process each block---------------------------------*/
     /*   Assumes all the blocks are from the same family!  */
     bcur = blist->next;
     minpos = 9999; maxpos = -9999;
     endpos = -9999;	/* first block is never out of order */
     not_in = in_order = above_cut = TRUE;
     iblock = pad1 = pad2 = 0;
     while (bcur != NULL && bcur->block != NULL)
     {
         iblock++;
         for (i = 0; i < bcur->block->num_sequences; i++)
         {
           if ((compare_names(sequence->name, bcur->block->sequences[i].name)) == 1)
           {
                printf("%s already in block\n", sequence->name), 
                not_in = FALSE;
           }
         }
         if (not_in)		/* sequence isn't in block */
         {
           /* Get best position & score for this block in this sequence */
	   bcur->bestpos = bcur->endpos = bcur->bestscore = -999; 
           best_pos(bcur, sequence);

           if (bcur->bestscore < CutOff) above_cut = FALSE;
           /*  Allow overlap of 1, motomat does?  */
           if (bcur->bestpos < (endpos-1))
           {
               printf(" out of order %d, previous end %d\n", 
		       bcur->bestpos, endpos);
               in_order = FALSE;
           }
           else
           {  endpos = bcur->bestpos + bcur->block->width; }

           /*>>>>pad seq with Xs if this is first or last block <<<*/
           if (bcur->bestpos < 0 && iblock == 1)
           {
              pad1 = 0 - bcur->bestpos;
              printf(" pad1=%d bestpos=%d endpos=%d seqlen=%d\n",
		       pad1, bcur->bestpos, endpos, sequence->length);
           }
           if (iblock == bcur->nblock && endpos > sequence->length)
           {
              printf(" pad2=%d bestpos=%d endpos=%d seqlen=%d\n",
		       pad2, bcur->bestpos, endpos, sequence->length); 
              pad2 = endpos - sequence->length;
           }
           if ( (iblock > 1 && bcur->bestpos < 0)  ||
                (iblock < bcur->nblock &&  endpos > sequence->length) )
           {
                printf(" bad bestpos=%d endpos=%d seqlen=%d\n",
			bcur->bestpos, endpos, sequence->length);
		fragment = TRUE;
           }

           if (bcur->bestpos < minpos) minpos = bcur->bestpos;
	   if ((bcur->bestpos + bcur->block->width) > maxpos)
		maxpos = bcur->bestpos + bcur->block->width - 1;
         }  /* end of not-in */
         bcur = bcur->next;
     }  /* end of block  */

     /*------Now all blocks have been examined for this sequence-----------*/
     /*  Add this sequence to the block, unless it's already in or
		blocks aren't in order or score is too low   */
     if ( (!blksonly && not_in) ||
           (blksonly && not_in && !fragment && in_order && above_cut) )
     {
        bcur = blist->next; bprev = NULL;
        while (bcur != NULL && bcur->block != NULL)
        {
            addseq(bprev, bcur, sequence);
            if (!blist->nadd) add_cluster(bcur->block);
	    bprev = bcur;
            bcur = bcur->next;
        }
        blist->nadd += 1;	/* #seqs added to all blocks */
        /*  Check to see if blocks are in order for sequence just added:
            flags block->undefined & block->sequences[].undefined */
        if (!blksonly && blist->nblock > 1)
           assemble_hit(blist, &minpos, &maxpos);
     }
     if (seqsonly)  
     {
        write_seq(fout, sequence, minpos, maxpos);
     }

     /*  save the name of the last sequence added */
     if (simionly) strcpy(siminame, sequence->name);
     free_sequence(sequence);
   }  /* end of sequence */

   if (NumSeqs == 0)
   {
      printf("No sequences found in %s .\n", seqsfile);
      exit(-1);
   }
   fclose(fseq);
   printf("\n");

   /*-------------Add sequence weights & output the final blocks----------*/
   /*  if simionly, just skips blocks out of order for last sequence added */
   if (!seqsonly)
   {
      bcur = blist->next;
      while (bcur != NULL && bcur->block != NULL)
      {
         if (blksonly ||
             (simionly && bcur->block->undefined))
         {
            pb_weights(bcur->block);	/* add position-based weights */
            scale_weights(bcur->block, 0);    /* max weight = 100 */
            fix_ac(bcur);
            output_block(bcur->block, fout);
            if (simionly)
            {
		printf("Query added to %s\n", bcur->block->number);
            }
         }
         bcur = bcur->next;
      }  /* end of block  */
   }
   fclose(fout);
   if (!simionly)
      printf("%d sequences added\n", blist->nadd);

   /*----------Append statistics to addseqs.dat----------------------*/
   if (blksonly)
   {
      if ( (fdat=fopen("addseqs.dat", "a")) != NULL)
      {
         fprintf(fdat, "%s %d %d %d %d\n",
              blist->next->block->number, blist->nblock, blist->nseq, 
              blist->next->block->num_sequences, NumSeqs);
         fclose(fdat);
      }
   }

   exit(0);

}  /* end of main */

/*=======================================================================
	Add sequence to the end of the blist block
        blist is current block, bprev is previous block
========================================================================*/
void addseq(bprev, blist, seq)
struct blocks_list *bprev, *blist;
Sequence *seq;
{
   int j, pos, posj, prevdist, clmax, newseq;

/*
      printf("%s: ", blist->block->number);
      printf("Adding %s at position %4d\n", seq->name, pos);
*/
      pos = blist->bestpos;
      if (blist->block->num_sequences + 1 > blist->block->max_sequences)
      {
         resize_block_sequences(blist->block);
         /*  resize() doesn't fix cluster pointers?  */
/*
         blist->block->clusters[0].sequences = &(blist->block->sequences[0]);
*/
      }

      newseq = blist->block->num_sequences;
      /*  Block points to first position */
      blist->block->sequences[newseq].position = pos + 1;
      strcpy(blist->block->sequences[newseq].name, seq->name);
      strcpy(blist->block->sequences[newseq].info, seq->info);
      blist->block->sequences[newseq].undefined = YES; /* block is in seq */
      blist->block->sequences[newseq].max_length =
             blist->block->width;
/*    blist->block->sequences[newseq].length =
             blist->block->width; 
*/
      blist->block->num_sequences += 1;

      clmax = blist->block->num_clusters - 1;
      blist->block->clusters[clmax].num_sequences += 1; 

      /*  Force all sequences into a single cluster */
/*
      blist->block->num_clusters = 1;
      blist->block->clusters[0].num_sequences = blist->block->num_sequences;
      blist->block->clusters[0].sequences = &(blist->block->sequences[0]);
*/

      for (j=0; j< blist->block->width; j++)
      {
         if (blist->bestscore >= CutOff)
         {
            posj = pos + j;
            if (posj >= 0 && posj < seq->length)
               blist->block->sequences[newseq].sequence[j] =
                      seq->sequence[posj];
            else
               blist->block->sequences[newseq].sequence[j] = 23;    /*   X */
         }
         else
         {
            blist->block->sequences[newseq].sequence[j] = 23;    /*   X */
         }
      }

      /*   Update distance from previous block if necessary  */
      prevdist = pos;
      if (bprev != NULL)
      {
         prevdist -= (bprev->block->sequences[newseq].position + bprev->block->width);
      }
      if (prevdist < blist->mindist) blist->mindist = prevdist;
      if (prevdist > blist->maxdist) blist->maxdist = prevdist;
    
}  /* end of addseq */

/*======================================================================
      Scale sequence weights so maximum value is 100
=======================================================================*/
void scale_weights(block, stype)
Block *block;
int stype;
{
   double maxweight, minweight, sumweight, factor;
   int seq;

   sumweight = maxweight = 0.0; minweight = 999999.9;
   for (seq = 0; seq < block->num_sequences; seq++)
   {
      sumweight += block->sequences[seq].weight;
      if (block->sequences[seq].weight > maxweight) 
           maxweight = block->sequences[seq].weight;
      if (block->sequences[seq].weight < minweight) 
           minweight = block->sequences[seq].weight;
   }

   factor = 1.0;
   /*    Force maximum weight to be 100  */
   if (stype == 0)  factor = 100. / maxweight;
   /*    Force sum of weights to be number of sequences */
   else if (stype == 1) factor = (double) block->num_sequences / sumweight;
   /*    Force sum of weights to be stype */
        else if (stype > 1) factor = (double) stype / sumweight;

   for (seq = 0; seq < block->num_sequences; seq++)
   {
      block->sequences[seq].weight *= factor;
   }
}  /* end of scale_weights */

/*=======================================================================
            Score all alignments of matrix with sequence & return
	     position of best alignment
============================================================================*/
void best_pos(blist, seq)
struct blocks_list *blist;
Sequence *seq;
{
  int scan_pos, seq_pos, mat_pos, best_position;
  double seq_score, max_seq_score;

  max_seq_score = 0.0;
  
  /* for every alignment of the matrix and sequence score the alignment. */
  /* there are (seq_length + matrix_length  - 1) different alignments. */
  /* Note: seq_pos is the relative position of the 1st matrix column to the */
  /*       1st sequence column */
  /* Note: the indexing is shifted to make the calculation of scan_pos */
  /*       (see below) easier/faster */
  for (seq_pos= -blist->pssm->width+1; seq_pos < seq->length; seq_pos++) {


    /* 
     * score this alignment
     */
    seq_score = 0.0;

    /* for each position in the matrix add the weight to the total score */
    /* Note: mat_pos is the current column of the matrix */
    for (mat_pos=0; mat_pos < blist->pssm->width; mat_pos++) {

      /* make sure the sequence and the matrix overlap at this point */
      /* Note: scan_pos is where the current matrix column is in the */
      /*       sequence */
      scan_pos = seq_pos + mat_pos;
      if ((scan_pos >= 0) && (scan_pos < seq->length)) { /* if in the seq */
	seq_score += blist->pssm->weights[seq->sequence[scan_pos]][mat_pos];
      }
      else {			/* not in the sequence */
	seq_score += blist->pssm->weights[aa_atob['-']][mat_pos]; 
	/* score as if it was a gap */
      }

    } /* end score this alignment */

    
      if (seq_score > max_seq_score) {
	max_seq_score = seq_score;
	best_position = seq_pos;
      }
    
  } /* end of pairwise lineup */
  
  /*  Normalize score */
  if (blist->pssm->percentile > 0)
  {
     printf("%s %s calibrated ", seq->name, blist->block->number);
     max_seq_score *= 1000.0;
     max_seq_score /= (double) blist->pssm->percentile;
  }
  blist->bestpos = best_position;
  blist->endpos = best_position + blist->block->width - 1;
  blist->bestscore = round(max_seq_score);
  printf("best score = %5d at %d\n", blist->bestscore, blist->bestpos);

}  /* end of best_pos */

/*=======================================================================
     routines for a list of blocks
========================================================================*/
struct blocks_list *make_blist()
{
   struct blocks_list *new;
   
   new = (struct blocks_list *) malloc (sizeof(struct blocks_list));
   new->nblock = new->nseq = new->nadd = 0;
   new->bestscore = new->bestpos = new->maxdist = -9999;
   new->mindist = 9999;
   new->block = NULL;
   new->next = NULL;

   return(new);
}  /* end of make_blist */

/*=======================================================================
  PSSMs are computed & block calibrated here. The 99.5
  and strength values are not separate fields in the block record, but
  are extra fields stored in the PSSM record.
=======================================================================*/
void insert_blist(blist, block)
struct blocks_list *blist;
Block *block;
{
   struct blocks_list *cur;
   char *ptr, ctemp[80];

   cur = blist;
   while (cur->next != NULL) cur = cur->next;
   cur->next = make_blist();
   cur->next->block = block;

   cur->next->pssm = block_to_matrix(block, 3);		/* log odds  PSSM */
   cur->next->pssm_frq = block_to_matrix(block, 2);	/* frequency PSSM */

   /*   Get min & max distances:
	block->ac = "PS00094A; distance from previous block=(0,1141)"  */
   strcpy(ctemp, block->ac);
   ptr = strtok(ctemp, "(");
   if (ptr != NULL)
   { 
      ptr = strtok(NULL, ",");
      if (ptr != NULL)
      {
          cur->next->mindist = atoi(ptr);
          ptr = strtok(NULL, ")");
          if (ptr != NULL)
          { cur->next->maxdist = atoi(ptr);}
      }
   }

   /*  Calibrate the block if it isn't already & add to block->bl line */
   if (cur->next->pssm->percentile <= 0)
   {
      cur->next->pssm->percentile = pssmdist(cur->next->pssm, 0, frequency, NULL);
      block->percentile = cur->next->pssm->percentile;
/*
      ptr = strtok(block->bl, "\n\r");
      sprintf(block->bl, "%s; 99.5%%=%d", ptr, cur->next->pssm->percentile);
*/
   }
   if (cur->next->pssm->strength <= 0)
   {
      cur->next->pssm->strength = pssmdist(cur->next->pssm, 1, frequency, cur->next->pssm_frq);
      block->strength = cur->next->pssm->strength;
/*
      ptr = strtok(block->bl, "\n\r");
      sprintf(block->bl, "%s; strength=%d", ptr, cur->next->pssm->strength);
*/
   }

   /*  Update the list totals */
   blist->nblock += 1;
   blist->nseq = block->num_sequences;
 
}  /* end of insert_blist */

/*=======================================================================*/
void free_blist(blist)
struct blocks_list *blist;
{
   struct blocks_list *cur, *last;
   cur = last = blist;
   while (cur->next != NULL)
   {
      last = cur;  cur = cur->next;
   }
   if (cur != blist)
   {
      free(cur);
      last->next = NULL;
      free_blist(last);
   }
   else free(blist);

}  /* end of free_blist */

/*=======================================================================
    ftype == 0 => use freqs (TN distribution)
    ftype == 1 => use obs_freqs (TP distribution)
========================================================================*/
int pssmdist(matrix, ftype, freqs, obs_freqs)
Matrix *matrix, *obs_freqs;
int ftype;
double *freqs;
{
  struct score_struct *last, *this, ends[MAXSCORE];
  struct score_struct middle[MAXSCORE], scores[2][MAXSCORE];
  int col, aa, minvalue, maxvalue, minscore, maxscore, mincol, maxcol;
  int x, score, minfirst, minlast;
  double cum, aligns, report, ftemp, dtemp, probwt[MAXWIDTH];

  /*   compute which score to report */
  if (ftype == 1) report = (double) 0.5 * TPS;		/* median TP */
  else            report = (double) 0.005 * Search;	/* 99.5% TN  */
  minscore = maxscore = 0;
  maxvalue = -1;		/* assumes no negative scores */
  minvalue = 9999;
  probwt[0] = 20.0;
  for (col = 0; col < matrix->width; col++)
  {
     /*  Intialize probability weights with powers of 20 */
     if (col > 0) probwt[col] = probwt[col - 1] * 20.0;
     mincol = 9999; maxcol = -1;
/*   for (aa = 0; aa < AASALL; aa++) */
     for (aa = 1; aa <= 20; aa++)
     {
        if (matrix->weights[aa][col] > maxvalue)
            maxvalue = matrix->weights[aa][col];
	if (matrix->weights[aa][col] < minvalue)
	    minvalue = matrix->weights[aa][col];
        if (matrix->weights[aa][col] > maxcol)
            maxcol = matrix->weights[aa][col];
	if (matrix->weights[aa][col] < mincol)
	    mincol = matrix->weights[aa][col];
     }
     maxscore += maxcol;
     minscore += mincol;
     if (col == 0) minfirst = mincol;
     if (col == (matrix->width - 1) ) minlast = mincol;
   }
   /*    Probability weights: weights applied to probs depending
         on how many columns of the block are aligned, the
         full alignment width gets about 19/21 = .905, alignments
         off either end of the sequence get the remainder */
   dtemp = probwt[matrix->width - 1] * 21.0 - 40.0;
   cum = 0.0;
   for (col = 0; col < matrix->width; col++)
   {
      probwt[col] = probwt[col] * 19.0 / dtemp;
      cum += probwt[col];
      if (col < matrix->width - 1) cum += probwt[col];
   }
   /* ---- Min. score could be just first or last column ----*/
   if (minfirst < minscore) minscore = minfirst;
   if (minlast < minscore)  minscore = minlast;
/*
   if (ftype == 1) printf("\nTrue positive distribution\n");
   else            printf("\nRandom distribution\n");
   printf("minscore without ends=%d\n", minscore);
   printf("minvalue=%d maxvalue=%d minscore=%d maxscore=%d\n",
	   minvalue, maxvalue, minscore, maxscore);
*/
   /*-----------------------------------------------------------------*/
   if (maxscore > MAXSCORE) maxscore = MAXSCORE - 1;
   last = scores[0]; this = scores[1];
   for (x = minvalue; x <= maxscore; x++)
   {  last[x].ways = last[x].prob = this[x].ways = this[x].prob = 0.0; 
      ends[x].ways = ends[x].prob = middle[x].ways = middle[x].prob = 0.0; }

   /*---------Initialize from first column -------------------------------*/
   /*   use obs_freqs->weights[aa][col] instead of freqs[aa] for TP probs */
   col = 0;
/* for (aa=0; aa < AASALL; aa++) */
   for (aa=1; aa <= 20; aa++)
   {
      x = matrix->weights[aa][col];
      last[x].ways += 1.0;
      if (ftype == 1) ftemp = obs_freqs->weights[aa][col] / 100.;
      else            ftemp = freqs[aa];
      last[x].prob += ftemp;   
   }

   /*---- Now enumerate all possible scores, expanding one column
          at a time ----------*/
   for (col=1; col < matrix->width; col++)
   {
      /*--------- Save the alignments hanging off the left end ------*/
      /*    There are currently col+1 columns of the block aligned  */
      for (x=minvalue; x <= maxscore; x++)
      {
            ends[x].ways += last[x].ways;
            ends[x].prob += last[x].prob * probwt[col - 1];
      }
/*    for (aa=0; aa < AASALL; aa++) */
      for (aa=1; aa <= 20; aa++)
      {
         for (x=minvalue; x <= maxscore; x++)
         {
            if (last[x].ways > 0)
            {
               score = x + matrix->weights[aa][col];
               this[score].ways += last[x].ways;
               if (ftype == 1) ftemp = obs_freqs->weights[aa][col] / 100.;
               else            ftemp = freqs[aa];
               this[score].prob += last[x].prob * ftemp;
            }
         } /* end of score x */
      }  /* end of aa */
      /*---------   Switch the arrays ------------------------------*/
      if (this == scores[1])
      {  last = scores[1]; this = scores[0]; }
      else
      {  last = scores[0]; this = scores[1]; }
      for (x = minvalue; x <= maxscore; x++)
      {  this[x].ways = this[x].prob = 0.0;  }
   }  /* end of col */

   /*-------- last now has the final counts of all combinations
      of column scores for full blocks, and ends has the counts
      for alignments off the left end. Still have to get counts
      for alignments off the right end and need two arrays to do
      it. So have to keep last results in another array--------*/
   for (x=minvalue; x <= maxscore; x++)
   {
	middle[x].ways = last[x].ways;
	middle[x].prob = last[x].prob * probwt[matrix->width - 1];
	last[x].ways = last[x].prob = 0.0;
   }

   /*-------- Get the alignments hanging off the right end -------*/
   /*    Initialize with last column   */
   col = matrix->width - 1;
   for (aa=1; aa <= 20; aa++)
   {
      x = matrix->weights[aa][col];
      last[x].ways += 1.0;
      if (ftype == 1) ftemp = obs_freqs->weights[aa][col] / 100.;
      else            ftemp = freqs[aa];
      last[x].prob += ftemp;   
   }
   for (col = matrix->width - 2; col >= 1; col--)
   {
      /*--------- Save the gapped alignments off the right end ------*/
      /*  There are currently length-col columns of the block aligned */
      for (x=minvalue; x <= maxscore; x++)
      {
            ends[x].ways += last[x].ways;
            ends[x].prob += last[x].prob * probwt[matrix->width - col - 2];
      }
      for (aa=1; aa <= 20; aa++)
      {
         for (x=minvalue; x <= maxscore; x++)
         {
            if (last[x].ways > 0)
            {
               score = x + matrix->weights[aa][col];
               this[score].ways += last[x].ways;
               if (ftype == 1) ftemp = obs_freqs->weights[aa][col] / 100.;
               else            ftemp = freqs[aa];
               this[score].prob += last[x].prob * ftemp;
            }
         } /* end of score x */
      }  /* end of aa */
      /*---------   Switch the arrays ------------------------------*/
      if (this == scores[1])
      {  last = scores[1]; this = scores[0]; }
      else
      {  last = scores[0]; this = scores[1]; }
      for (x = minvalue; x <= maxscore; x++)
      {  this[x].ways = this[x].prob = 0.0;  }
   }  /* end of column */
   /*--------- Save the gapped alignments off the right end ------*/
   /*  Need to get the length - 1 counts from the right   */
   for (x=minvalue; x <= maxscore; x++)
   {
         ends[x].ways += last[x].ways;
         ends[x].prob += last[x].prob * probwt[matrix->width - 2];
   } 

   /*   number of alignments done in a hypothetical search   */
   /*   Always assume one alignment per TP sequence          */
   if (ftype == 1) aligns = TPS;
   else aligns = (double) SearchAA + Search * (matrix->width - 1); 

   /* Count the TP scores above 99.5% of TN scores, x is the score */
   if (ftype == 1 && TN995 > 0)
   {
      x = maxscore; TPabove = 0.0;
      while (x > TN995)
      {
         if (middle[x].prob > 0.0 || ends[x].prob > 0.0)
         {
            TPabove += (middle[x].prob + ends[x].prob) * aligns;
         }
         x--;
      }
   }

   /* Print the top scores, x is the score */
   x = maxscore; cum = 0.0;
/*
   printf("\nCum      Score   Expected\n");
*/
   while (cum < report && x >= 0)
   {
       if (middle[x].prob > 0.0 || ends[x].prob > 0.0)
       {
/*
          if (cum > 100.0)
             printf("%8.4f %4d %8.4f\n", 
		cum, x, (middle[x].prob + ends[x].prob) * aligns);
*/
          cum += (middle[x].prob + ends[x].prob) * aligns;
       }
       x--;
   }
   if (ftype == 1) printf("Median TP score = %d, TPabove = %d\n",
                           x, TPabove);
   else            printf("99.5 TN score = %d\n", x);
   return(x);
}  /* end of pssmdist */
/*================================================================
    fix_ac() updates distances on the block->ac line
	block->ac = "PS00094A; distance from previous block=(0,1141)"
================================================================*/
void fix_ac(blist)
struct blocks_list *blist;
{
   sprintf(blist->block->ac, "%s; distance from previous block=(%d,%d)",
           blist->block->number, blist->mindist, blist->maxdist);
} /* end of fix_ac */
/*=====================================================================
    Add a cluster pointing to the last sequence in the block
    Blimps assumes the sequences are in cluster order within the block
=======================================================================*/
void add_cluster(block)
Block *block;
{
   /*  Open up an extra cluster for the additional sequences */
   if (block->num_clusters + 1 > block->max_clusters)
   {
      resize_block_clusters(block);  /* updates block->max_clusters */
   }
   /*  First sequence will have been added to last cluster */
   block->clusters[ block->num_clusters - 1 ].num_sequences -= 1;
   block->clusters[ block->num_clusters ].num_sequences = 1;
   block->clusters[ block->num_clusters ].sequences = 
          &(block->sequences[ block->num_sequences - 1 ]);
   block->num_clusters += 1;
}  /* add_cluster */
/*=====================================================================
   Write sequence to fout in fasta format from inpos to maxpos
=======================================================================*/
void write_seq(fout, seq, minpos, maxpos)
FILE *fout;
Sequence *seq;
int minpos, maxpos;
{
   int min, max, pos;

   min = 9999; max = -9999;

   if ((minpos < 0) || (maxpos > seq->length) || (maxpos < minpos))
   {
	printf("Cannot write %s of length %d from %d to %d\n",
            seq->name, seq->length, minpos, maxpos);
   }
   else
   {
      min = minpos - 10;
      if (minpos < 0) minpos = 0;
      max = maxpos + 10;
      if (maxpos > seq->length) maxpos = seq->length;
      fprintf(fout, ">%s %s from %d to %d\n", 
	seq->name, seq->info, min+1, max+1);
      for (pos=min; pos < max; pos++)
      {
          /*  assuming seq->type == AA_SEQ here ... */
          fprintf(fout, "%c", aa_btoa[seq->sequence[pos]]);
          if ((pos+1)%60 == 0) { fprintf(fout, "\n");  }
      }
      fprintf(fout, "\n");
   }
}  /* end of write_seq  */

/*=======================================================================*/
/*  Sequences may not be in the same order both blocks. If not, then
    set sseq[s1] = s2  where
    b1->sequences[s1].name == b2->sequences[s2].name
=========================================================================*/
void order_seq(sseq, b1, b2)
struct seqseq *sseq;
Block *b1, *b2;
{
   int nseq, i1, i2;

   nseq = b1->num_sequences;
   if (b2->num_sequences < nseq) nseq = b2->num_sequences;
   for (i1 = 0; i1 < nseq; i1++)
   {
      if (b1 == b2) sseq[i1].seq = i1;
      else
      {
         sseq[i1].seq = -1;
         i2 = 0;
         while (sseq[i1].seq < 0 && i2 < nseq)
         {
            if (strcmp(b1->sequences[i1].name, b2->sequences[i2].name) == 0)
               sseq[i1].seq = i2;
            i2++;
         }
      }
   }
}  /*  end of order_seq */

/*=======================================================================
       Indicate which blocks are to be used for this
       sequence; in order, etc. if it matters 
       assemble_hit(blist): look at last sequence, 
        last = blist->block->num_sequences - 1
        look at blist->block->sequences[last].position,
                blist->bestscore, blist->bestpos
	flag    blist->block->sequences[last].undefined
=======================================================================*/
void assemble_hit(blist, minpos, maxpos)
struct blocks_list *blist;
int *minpos, *maxpos;
{
   struct blocks_list *bcur, *bprev;
   struct btemp *btemp;
   int i, j, qseq;

   btemp = (struct btemp *) malloc(blist->nblock * sizeof(struct btemp));

   bcur = blist->next; bprev = NULL;
   i = 0;
   while (bcur != NULL && bcur->block != NULL)
   {
      btemp[i].score = bcur->bestscore;
      btemp[i].bnum = i+1;
      btemp[i].blist = bcur;
      bcur->block->undefined = NO;
      i++;
      bprev = bcur;
      bcur = bcur->next;
   }
   /*	descending sort by maxscore  */
   qsort(btemp, blist->nblock, sizeof(struct btemp), btempcmp);

   /*  Just checking overlap not distance here   */
   btemp[0].blist->block->undefined = YES;		/* anchor block */
   qseq = btemp[0].blist->block->num_sequences - 1;
   for (i=1; i<blist->nblock; i++)
   {
      btemp[i].blist->block->undefined = YES;
      for (j=0; j<i; j++)
      {
         if (btemp[j].blist->block->undefined &&
             ( (btemp[j].blist->bestscore < CutOff) ||
               (btemp[i].bnum < btemp[j].bnum && 
                btemp[i].blist->endpos > btemp[j].blist->bestpos) ||
               (btemp[i].bnum > btemp[j].bnum &&
                btemp[i].blist->bestpos < btemp[j].blist->endpos) ) )
         {
               btemp[i].blist->block->sequences[qseq].undefined = NO;
               btemp[i].blist->block->undefined = NO;
         }
      }
   }
   /*---Not update minpos & maxpos for good blocks---------------*/
   *minpos = *maxpos; *maxpos = -999; 
   bcur = blist->next;
   while (bcur != NULL && bcur->block != NULL)
   {
      if (bcur->block->sequences[qseq].undefined)
      {
         if (bcur->block->sequences[qseq].position < *minpos)
                    *minpos = bcur->block->sequences[qseq].position;
         if (bcur->block->sequences[qseq].position > *maxpos)
                    *maxpos = bcur->block->sequences[qseq].position;
      }
      bcur = bcur->next;
   }

   free(btemp);
}  /* end of assemble_hit */

/*=====================================================================
   btempcmp sorts a btemp structure in descending order by value
=====================================================================*/
int btempcmp(t1, t2)
struct btemp *t1, *t2;
{
   double diff;
  
   diff = t2->score - t1->score;
   if (diff > 0.0) return(1);
   else if (diff < 0.0) return(-1);
   else return(0);

}  /*  end of btempcmp */

/*=========================================================================
???used in addseqs???
Computes and prints distance matrix
"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, meandist, maxscore, dtemp;

   maxdist = -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) 
         {
            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 (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) );

   /*  Print dissim. matrix for SAS, requires full matrix, max linesize=256 */
   /*  Proc varclus requires a covariance matrix as input ... */
   for (s1=0; s1<nseq; s1++)
   {
      fprintf(ofp, "%s ", seqs[s1]->name);
      for (s2=0; s2<nseq; s2++)
      {
	 if (mtype == 1)
            fprintf(ofp, "%.0f ", dist[s1][s2]);
         else if (mtype == 2)
            fprintf(ofp, "%.0f ", maxdist - dist[s1][s2]);
         else if (mtype == 3)
            fprintf(ofp, "%.2f ", dist[s1][s2]);
      if ((s2%25 == 0)) fprintf(ofp, "\n");
      }
      if (s2 == nseq) fprintf(ofp, "\n");
   }


}  /* end of compute_dist */
/*======================================================================
	Compare two multi-part sequence names and return flag if
	any one of them matches
=======================================================================*/
int compare_names(name1, name2)
char *name1, *name2;
{
   int match, n2, i;
   char *ptr1, *ptr2, parts[10][12], ctemp1[120], ctemp2[120];

   match = 0;
  
   /* strtok modified the strings */
   strcpy(ctemp1, name1);
   strcpy(ctemp2, name2);

   /*  strtok only tracks one string at a time, so pre-process name2 */
   /*  max of 10 name parts, 12 chars in length each */
   n2 = 0;
   ptr2 = strtok(ctemp2, "|");
   while (ptr2 != NULL)
   {
      strcpy(parts[n2], ptr2);
      n2++;
      ptr2 = strtok(NULL, "|");
   }

   ptr1 = strtok(ctemp1, "|");
   while (ptr1 != NULL)
   { 
      for (i=0; i<n2; i++)
      {
         if (strcmp(ptr1, parts[i]) == 0) { match = 1; }
         else   /* match Q99XR5 and Q99XR5_STRP1, etc.  */
         {
            /* 1st 6 chars match; 7th char of one is '_'; other is 6 chars  */
            if ((strncmp(ptr1, parts[i], 6) == 0) &&
                ((strlen(ptr1) == 6 && strcspn(parts[i],"_") == 6) ||
                 (strlen(parts[i]) == 6 && strcspn(ptr1,"_") == 6) )) 
            {  match = 1; }
         }
      }
      ptr1 = strtok(NULL, "|");
   }

   /*	Use the longer name if they match */
   if (match==1 && strlen(name1) > strlen(name2))
   {   strcpy(name2, name1);  }
   
   return(match);

}  /* end of compare_names */
