/*    makeblockmap.c  create a block map description from 
                      block families in a blocks DB
      makeblockmap <input blocks file> <output map file> [<input list file>]

      written by Shmuel Pietrokovski.

April 97. Changes made by Ross Morgan-Linial :

          A note printed out that the reported sequence lengths are 
          actually the position of the end of the 'farthest' block on 
          the sequence.

          The 'pseudo' sequence length is taken from position of the 
          'farthest' block - not necessarily the last one.

Apr/May 97. Changes by Shmuel Pietrokovski :

          Block sequence segments scanned for gaps to get correct end 
          coordinate of the block region.

          Using procedure getargs to get arguments. Default input and 
          output as stdin/stdout. removed procedure print_map.

January 98. Changes by Shmuel Pietrokovski :

          Block overlap will not cause program to quit. User is notified 
          and start position of the C' block is modified to follow the end
          of the N' block. 

6/14/99 Change map to start with ">" instead of "#MAP#.
6/23/99 Add correct_lengths code: read in .lis file containing sequence
	names & lengths
2/ 2/00 Allow longer ACs for block_family name
4/ 9/03 It was still sometimes assuming block family name was 7
====================================================================*/

#define EXTERN

#include <blocksprogs.h>
#include "blockmap.h"

#define INP_DFLT_FNAME "stdin"
#define INP_DFLT        stdin
#define OUT_DFLT_FNAME "stdout"
#define OUT_DFLT        stdout
#define DFLT_SRC_FAM_NAME ""

#define USAGE "A program to create block map description(s) from block family(ies) data:\n%s blocks-input_file_name [output_file_name] [-Fblock_family_name] [-Lsequence_length_file]\n"

#define GET_HELP "((input now expected from screen; CTRL-C to quit))\n\n"

#define DEFAULTS "Defaults: in-\"%s\" out-\"%s\" block_family_name-\"%s\"\n\n"

#define COMMENTS "Comments: \"-\" instead of an input/output file name => stdin/stdout\n           The order of the arguments is not important but the\n           first name is for the input file and the second for the output.\n          block_family_name = \"\" : all block families in input\n\n"

/*  routines from protomat.o */
struct db_id *makedbid();
int get_ids();

/*
 * Local variables and data structures
 */

int check_block_fam() ;
blocks_map *makemap() ;
void write_a_map() ;
void free_map() ;
void getargs() ;

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

