/* COPYRIGHT 1999-2003 Fred Hutchinson CAncer Research Center
 * block_vis.c
 *
 * A program to draw the positions of blocks in sequences.
 * Input is a block map(s) (made by program makeblockmap from block
 * family data). The drawing is in ascii characters.
 *
 * Written by Ross Morgan-Linial April 97'.
 *
 * General description, to make your life easier:
 * main() calls read_args() to get the input files and so on. Main then
 * calls read_file() for each input file (up to 16). read_file() opens
 * the input and output files, and repeatedly calls read_blocks_map(),
 * graph_all(), and dealloc_blocks_map(), which read the data, draw it,
 * and free the memory used. graph_all() calls make_graph(), which does
 * the actual drawing. All the other functions are just helpers.
 *
 * Apr 97' added and moved a few comments.
 *         modified to draw sequence length in parenthesis.
 *         removed ' =' from sacle drawing.
 *         modified to make helptext a constant and to include name of the
 *         program run (global variable program, = argv[0]).
 * 
 * July '97  RML
 *         added single-family option.

  6/22/99 Read either ">" or "#MAP#" input files
          Longer sequence names (10->18)
 12/23/06 Longer sequence names (18->20)
 */

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

/* The character used to represent non-block bases. */
#define C_BASE '-'

/* The maximum length of each line to read in. */
#define LINEBUF_LEN 160

/* Program name for the helptext */
char *program ;

/*
 * Scale control variables.
 * SCALE_CONSTANT means that 1 square = o_scale_val bases.
 * SCALE_MAX means that the scale is such that the longest sequence is
 *   o_scale_val squares, and all other sequences are proportional.
 * SCALE_FLUSH means that the scale is such that each sequence is o_scale_val
 *   squares.
 */
enum {SCALE_CONSTANT, SCALE_MAX, SCALE_FLUSH};
int o_scale = SCALE_CONSTANT;
int o_scale_val = 10;

/* We did nothing - why run the program? (A warning flag) */
int o_did_nothing = 1;

/* Name of a single block family (BLxxxxx) */
const char *one_family = 0;

#ifdef INCLUDE_REOUTPUT_STUFF
/*
 * Take the data we get from the input files and reoutput into a file with
 * the same format.
 */ 
int o_reoutput = 0;
#endif

/*
 * Level 0 = no output
 * Level 1 = errors only
 * Level 2 = quiet
 * Level 3 = normal
 * Level 4 = verbose
 * Level 5 = debug
 * Level 6 = verbose debug (!)
 */
int o_output_level = 3;

/* Full debug mode. Exactly the same as o_output_level = 5. */
int o_full_debug = 0;

/* Input files (up to 16, for no particular reason). */
char *o_input_filenames[16];
int o_num_input_files = 0;

/* The output file - defaults to stdout. */
char *o_output_filename = "-";

/* Currently unused option. */
int o_bare_output = 0;

/* Function to determine whether a particular message is OK to show. */
int mlevel(int req)
{
  return (o_output_level >= req || o_full_debug);
}

/* Text for the -h (help) option. */

#define HELPTEXT \
"This program takes block map data and generates a graphical map of the\n"\
"sequences.\n\n"\
"Usage: %s [infiles] [options]\n"\
"Example: %s blocks.mapdata -v2 -o blocks.maps -m80\n\n"\
"Arguments to options should be '-o[argument]' or '-o [argument]'.\n"\
"Use '-' to stand for standard input/output.\n"\
"Default output is to the screen.\n\n"\
"Available options:\n"\
" Option          Effect\n"\
" -[cfm] number   Use scale mode [constant/full/maximum]\n"\
"                 Constant: Each character is <number> amino acids.\n"\
"                 Full: Each sequence is <number> characters long.\n"\
"                 Maximum: The longest sequence in each family is <number>\n"\
"                 characters long, the rest are to scale.\n"\
"                 The default is -c10 (each character is 10 amino acids).\n"\
" -h              Get this help screen.\n"\
" -i filename     Add an input file(s) (this is not needed if input files\n"\
"                 are placed before the options). Seperate multiple filenames\n"\
"                 with spaces.\n"\
" -o filename     Set the output filename. The input filename is substituted\n"\
"                 for '*' within <filename>, so '*.vis' is '<input file>.vis'.\n"\
"                 You must use single quotes around <filename> if it contains\n"\
"                 '*'.\n"\
" -v number       Set verbosity level, 0-5. 0 = no messages, 1 = errors only,\n"\
"                 2 = quiet, 3 = normal (default), 4 = verbose, 5 = debug.\n"\
" -a BLxxxxx      Do only a single block family.\n"

/* A simple function to put the help text on the screen. */
void do_help(void)
{

  fprintf(stderr, HELPTEXT, program, program) ;

}

/* 
 * Process an option.
 *
 * This function should use the option and return 1 if arguments are
 * possible but not neccessary, 2 if arguments are impossble, and 0 if 
 * arguments are needed.
 */
int arg_start(char c)
{
  switch (c)
  {
  case 'd':
    o_full_debug = 1;
    if (mlevel(5)) 
      fprintf(stderr, "Debug messages on.\n");
    return 2;

  case 'b':
    o_bare_output = 1;
    if (mlevel(4))
      fprintf(stderr, "Output is in bare format.\n");
    return 2;

#ifdef INCLUDE_REOUTPUT_STUFF
  case 'r':
    o_reoutput = 1;
    if (mlevel(4))
      fprintf(stderr, "Data will be reoutput to .reout files.\n");
    return 2;
#endif

  case 'c':
    o_scale = SCALE_CONSTANT;
    return 0;

  case 'm':
    o_scale = SCALE_MAX;
    return 0;

  case 'f':
    o_scale = SCALE_FLUSH;
    return 0;

  case 'i':
  case 'o':
  case 'v':
  case 'a':
    return 0;

  case 'h':
  case '?':
    do_help();
    o_did_nothing = 0;
    return 2;

  default:
    if (mlevel(2))
      fprintf(stderr, "Warning: unrecognized option '-%c'.\n", c);
    return 1;
  }
}

/*
 * Take an argument to an option.
 *
 * This returns the same values as arg_start(c), above, to indicate whether
 * more arguments are wanted.
 */
int arg_input(char c, char *s)
{
  switch (c)
  {
  case 'c':
  case 'm':
  case 'f':
    o_scale_val = atoi(s);
    if (mlevel(4))
      fprintf(stderr, "Scale type is %c, scale value is %i.\n", c, o_scale_val);
    return 2;

  case 'i':
    o_input_filenames[o_num_input_files++] = s;
    if (mlevel(4))
      fprintf(stderr, "Input file %i is '%s'.\n", o_num_input_files, s);
    o_did_nothing = 0;
    return o_num_input_files >= 16 ? 2 : 1;

  case 'o':
    o_output_filename = s;
    if (mlevel(4))
      fprintf(stderr, "Output file is '%s'.\n", s);
    return 2;

  case 'v':
    o_output_level = atoi(s);
    if (mlevel(6))
      fprintf(stderr, "Verbosity level is %i.\n", o_output_level);
    return 2;

  case 'a':
    one_family = s; /* This is NOT a copy, beware */
    return 2;
  }
  return 2;
}

/*
 * Use the command-line arguments to set options.
 * Each option must have an entry in arg_start(c), above.
 * Arguments taking options must have an entry in arg_input(c, s), above.
 *
 * No input files is probably a condition that should give a warning.
 */