int main(argc, argv)
     int argc;
     char *argv[];
{
  FILE  *bfp=INP_DFLT, *ofp=OUT_DFLT ;
  FILE *lfp;
  char  bdbname[MAXNAME]=INP_DFLT_FNAME, outname[MAXNAME]=OUT_DFLT_FNAME ;
  char lisname[MAXNAME];
  char  src_fam_name[MAXNAME]=DFLT_SRC_FAM_NAME;
  Block *block, *block_1, **block_fam, **tmp_ptr ;
  int  i1, blocks_num, seqs_num , nlis, aclen;
  int  allocated_blocks = BLOCKS_ALLOC, prcssd_maps = 0, unprcssd_maps = 0 ;
  char family_name[8] ;
  blocks_map *map ;
  struct db_id *ids;

  if (argc == 1) /* show usage and what defaults are used */
     {
     fprintf(stderr, USAGE, argv[0]) ;
     fprintf(stderr, DEFAULTS, bdbname, outname, src_fam_name) ;
     fprintf(stderr, COMMENTS);
     fprintf(stderr, GET_HELP) ;
     }
  else
     getargs(argc,argv,bdbname,outname,src_fam_name,lisname) ;

/* open files */
/* Input */
  if (strcmp(bdbname,INP_DFLT_FNAME) !=0 && (bfp=fopen(bdbname, "r")) == NULL)
     {
     fprintf(stderr, "Cannot open input file %s\n", bdbname);
     exit(-1);
     }
  lfp = NULL;
  if (strlen(lisname) && (lfp=fopen(lisname, "r")) == NULL)
     {
     fprintf(stderr, "Cannot open input file %s\n", lisname);
     }

/* output */
  if (strcmp(outname,OUT_DFLT_FNAME) !=0 && (ofp=fopen(outname, "w")) == NULL)
     {
     fprintf(stderr, "Cannot open output file %s\n", outname);
     exit(-1);
     }

/*-----------------------------------------------------------------*/
  fprintf(stderr, "Input file \"%s\" processed by program \"%s\"\n",
               bdbname, argv[0]) ;

  if (lfp == NULL)
  {
     ids = NULL;
     fprintf(stderr, "Note: The sequence lengths shown are the 'right-most' sequence positions found in the blocks.\n");
  }
  else   /* read in the sequence names & lengths */
  {
      ids = makedbid();
if (ids == NULL) printf("Unable to allocate ids\n");
      nlis = get_ids(lfp, ids);
      fclose(lfp);
  }

/*-----------------------------------------------------------------*/
/* allocate memory for blocks family array - 
   more would be reallocated if needed */
  block_fam = (Block **) calloc(allocated_blocks, sizeof(Block *)) ;

/* read first block in DB */
  if ((block_1 = read_a_block(bfp)) == NULL)
     {
     fprintf(stderr, "Error ! Input file %s does not contain any blocks.\n",
            bdbname) ;
     exit(-2) ;
     }

  block_fam[0] = block_1 ;
  aclen = strlen(block_fam[0]->number) - 1; 
  if (block_fam[0]->number[(int) strlen(block_fam[0]->number)-1] != 'A')
  {   aclen++;  }
  blocks_num = 1 ;

/* read all blocks of blocks DB */
  while ((block = read_a_block(bfp)) != NULL)
     {
     if (strncmp(block->number, block_1->number, aclen) == 0)
        {
        block_fam[blocks_num++] = block ;

                      /* check if more elements needed in block family array */
        if (blocks_num == allocated_blocks)                    /* reallocate */
	   {
           allocated_blocks += BLOCKS_ALLOC ;
           tmp_ptr = (Block **)
                     realloc(block_fam, allocated_blocks*sizeof(Block *)) ;
           block_fam = tmp_ptr ;
	   }
        }
     else              /* all blocks belonging to 1st block's family read in */
        {

        if ( (int)strlen(src_fam_name) == 0 || 
            ((int) strlen(src_fam_name) > 0 && 
             strncmp(src_fam_name,block_1->number, aclen) == 0))
	   {
/* check that the blocks are in consecutive order and 
   how many different sequences they contain */

           if (check_block_fam(block_fam, blocks_num, &seqs_num) != OK) 
              exit (-3) ;

/*           sprintf(family_name, "%s",block_1->number) ; */
           strncmp(family_name, block_1->number, aclen);
           family_name[aclen] = '\0';

           if ((map = makemap(block_fam, blocks_num, seqs_num, ids)) != NULL)
	      {
              write_a_map(map, ofp) ;
              free_map(map) ;
              prcssd_maps++ ;
              }
           else
              { 
              fprintf(stderr,"Couldn't make a block map for block family %s.\n",
                     family_name) ;
              unprcssd_maps++ ;
	      }
           }
                          /* free memory allocated to block structures array */
        for(i1=0; i1<blocks_num; i1++) free_block(block_fam[i1]) ;

        block_1 = block ;

        block_fam[0] = block ;

        blocks_num = 1 ;
	}

     }

  if ( (int)strlen(src_fam_name) == 0 || 
      ((int) strlen(src_fam_name) > 0 && 
       strncmp(src_fam_name,block_1->number, aclen) == 0))
     {
/* check that the blocks are in consecutive order and 
   how many different sequences they contain */
        
     if (check_block_fam(block_fam, blocks_num, &seqs_num) != OK) exit (-3) ;

/*     sprintf(family_name, "%.7s",block_1->number) ; */
     strncmp(family_name, block_1->number, aclen);
     family_name[aclen] = '\0';

     if ((map = makemap(block_fam, blocks_num, seqs_num, ids)) != NULL)
        {
        write_a_map(map, ofp) ;
        free_map(map) ;
        prcssd_maps++ ;
	}
     else 
        {
        fprintf(stderr,"Couldn't make a block map for block family %s.\n\n", 
               family_name) ;
        unprcssd_maps++ ;
        }
     }

                          /* free memory allocated to block structures array */
  for(i1=0; i1<blocks_num; i1++) free_block(block_fam[i1]) ;
   
  fclose(bfp); fclose(ofp);

  if (prcssd_maps == 0 && (int) strlen(src_fam_name) > 0)
     fprintf(stderr,"\nCouldn't find or process block family %s !\n", src_fam_name) ;
  else
     {
     fprintf(stderr,"\n%d block family(ies) processed into maps.", prcssd_maps) ;
     if (unprcssd_maps > 0) 
        fprintf(stderr,"\n%d block family(ies) could not be processed.", unprcssd_maps) ;
     fprintf(stderr,"\nOutput in file %s.\n", outname) ;
     }

  exit(0);

}  /* end of main */


/* check that the blocks are in consecutive order and 
   how many different sequences they contain */

int check_block_fam(block_fam, blocks_num, seqs_num)
Block *block_fam[] ;
int   blocks_num, *seqs_num ;
{
   int i1, i2, i3, i4 ;

/* is the first block's has family suffix A ? */
   if (block_fam[0]->number[(int) strlen(block_fam[0]->number)-1] != 'A')
      {
      fprintf(stderr,"Warning ! The name of the first block in family %s\n",
             block_fam[0]->number) ;
      fprintf(stderr,"does not end with an \"A\".\n") ;
      }

/* does the suffix of each block follow the suffix of the preceding block ? */

   for(i1=1; i1<blocks_num; i1++)
      if (block_fam[i1]->number[(int) strlen(block_fam[i1]->number)-1] -
          block_fam[i1-1]->number[(int) strlen(block_fam[i1-1]->number)-1]
          != 1) 
         {
         fprintf(stderr,
            "Error ! Block %s is not the block expected to follow (by name)\n",
                block_fam[i1]->number ) ;
         fprintf(stderr,"block %s.\n", block_fam[i1-1]->number) ;
         return(ERROR) ;
	 }


   *seqs_num = block_fam[0]->num_sequences ;

/* in case some blocks include sequences not found in the first block : */
/* this is not allowed in the Blocks DB, where each sequence must contain
   all the blocks, but this is possible in general */

   for(i1=1; i1<blocks_num; i1++)    /* go through all blocks except 1st one */
                                                 /* go through all sequences */
      for(i2=0; i2<block_fam[i1]->num_sequences; i2++) 
         {
                 /* compare sequence name to all the ones in previous blocks */
      /* this is inefficient but avoids the need to store the sequence names */
         for(i3=0; i3<i1; i3++)            /* go through each previous block */
	    {
            for(i4=0; i4<block_fam[i3]->num_sequences &&
                      strcmp(block_fam[i1]->sequences[i2].name,
                             block_fam[i3]->sequences[i4].name) != 0; i4++) ;

         /* if the sequence name was equal to any sequence name in THIS block 
            no need to check any other blocks */
            if (i4 < block_fam[i3]->num_sequences) break ;
            }

 /* check if no break occurred i.e. sequence name was different from all 
     those it was compared to */
         if (i3 == i1) (*seqs_num)++ ;
         }

   for(i1=0; i1<blocks_num && block_fam[i1]->num_sequences == *seqs_num; 
       i1++) ;
   if (i1 != blocks_num)
      fprintf(stderr,
          "Note - Not all sequences in family %s contain all the blocks.\n",
             block_fam[0]->number) ;

   return(OK) ;

} /* end of check_block_fam */

blocks_map *makemap(block_fam, blocks_num, seqs_num, ids)
Block *block_fam[] ;
int blocks_num, seqs_num ;
struct db_id *ids;
{
   blocks_map *map ;
   int i1, i2, i3, i4, gaps ;
   char *blk_sfx ;
   struct db_id *did;


/* allocate memory to blocks_map structure */

                                                 /* memory for map structure */
  map = (blocks_map *) malloc(sizeof(blocks_map)) ;

                                    /* memory for sequences in map structure */
  map->seq_map = (sequence_map *) calloc(seqs_num, sizeof(sequence_map)) ;

                          /* memory for blocks in sequences in map structure */
  for(i1=0; i1<seqs_num; i1++)
       map->seq_map[i1].blocks = 
                        (block_pos *) calloc(blocks_num, sizeof(block_pos)) ;
 
/* allocate memory to block suffices array */

  blk_sfx = (char *) calloc(blocks_num, sizeof(char)) ;

  sprintf(map->block_family, "%s", block_fam[0]->family) ;