void read_args(int argc, char *argv[])
{
  int n;
  char c = 'i';
  int used = 1;

  /* Check each argument */
  for (n = 1; n < argc; n++)
  {
    /* '-' is special; anything else starting with '-' is an option. */
    if ((argv[n][0] == '-' || argv[n][0] == '+') && argv[n][1])
    {
      /* Warn about options that need arguments but didn't get any. */
      if (!used)
	if (mlevel(3))
	  fprintf(stderr, "Warning: option '-%c' not used.\n", c);

      /* arg_start(c) and arg_input(c,s) expect lower case. */
      c = tolower(argv[n][1]);

      /* Argumentless options take effect now. */
      used = arg_start(c);
      
      /* There could be an argument passed in the same string (eg -v2). */
      if (argv[n][2])
      {
	/* If an argument is wanted, use it. */
	if (used < 2)
	  used = arg_input(c, argv[n] + 2);
	/* Otherwise, warn. The used == 2 is for decoration; used <= 2. */
	else if (mlevel(2) && used == 2)
	{
	  fprintf(stderr, "Warning: option '-%c' has an attached argument (ignored).\n", c);
	  used = 3;
	}
      }
    }
    /* If it's not an option, it's an argument. */
    else
    {
      /* If an argument is wanted, use it. */
      if (used < 2)
	used = arg_input(c, argv[n]);
      /* Otherwise, warn. Used will be 3 if the user has already been warned. */
      else if (mlevel(2) && used == 2)
      {
	fprintf(stderr, "Warning: option '-%c' has too many arguments (ignored).\n", c);
	used = 3;
      }
    }
  }

  /* Warn if the last option expected arguments and got none. */
  if (!used)
    if (mlevel(3))
      fprintf(stderr, "Warning: option '-%c' not used.\n", c);
}

/* A pointer to some malloc()'d memory. */
blocks_map *current_map;

/* Three file pointers, see how they run */
FILE *in_file;
FILE *out_file;
FILE *reout_file;

/* A buffer for the most recently read input line. */
char buffer[LINEBUF_LEN];

/* fgets() might work, but it has some unwanted behavior. */
void read_line(char *s)
{
  int i;
  int c;
  
  /* In debug mode, it's good to know what's expected. */
  if (mlevel(6))
    fprintf(stderr, "Reading line: %s\n", s);

  /* Get characters one at a time. */
  for (i = 0; i < LINEBUF_LEN-1; i++)
  {
    c = fgetc(in_file);

    /* EOF or newline is *not* put in the buffer. */
    if (c == EOF || c == '\n')
      break;

    buffer[i] = c;
  }

  /* Always zero-terminate. */
  buffer[i] = 0;

  /* In debug mode, tell the user what we read. */
  if (mlevel(6))
    fprintf(stderr, "# %s\n", buffer);
}

int maps_processed;

/*
 * Takes the file pointed to by FILE *in_file and reads the data into
 * blocks_map *current_map (a pointer to dynamically allocated memory).
 */