  strncpy(map->description, block_fam[0]->de, SMALL_BUFF_LENGTH) ;

  strncpy(map->id, get_token(block_fam[0]->id), SMALL_BUFF_LENGTH) ;

                                      /* strip possible ";" at end of string */
  if (map->id[(int) strlen(map->id)-1] == ';')
     map->id[(int) strlen(map->id)-1] = '\0' ;

  map->num_seqs = seqs_num ;

  map->tot_num_blocks = blocks_num ;

/* get block suffices */
  for (i1=0; i1<blocks_num; i1++) 
      {
      blk_sfx[i1] = 
                 block_fam[i1]->number[(int) strlen(block_fam[i1]->number)-1] ;

      if (!isalpha(blk_sfx[i1]) && blocks_num == 1) blk_sfx[i1] = 'A' ;
      }

/* get sequence names */
  for (i1=0; i1<block_fam[0]->num_sequences; i1++) 
      strncpy(map->seq_map[i1].seq_name, block_fam[0]->sequences[i1].name, 
              SMALL_BUFF_LENGTH) ;

/* If not all blocks contain all sequences it is possible that the first block
   doesn't have all the sequence names. If so keep looking for them in the
   rest of the blocks. */
  if (block_fam[0]->num_sequences < seqs_num) 
     {
     i1 = block_fam[0]->num_sequences ;

     for(i2=1; i2<blocks_num; i2++)  /* go through all blocks except 1st one */
                                                 /* go through all sequences */
         for(i3=0; i3<block_fam[i2]->num_sequences; i3++) 
	    {
               /* compare sequence name to all the ones in the map structure */
            for(i4=0; i4<i1 && 
                strcmp(block_fam[i2]->sequences[i3].name,
                       map->seq_map[i4].seq_name) != 0; i4++) ;

          /* is sequence name equal to any sequence name in map structure ? */
            if (i4 == i1) strncpy(map->seq_map[i1++].seq_name,
                                  block_fam[i2]->sequences[i3].name,
                                  SMALL_BUFF_LENGTH) ;
	    }

     if (i1 != seqs_num) /* not all sequence found */
        {
        fprintf(stderr,"Error ! Couldn't find all %d sequences.\n", seqs_num) ;
        free_map(map) ;
        free(blk_sfx) ;
        return(NULL) ;
	}
     }
/** sort sequence names - makes it easier to compare different  maps of same sequences **/

/* for each sequence get the suffices and positions of all blocks in it */

  for(i1=0; i1<seqs_num; i1++) /* for every sequence - */
     {
     map->seq_map[i1].num_blocks = 0 ;
     for(i2=0; i2<blocks_num; i2++) /* go through every block - */
        {
        for(i3=0; i3<seqs_num; i3++) /* find the sequence */
           if (strcmp(map->seq_map[i1].seq_name, 
                      block_fam[i2]->sequences[i3].name) == 0) break ;
        if (i3 == seqs_num) 
           fprintf(stderr,
              "Note - Sequence %s in family %s does not contain block %c.\n",
                  map->seq_map[i1].seq_name, map->block_family, blk_sfx[i2]) ;
        else
           {
           map->seq_map[i1].blocks[map->seq_map[i1].num_blocks].code = 
                                     blk_sfx[i2] ;

/* check if first block starts before position 1. 
   Do it here so that the sequence can be examined if this is so */
           if (map->seq_map[i1].num_blocks == 0 &&
               block_fam[i2]->sequences[i3].position < 1) 
	       {
                                     /* this is OK only if the aa < 1 are Xs */
               for(i4=0; i4<(1-block_fam[i2]->sequences[i3].position) && 
                   block_fam[i2]->sequences[i3].sequence[i4] == aa_atob['X'];
                   i4++) ;
               if (i4 != 1 - block_fam[i2]->sequences[i3].position)
		  {
                  fprintf(stderr,
                   "Note, first block (%c %d-%d) in sequence %s of family %s\n\
begins BEFORE the first aa of the sequence !\n",
                         map->seq_map[i1].blocks[0].code,
                         block_fam[i2]->sequences[i3].position,
                         block_fam[i2]->sequences[i3].position +
                         block_fam[i2]->sequences[i3].length - 1,
                         map->seq_map[i1].seq_name, map->block_family) ;
/* This was not made an error because of the inteins - they are "imbeded"
   inside other proteins and their first and last blocks include aa in this
   flanking protein sequence 
                  free_map(map) ;
                  free(blk_sfx) ;
                  return(NULL) ;
*/
  	          }
/*
               else
*/
                   /* the begining of the block is missing from the sequence */
               map->seq_map[i1].blocks[map->seq_map[i1].num_blocks].start = 1 ;
	       }
           else
               map->seq_map[i1].blocks[map->seq_map[i1].num_blocks].start = 
                                     block_fam[i2]->sequences[i3].position ;

                      /* check how many gaps (if at all) are in the sequence
                         segment in this block */
                      /* this will be used to adjust the length of the block */

           for(i4=0, gaps=0; i4<block_fam[i2]->sequences[i3].length; i4++)
              if (block_fam[i2]->sequences[i3].sequence[i4] == aa_atob['-']) gaps++;



/*if (gaps !=0 )
printf("%d gaps in block %c of %s\n", 
gaps, map->seq_map[i1].blocks[map->seq_map[i1].num_blocks].code, map->seq_map[i1].seq_name) ;*/

           /* find end of block position, taking into account possible gaps. */

           map->seq_map[i1].blocks[map->seq_map[i1].num_blocks].end = 
                                     block_fam[i2]->sequences[i3].position +
                                     block_fam[i2]->sequences[i3].length - 1 - gaps ;

           map->seq_map[i1].num_blocks++ ;
           }
        }
     }

/* sequence lengths are unknown 
   use end of fartherest block in each sequence as the sequence length */

  for (i1=0; i1<seqs_num; i1++)
  {
    map->seq_map[i1].seq_len = 0;
    /*  first get right-most block end  */
    for (i2 = 0; i2 < map->seq_map[i1].num_blocks; i2++)
      if (map->seq_map[i1].blocks[i2].end > map->seq_map[i1].seq_len)
      	 map->seq_map[i1].seq_len = map->seq_map[i1].blocks[i2].end;

    /*  then see if the .lis file has a longer length for this sequence */
    if (ids != NULL) did = ids->next;
    else             did = NULL;
    while (did != NULL && strcmp(did->entry, map->seq_map[i1].seq_name) != 0)
    {   did = did->next; }
    if (did != NULL && strcmp(did->entry, map->seq_map[i1].seq_name) == 0 &&
        did->len > map->seq_map[i1].seq_len )
    {   map->seq_map[i1].seq_len = did->len; }
  }

/* find length of longest sequence */
  map->max_seq_len = 0 ;
  for (i1=0; i1<seqs_num; i1++)
      if (map->seq_map[i1].seq_len > map->max_seq_len)
         map->max_seq_len = map->seq_map[i1].seq_len ;

/* check for each sequence that the blocks follow one another, do not overlap
   and are within the sequence boundaries */

  for (i1=0; i1<seqs_num; i1++) 
  {
      /* the start positions were already checked above */
      if (map->seq_map[i1].blocks[map->seq_map[i1].num_blocks-1].end >
          map->seq_map[i1].seq_len)
         {
         fprintf(stderr,"Error ! Last block (%c, %d-%d) in sequence %s of family %s\n\
is out of bounds.\n",
                map->seq_map[i1].blocks[map->seq_map[i1].num_blocks-1].code,
                map->seq_map[i1].blocks[map->seq_map[i1].num_blocks-1].start,
                map->seq_map[i1].blocks[map->seq_map[i1].num_blocks-1].end,
                map->seq_map[i1].seq_name, map->block_family) ;
         free_map(map) ;
         free(blk_sfx) ;
         return(NULL) ;
         }

      for(i2=1; i2 < map->seq_map[i1].num_blocks; i2++)
      {
	 if (map->seq_map[i1].blocks[i2].start <= 
             map->seq_map[i1].blocks[i2-1].end)
         {
            if (map->seq_map[i1].blocks[i2].end <= 
             map->seq_map[i1].blocks[i2-1].start)
            {  fprintf(stderr,
           "Note - Block %c PRECEDES block %c in sequence %s of family %s.\n",
                      map->seq_map[i1].blocks[i2-1].code, 
                      map->seq_map[i1].blocks[i2].code, 
                      map->seq_map[i1].seq_name, map->block_family) ;
            }
            else 
            /* blocks overlap */
            {
      fprintf(stderr,"Note - Blocks %c (%d-%d) and %c (%d-%d) in sequence %s\n\
of family %s overlap.\n", 
                map->seq_map[i1].blocks[i2-1].code, 
                map->seq_map[i1].blocks[i2-1].start,
                map->seq_map[i1].blocks[i2-1].end,
                map->seq_map[i1].blocks[i2].code, 
                map->seq_map[i1].blocks[i2].start,
                map->seq_map[i1].blocks[i2].end,
                map->seq_map[i1].seq_name, map->block_family) ;
     map->seq_map[i1].blocks[i2].start = map->seq_map[i1].blocks[i2-1].end + 1;
            }
         }
      } /* end of i2 block */

   }  /* end of i1 seq */