void read_blocks_map(void)
{
  int i1, i2;
  sequence_map *current_seq;
  block_pos *current_block;
  char junk[LINEBUF_LEN];

  /* More debug messages. */
  if (mlevel(5))
    fprintf(stderr, "Reading one family...\n");

  /* Allocate space for the blocks_map structure (remember to dealloc) */
  current_map = (blocks_map *)malloc(sizeof(blocks_map));
  
  /* Read the structure from the file */

  /* End-of-file check */
  if (feof(in_file))
  {
    /* Always clean up memory. */
    free((void *)current_map);

    /* This tells read_file we barfed. */
    current_map = 0;

    /* Tell the user what's going on. Perhaps change to mlevel(4)? */
    if (mlevel(5))
      fprintf(stderr, "End of input file reached.\n");
    return;
  }

  /* Find the family information, and discard any junk lines before that. */
  do 
  {
    read_line("blocks_map");
    
    /* If mlevel(6), we printed this in the read_line() function. */
    if (mlevel(5) && !mlevel(6) && buffer[0])
      fprintf(stderr, "# %s\n", buffer);

    /* End-of-file check, like above */
    if (feof(in_file))
    {
      free((void *)current_map);
      current_map = 0;
      if (mlevel(5))
        fprintf(stderr, "End of input file reached.\n");
      return;
    }
  } while (strncmp(buffer, ">", 1) != 0 &&
           strncasecmp(buffer, "#MAP#", 5) != 0);
/*
  } while ((sscanf(buffer, "%s %s %d %d %d %s", junk, current_map->block_family, 
 		 &current_map->tot_num_blocks, &current_map->num_seqs, 
		 &current_map->max_seq_len, current_map->id) < 6) );
*/
  if (strncmp(buffer, ">", 1) == 0)
        sscanf(buffer, ">%s %d %d %d %s", current_map->block_family, 
 		 &current_map->tot_num_blocks, &current_map->num_seqs, 
		 &current_map->max_seq_len, current_map->id);
/*
  if (strncasecmp(buffer, "#MAP#", 5) == 0)
*/
  else
        sscanf(buffer, "%s %s %d %d %d %s", junk, current_map->block_family, 
 		 &current_map->tot_num_blocks, &current_map->num_seqs, 
		 &current_map->max_seq_len, current_map->id);

  /* Allocate space for the sequence_map structures (remember to dealloc) */
  current_map->seq_map = (sequence_map *)malloc(sizeof(sequence_map) * 
						current_map->num_seqs);

  /* Description line. */
  read_line("description");
  strcpy(current_map->description, buffer);

  /* End-of-file check, like above (we'll be doing this often) */
  if (feof(in_file))
  {
    free((void *)current_map->seq_map);
    free((void *)current_map);
    current_map = 0;
    if (mlevel(1))
      fprintf(stderr, "Error: premature end of input file.\n");
    return;
  }

  /* Multiple sequence_map structures. */
  for (i1 = 0; i1 < current_map->num_seqs; i1++)
  {
    /* A pointer, for easy reference. */
    current_seq = current_map->seq_map + i1;
    
    /* Line with sequence_map information. */
    read_line("sequence_map");
    sscanf(buffer, "%s %d %d", current_seq->seq_name, &current_seq->seq_len,
	   &current_seq->num_blocks);

    /* End-of-file check, again. */
    if (feof(in_file))
    {
      for (i2 = 0; i2 < i1; i2++)
	free((void *)current_map->seq_map[i2].blocks);
      free((void *)current_map->seq_map);
      free((void *)current_map);
      current_map = 0;
      if (mlevel(1))
        fprintf(stderr, "Error: premature end of input file.\n");
      return;
    }

    /* Allocate space for the block_pos structures (remember to dealloc) */
    current_seq->blocks = (block_pos *)malloc(sizeof(block_pos) *
					      current_seq->num_blocks);

    /* Multiple block_pos structures */
    for (i2 = 0; i2 < current_seq->num_blocks; i2++)
    {
      /* Another handy pointer. */
      current_block = current_seq->blocks + i2;
      
      /* Line with block_pos information */
      read_line("block_pos");
      sscanf(buffer, "%c %d %d", &current_block->code, &current_block->start,
	     &current_block->end);

      /* End-of-file check */
      if (feof(in_file))
      {
        for (i2 = 0; i2 <= i1; i2++)
 	  free((void *)current_map->seq_map[i2].blocks);
        free((void *)current_map->seq_map);
        free((void *)current_map);
        current_map = 0;
        if (mlevel(1))
          fprintf(stderr, "Error: premature end of input file.\n");
        return;
      }
    }
  }

  /* End-of-family marker. If this is wrong, we screwed up somewhere. */
  read_line("marker");
  if (strcmp(buffer, "//") != 0)
  {
    /*
     * This is very important. Freeing everything and aborting would be
     * smart right now, but we don't. Hopefully, the next family will be
     * parsed correctly.
     */
    if (mlevel(1))
      fprintf(stderr, "Error: bad data read.\n");
    return;
  }

  maps_processed++;
}