  free(blk_sfx) ;
  return(map) ;
}   /* end of makemap  */

/* free memory allocated to blocks_map structure */
void free_map(map) 
blocks_map *map ;
{
  int i1 ;

  for(i1=0; i1<map->num_seqs; i1++) free(map->seq_map[i1].blocks) ; 
   
  free(map->seq_map) ;

  free(map) ;

} /* end of free_map */

/* write out a block map */
void write_a_map(map, ofp) 
blocks_map *map ;
FILE *ofp ;
{
  int i1, i2 ;

/*
  fprintf(ofp, "#MAP# ") ;
*/
  fprintf(ofp, ">%s %d %d %d %s\n%s\n",
          map->block_family, map->tot_num_blocks, map->num_seqs, 
          map->max_seq_len, map->id, map->description);

  for(i1=0; i1<map->num_seqs; i1++)
     {
     fprintf(ofp, "%s %d %d\n",
             map->seq_map[i1].seq_name, map->seq_map[i1].seq_len,
             map->seq_map[i1].num_blocks) ;

     for(i2=0; i2<map->seq_map[i1].num_blocks; i2++)
        fprintf(ofp, "%c %d %d\n", map->seq_map[i1].blocks[i2].code, 
                                   map->seq_map[i1].blocks[i2].start, 
                                   map->seq_map[i1].blocks[i2].end) ;
     }

  fprintf(ofp, "//\n") ;
  fflush(ofp) ;

} /* end of write_a_map */

/*****************************************************************************
 * get input and output file names and other program parameters.
 *
 * All parameters, except explicit input and output file names, 
 * should be preceded by a "-".
 * A sole "-" (implicitly) specifies a default for a file name.
 * The first explicit or implicit file name will be assigned to the input file
 * and the second to the output file.
 *****************************************************************************/

void getargs(argc,argv,inpfname,outfname,fam_name, lisname)
int    argc;
char   *argv[];
char   inpfname[], outfname[], fam_name[], lisname[];
{
   int i1 ;
   int fnames=0 ;
   char *chr_ptr ;

   inpfname[0] = outfname[0] = fam_name[0] = lisname[0] = '\0';

   for(i1=1; i1<argc; i1++)                       /* go through all arguments*/
      {
      if (argv[i1][0] != '-')                        /* presumed a file name */
	 {
         if (fnames == 0)                    /* first name is for input file */
	    {
            strcpy(inpfname,argv[i1]) ;
            fnames++ ;
	    }
         else if (fnames == 1)             /* second name is for output file */
	    {
            strcpy(outfname,argv[i1]) ;
            fnames++ ;
	    }
         else                                           /* probably an error */
            fprintf(stderr, 
            "Warning unrecognized parameter - \"%s\".\n", 
            argv[i1]);
	 }
      else
	 {
         switch (toupper(argv[i1][1]))
	    {
            case 'F' :
               chr_ptr = argv[i1] + 2 ;
               strcpy(fam_name,chr_ptr) ;
               break ;
            case 'L' :
               chr_ptr = argv[i1] + 2 ;
               strcpy(lisname,chr_ptr) ;
               break ;
	    case '\0' :                           /* parameter is just a "-" */
               fnames++ ;
               break ;
	    default :
               fprintf(stderr, 
               "Warning unrecognized parameter - \"%s\".\n", 
               argv[i1]);
	    }
         }
      }

      return ;
}  /* end of getargs */