/* Ignore this. */
#ifdef INCLUDE_REOUTPUT_STUFF
void write_a_map(void)
{
  int i1, i2 ;
  blocks_map *map = current_map;

  /* Marker, so other programs can tell this is the start of the data. */
  fprintf(reout_file, "#MAP# ") ;

  /* The blocks_map information. */
  fprintf(reout_file, "%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 each sequence... */
  for(i1=0; i1<map->num_seqs; i1++)
  {
    /* ...print the name, (possibly fake) length, number of blocks... */
    fprintf(reout_file, "%s %d %d\n",
	    map->seq_map[i1].seq_name, map->seq_map[i1].seq_len,
	    map->seq_map[i1].num_blocks) ;

    /* ...and all the block information, just like makeblockmap does. */
    for(i2=0; i2<map->seq_map[i1].num_blocks; i2++)
      fprintf(reout_file, "%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) ;
  }

  /* Print an end-of-family marker to keep everyone in synch. */
  fprintf(reout_file, "//\n\n") ;

  /* I have no idea what this does, I copied it from makeblockmap.c */
  fflush(reout_file) ;
}
#endif

/* Free all the memory allocated in read_blocks_map. This is very important. */
void dealloc_blocks_map(void)
{
  int i1, i2;

  /* Deallocate the blocks in each sequence_map */
  for (i1 = 0; i1 < current_map->num_seqs; i1++)
  {
    free((void *)current_map->seq_map[i1].blocks);
  }

  /* Deallocate the sequence_maps */
  free((void *)current_map->seq_map);

  /* Deallocate the blocks_map */
  free((void *)current_map);

  /* Mark the blocks_map as gone */
  current_map = 0;
}

/* Two variables which I was too lazy to pass to each function. */
int length;
int maxlength;

/* Convert an amino acid position to a square position. */
int aa_to_square(int aa)
{
  switch (o_scale)
  {
  case SCALE_CONSTANT:
    return (aa + o_scale_val/2) / o_scale_val;
  case SCALE_MAX:
    return (aa * o_scale_val + maxlength/2) / maxlength;
  case SCALE_FLUSH:
    return (aa * o_scale_val + length/2) / length;
  }
}

/* Determine the size of the average square, rounded up. */
int square_size(int n)
{
  switch (o_scale)
  {
  case SCALE_CONSTANT:
    return o_scale_val * n;
  case SCALE_MAX:
    return (n * maxlength + (o_scale_val - 1)) / o_scale_val;
  case SCALE_FLUSH:
    return (n * length + (o_scale_val - 1)) / o_scale_val;
  }
}

/* Take a sequence_map and sort the blocks by position. */
void sort_sequence_map(sequence_map *seq)
{
  int i1;
  int i2;
  int t;

  /* Do a bubble sort (aaarrrggghhh!) */
  for (i1 = 0; i1 < seq->num_blocks - 1; i1++)
  {
    for (i2 = i1 + 1; i2 < seq->num_blocks; i2++)
    {
      /* Wrong order, reverse */
      if (seq->blocks[i1].start > seq->blocks[i2].start)
      {
	t = seq->blocks[i1].start;
	seq->blocks[i1].start = seq->blocks[i2].start;
	seq->blocks[i2].start = t;

	t = seq->blocks[i1].end;
	seq->blocks[i1].end = seq->blocks[i2].end;
	seq->blocks[i2].end = t;

	t = seq->blocks[i1].code;
	seq->blocks[i1].code = seq->blocks[i2].code;
	seq->blocks[i2].code = t;
      }
    }
  }
}

/*
 * I _know_ there's a stdio.h function that does this...
 */
void rep_char(char c, int n)
{
  while (n-- > 0) fputc(c, out_file);
}

/* 
 * Does the dirty work. Enjoy.
 *
 * 'Never use comments. If it was hard to write, it should be hard
 * to understand.'
 */
void make_graph(sequence_map *seq)
{
  int i1, i2, n;
  
  /* Store length where other functions can get to it. */
  length = seq->seq_len;

  /* Sort the blocks by starting positions */
  sort_sequence_map(seq);

  /* Print the name and (probably fudged) length. */
  fprintf(out_file, "%-20s (%4d) ", seq->seq_name, length);

  /* Print the empty space before each block, and the blocks. */
  for (i1 = 0; i1 < seq->num_blocks; i1++)
  {
    n = aa_to_square(min(seq->blocks[i1].end, seq->seq_len) - 
		     max(seq->blocks[i1].start, 1) + 1);
    if (n <= 0) n = 1;

    if (i1 == 0)
      rep_char(C_BASE, aa_to_square(max(seq->blocks[i1].start, 1) - 1));
    else
      rep_char(C_BASE, aa_to_square(seq->blocks[i1].start - 
				    seq->blocks[i1 - 1].end - 1));

    rep_char(seq->blocks[i1].code, n);
  }

  /* Print any last empty space */
  rep_char(C_BASE, aa_to_square(seq->seq_len - 
				seq->blocks[seq->num_blocks-1].end));

  /* Add a scale bar, in flush mode */
  if (o_scale == SCALE_FLUSH)
    fprintf(out_file, " [---- %d aa]", square_size(4));

  /* Add a return */
  fprintf(out_file, "\n");
}

/* Draw all the sequences in a blocks_map. */
void graph_all(void)
{
  int i;

  /* Print the sequence information. */
  fprintf(out_file, "%s: %s\n",
	  current_map->block_family, current_map->description);
  fprintf(out_file, "%d distinct blocks in %d sequences\n", 
	  current_map->tot_num_blocks, current_map->num_seqs);

  /* Store maxlength where other functions can get it. */
  maxlength = current_map->max_seq_len;

  /* Graph each sequence. */
  for (i = 0; i < current_map->num_seqs; i++)
  {
    make_graph(current_map->seq_map + i);
  }

  /* Draw a scale bar */
  if (o_scale != SCALE_FLUSH)
  {
    fprintf(out_file, "%20s---- %d amino acids\n\n", "", square_size(4));
  }
}

/* 
 * Open all the neccessary files, read and graph all the sequences,
 * and close the file again. This is the meat of the program.
 */
void read_file(char *ifilename, char *ofilename)
{
  char rofilename[80];

  maps_processed = 0;

#ifdef INCLUDE_REOUTPUT_STUFF
  /* Find the reoutput file name, but special case for ifilename == "-" */
  if (o_reoutput)
  {
    if (ifilename[0] == '-')
      sprintf(rofilename, "stdio.reout");
    else
      sprintf(rofilename, "%s.reout", ifilename);
  }
#endif

  if (mlevel(5)) 
    fprintf(stderr, "Opening '%s' for read.\n", ifilename);

  /* Open the input file, but special-case for "-" == stdio. */
  if (ifilename[0] == '-')
    in_file = stdin;
  else
    in_file = fopen(ifilename, "rb");

  /* Check for failure to open. */
  if (!in_file)
  {
    /* This is a very important error message. */
    if (mlevel(1))
      fprintf(stderr, "Error: cannot open input file '%s'.\n", ifilename);
    
    /* Abort! Abort! */
    return;
  }

  if (mlevel(5)) 
    fprintf(stderr, "Opening '%s' for write.\n", ofilename);

  /* Open the output file, special-casing again. */
  if (ofilename[0] == '-')
    out_file = stdout;
  else
    out_file = fopen(ofilename, "wb");

  /* Check for failure to open again. */
  if (!out_file)
  {
    if (ifilename[0] != '-')
      fclose(in_file);
    if (mlevel(1))
      fprintf(stderr, "Error: cannot open output file '%s'.\n", ofilename);
    return;
  }

#ifdef INCLUDE_REOUTPUT_STUFF
  /* The reoutput file is simpler to open. */
  if (o_reoutput)
    reout_file = fopen(rofilename, "wb");

  /* Only check for failure if the reoutput file is needed. */
  if (o_reoutput && !reout_file)
  {
    if (ifilename[0] != '-')
      fclose(in_file);
    if (ofilename[0] != '-')
      fclose(out_file);
    if (mlevel(1))
      fprintf(stderr, "Error: cannot open reoutput file '%s'.\n", rofilename);
    return;
  }
#endif

  /* We stop when we reach end-of-file. */
  while (1)
  {
    /* Read a family. */
    read_blocks_map();

    /* End-of-file signal is !current_map */
    if (!current_map) break;

#ifdef INCLUDE_REOUTPUT_STUFF
    /* Reoutput the data, if neccessary. */
    if (o_reoutput)
      write_a_map();
#endif

    if (!one_family || strcmp(current_map->block_family, one_family) == 0)
    {
        /* Draw the graph. */
        graph_all();
    }
    else
        maps_processed--;

    /* Always clean up (memory) after yourself. */
    dealloc_blocks_map();
  }

  /* Close the input file, except stdio. */
  if (ifilename[0] != '-')
    fclose(in_file);

  /* Flush output (?) */
  fflush(out_file);

  /* Close the output file, except stdio. */
  if (ofilename[0] != '-')
    fclose(out_file);

#ifdef INCLUDE_REOUTPUT_STUFF
  /* Close the reoutput file, if neccessary. */
  if (o_reoutput)
    fclose(reout_file);
#endif

  if (maps_processed)
  {
/*		Don't want any messages !
    if (mlevel(3))
      fprintf(stderr, "%d families successfully processed.\n", maps_processed);
*/
  }
  else
  {
    if (mlevel(1))
      if (one_family)
        fprintf(stderr, "Error: Family '%s' not found in file '%s'\n.",
                one_family, ifilename);
      else
        fprintf(stderr, "Error: no usable data in file '%s'.\n", ifilename);
  }

/*
  if (mlevel(3))
    if (ofilename[0] == '-')
      fprintf(stderr, "Output sent to stdout.\n");
    else
      fprintf(stderr, "Output placed in file '%s'.\n", ofilename);
*/
}

/*
 * Get the arguments from the command line, then process
 * each input file. The only actual work done here is interpreting
 * '*' in the output filename.
 */
void main(int argc, char *argv[])
{
  int i1, i2, i3, i4;
  char out_filename[80];

  program = argv[0] ;

  /* Read the arguments. */
  read_args(argc, argv);

  /* Process each input file. */
  for (i1 = 0; i1 < o_num_input_files; i1++)
  {
    /* It's always nice to know what's going on. */
    if (mlevel(4))
      fprintf(stderr, "Processing file '%s'.\n", o_input_filenames[i1]);

    /* We copy the output filename to a buffer by hand... */
    for (i2 = i3 = 0; o_output_filename[i2]; i2++)
    {
      /* Because '*' within the output filename stands for the input filename. */
      if (o_output_filename[i2] == '*')
	for (i4 = 0; o_input_filenames[i1][i4]; i4++)
	  out_filename[i3++] = o_input_filenames[i1][i4];
      else
	out_filename[i3++] = o_output_filename[i2];
    }

    /* Always zero-terminate strings. */
    out_filename[i3++] = 0;
	  
    /* Process the file. */
    read_file(o_input_filenames[i1], out_filename); 
  }

  /* Warn of lack of purpose */
  if (o_did_nothing && mlevel(2) && argc > 1)
    fprintf(stderr, "Warning: no input files.\n");

  /* No arguments? Give the user some help (and skip the warning above) */
  if (argc == 1)
    do_help();
}

