/* file = mmeat.c --- the meat of the match (word spotting) operation */
/* Nigel Ward, University of Tokyo, April 1994 */

#include "cheap.h"

/* ========= some stuff used by both real and match ========== */


/* these flags are set either in real.c or in match.c */
extern int verbosep;
extern int normalizep;
extern int realtime_winp;
extern int prunep;

#define LARGE_FLOAT 9999999999999999999999.0;       
#define NOT_SET -1

/* the larger this, the more preference for longer words */
#define MAGIC_FACTOR 2.0    /* was 2.0 */   /* good value determined empirically */

static struct signature templates[MAX_NTEMPLATES];
static int ntemplates;
static int most_likely_offset;        /* saved across templates */

static int numerical_index[MAX_NTEMPLATES];
static int alphabetical_index[MAX_NTEMPLATES];

/*-----------------------------------------------------------------------------*/
int process_template_filenames(argc, argv, start_arg)    /* ENTRY POINT */
  int argc;   char *argv[];    int start_arg;
{
  /* if(verbosep) fprintf(stderr, " match: first template file is argv[%d]=%s \n",
		       start_arg, argv[start_arg]); */
  if (start_arg == argc) {
    fprintf(stderr, "match: no templates specified. exiting\n"); exit(STRANGE);}
  else {
    ntemplates = 0;
    while (start_arg < argc) {
      (void) setup_template(argv[start_arg]);
      start_arg++; } }
  if (verbosep) fprintf(stderr, " read %d templates\n", ntemplates);
  return(templates[0].logp);
}

/* ----------------------------------------------------------------------------- */
int setup_template(fullpath)
      char *fullpath;
{
  int result;   FILE *fp;

  if (verbosep) fprintf(stderr,"  match: reading %s \n", fullpath);

  if (ntemplates >= MAX_NTEMPLATES) {
    fprintf(stderr, "match: template limit number (%d) exceeded \n", MAX_NTEMPLATES);
    fprintf(stderr, "       ignoring subsequent templates \n", MAX_NTEMPLATES);
    return(0);}

  fp = fopen(fullpath, "r");
  if (fp == NULL) {
    fprintf(stderr,"match: can't open template file '%s' (errorno=%d)\n",
	    fullpath, errno);
    return(FALSE);}

  result = read_fe_into_sig(fp, &(templates[ntemplates]));
  fclose(fp);
  if (result == FALSE) {
    fprintf(stderr,"match: something wrong was detected reading %s\n", fullpath);
    return(FALSE); }

  if (ntemplates > 0) check_compatibility(&(templates[ntemplates]));
  
  /* the idea behind length_factor is 
     1. normalize the distance w.r.t length, (divide badness by length)
     2. reward matches for longer templates (divide badness by length again) */
  templates[ntemplates].length_factor =
    1.0 / pow((float)templates[ntemplates].nframes, MAGIC_FACTOR);
  ntemplates++;
  return(TRUE); 
}

/* ----------------------------------------------------------------------------- */
/* check that a signature is in the same format as templates[0]
   ie, verify that the features were computed in the same way */
check_compatibility(s_ptr)                  /* ENTRY POINT */
     struct signature *s_ptr;
{  
  if (strcmp(templates[0].feature_type, s_ptr->feature_type) != 0) {
    fprintf(stderr,"  features computed incompatibly: feature_type %s != %s\n",
	    templates[0].feature_type, s_ptr->feature_type);
    complain_and_exit(s_ptr);}
  if (templates[0].nbins != s_ptr->nbins) {
    fprintf(stderr,"  features computed incompatibly: nbins %d != %d\n",
	    templates[0].nbins, s_ptr->bins); 
    complain_and_exit(s_ptr);}
  if (templates[0].frame_period != s_ptr->frame_period) {
    fprintf(stderr,"  features computed incompatibly: frame_period %d != %d\n",
	    templates[0].frame_period, s_ptr->frame_period); 
    complain_and_exit(s_ptr);}
  if (templates[0].logp != s_ptr->logp) {
    fprintf(stderr,"  features computed incompatibly: with/without taking log\n"); 
    complain_and_exit(s_ptr);}
}

complain_and_exit(s_ptr)                 struct signature *s_ptr;
{
  fprintf(stderr,"  so matching template %s is senseless\n", s_ptr->info_string);
  exit(STRANGE); }

/* ----------------------------------------------------------------------------- */
pre_match_initialization()    /* ENTRY POINT */
{ int tnum;

  for (tnum = 0; tnum < ntemplates; tnum++) {
    templates[tnum].cutoff = LARGE_FLOAT;
    templates[tnum].best_score = LARGE_FLOAT;
    templates[tnum].best_offset = NOT_SET; } }

/* ----------------------------------------------------------------------------- */
incremental_match(start, end, input_ptr)     /* ENTRY POINT */
     int start, end;     /* start/end of the newly computed frames */
     struct signature *input_ptr;
{
  int start_idx, end_idx, tnum;     
  struct signature *t_ptr;
  if (verbosep) fprintf(stderr, " mmeat: start %d, end %d\n", start, end);

  /* !! probable off-by-one errors */
  for (end_idx = start; end_idx <= end; end_idx++) {
    /* try all placements where the template's right edge
       falls within the newly computed frames */
    for (tnum = 0; tnum < ntemplates; tnum++) {
      t_ptr = &(templates[tnum]);
      start_idx = end_idx - t_ptr->nframes;
      if (0 <= start_idx && end_idx <= end) {
	match_at_time(input_ptr, t_ptr, start_idx);
	/* if (verbosep)
	   fprintf(stderr,"start_idx %d, tnum %d , t_ptr->hyp_score[start_idx] %f\n",
	   start_idx, tnum, t_ptr->hyp_score[start_idx]);  */
	if (realtime_winp)
	  draw_likelihood(start_idx, tnum, t_ptr->hyp_score[start_idx], t_ptr->logp); }
    }
    if (realtime_winp) sg_flush();
  }
}

/* ----------------------------------------------------------------------------- */
float cummulative_energy(energy_array, start, nframes)
     float energy_array[];
     int start, nframes;
{
  float sum; int i;
  sum = 0.0;
  for (i = 0; i < nframes; i++)
    sum += energy_array[start + i];
  return(sum); }

/* ----------------------------------------------------------------------------- */
/* Pruning happens as soon as the cummulative distance computed so far exceeds
   the best_score, meaning that this placement cannot be the best for this template */
match_at_time(i_ptr, t_ptr, offset)
     struct signature *i_ptr, *t_ptr;
     int offset;
{
  /* distance = raw difference; score = distance normalized */
  float input_val, templ_val, raw, distance, score, energy_ratio;  
  int time, bin,  hope;

  if (normalizep && i_ptr->logp == FALSE)  
    energy_ratio =
      cummulative_energy(i_ptr->energy, offset, t_ptr->nframes) 
	/ cummulative_energy(t_ptr->energy, 0, t_ptr->nframes);
  else if (normalizep && i_ptr->logp == TRUE)  
    energy_ratio =
      (cummulative_energy(i_ptr->energy, offset, t_ptr->nframes) 
       - cummulative_energy(t_ptr->energy, 0, t_ptr->nframes))
	/ t_ptr->nframes;
  else {energy_ratio = 1.0;}
  distance = 0.0;
  hope = TRUE;
  for (time = 0; time < t_ptr->nframes && hope; time++) {
    for (bin = 0; bin < i_ptr->nbins; bin++) {
      input_val = i_ptr->bins[offset + time][bin];
      if (i_ptr->logp == TRUE)
	templ_val = energy_ratio + t_ptr->bins[time][bin];
      else
	templ_val = energy_ratio * t_ptr->bins[time][bin];
      raw = input_val - templ_val;
      distance += raw * raw; 
      /* if(bin == 0 && time == 0)
	fprintf(stderr, "raw = %f, distance = %f \n", raw, distance); */
      } 
    if (prunep && distance > t_ptr->cutoff)
      hope = FALSE;
  }
  score = distance * t_ptr->length_factor; 
  /* fprintf (stderr, "   tp: %12s, offset %3d, score %10.1f, best %10.1f \n",
	   t_ptr->info_string, offset, score, t_ptr->best_score);    */

  t_ptr->hyp_score[offset] = score;
  if (score < t_ptr->best_score) {
    t_ptr->cutoff = distance;
    t_ptr->best_score = score;
    t_ptr->best_offset = offset;
    most_likely_offset = offset; }
}


/*-----------------------------------------------------------------------------*/
sort_and_output_templates(s_ptr, output_fp)        /* ENTRY POINT */
     struct signature *s_ptr;  FILE *output_fp;  
{
  if (verbosep) {
    alphabetize_templates();
    printf("\n Best matching locations for each template (alphabetical order): \n");
    show_best_hypothesis_per_template(alphabetical_index); }
  sort_templates_by_scores();
  if (verbosep) {
    printf("\n Best matching locations for each template (ranked): \n");
    show_best_hypothesis_per_template(numerical_index); }
  if (verbosep) fprintf(stderr, " match: ntemplates %d\n",ntemplates);
  write_hypotheses_to_file(s_ptr, output_fp); }

/* ----------------------------------------------------------------------------- */
show_best_hypothesis_per_template(ordering)
     int ordering[]; 
{
  int i;   struct signature templ;

  for(i = 0; i < ntemplates; i++) {
    templ = templates[ordering[i]];
    if (templ.best_offset != NOT_SET) {
      printf("   least distance (%13.6f) at %3d, for `%-16s'\n",
	     templ.best_score, templ.best_offset, templ.info_string); }
  } }  

/* ----------------------------------------------------------------------------- */
static int name_compare (i,j)
     int *i, *j;
{  return(strcmp(templates[*i].info_string, templates[*j].info_string));  }

/* ----------------------------------------------------------------------------- */
alphabetize_templates() 
{
  int i;
  for (i = 0; i < ntemplates; i++) {
    alphabetical_index[i] = i; }
  qsort(alphabetical_index, ntemplates, sizeof(int), name_compare); }
      
/* ----------------------------------------------------------------------------- */
static int num_compare (i,j)
     int *i, *j;
{  return(templates[*i].best_score > templates[*j].best_score);  }

/* ----------------------------------------------------------------------------- */
sort_templates_by_scores() 
{
  int i;
  for (i = 0; i < ntemplates; i++) {
    numerical_index[i] = i; }
  qsort(numerical_index, ntemplates, sizeof(int), num_compare); }

/* ----------------------------------------------------------------------------- */
write_hypotheses_to_file (s_ptr, file_ptr)       
     struct signature *s_ptr; FILE *file_ptr;
{
  int ctr, i;
  
  write_wh_file_hdr(file_ptr, s_ptr);
  
  for (ctr = 0; ctr < ntemplates; ctr++) {
    i = numerical_index[ctr];
    if (templates[i].best_offset == -1) 
      {}  /* do nothing, since irrelevant */
    else {
      write_wh_file_line(file_ptr, 
			 templates[i].info_string,
			 templates[i].nframes,
			 templates[i].best_offset,
			 templates[i].best_score); } }
  fclose(file_ptr);
}


/* ============================================================================= */
/* ========= some stuff called only from match.c ========== */

/* Most_likely_offset is used to allow setting a good value for
   best_score, in order to make pruning more effective.  Of course, this
   is only useful if the previous template was for the same word (eg,
   missed-from-jmm and missed-from-mmj).  If filenames are chosen sensibly 
   (so that, when alphabetized, templates for the same word are adjacent) 
   this will work.  The observed speedup due to this is about 10% */
match_by_templates(input_ptr)              struct signature *input_ptr;
{
  int index, tnum;
  struct signature *t_ptr;

  for (tnum = 0; tnum < ntemplates; tnum++) {
    t_ptr = &(templates[tnum]);
    if (input_ptr->nframes - t_ptr->nframes < 0) {
      fprintf(stderr, "match: skipping template `%s' since length (%d) > input length (%d)\n",
	      t_ptr->info_string, t_ptr->nframes, input_ptr->nframes); }
    else {
      /* done for side-effect: sets best_match_yet, helps effective pruning */
      if (most_likely_offset + t_ptr->nframes < input_ptr->nframes)
	match_at_time(input_ptr, t_ptr, most_likely_offset); 

      for(index = 0; index + t_ptr->nframes <= input_ptr->nframes; index++) {
	  match_at_time(input_ptr, t_ptr, index);} 
    } } }


/* ============================================================================= */
/* ========= some stuff called only from real.c ========== */

static int next_frame_to_match;

/* ----------------------------------------------------------------------------- */
init_match(s_ptr)     
     struct signature *s_ptr;
{
  if (realtime_winp)  prepare_realtime_canvas(ntemplates, templates);
  usleep(50000);     /* this shouldn't be necessary */
  /* see if the header-type stuff of the input
     (as determined by the command-line arguments, and stored in input_sig )
     is the same as that of the templates */
  check_compatibility(s_ptr);
  pre_match_initialization();
  next_frame_to_match = 0;
}

/* ----------------------------------------------------------------------------- */
match_some(last_valid_frame, input_ptr)
     int last_valid_frame; struct signature *input_ptr;
{
  if (verbosep) fprintf(stderr, "  matching from nftm %d thru lvf %d\n", 
	  next_frame_to_match, last_valid_frame); 
  incremental_match(next_frame_to_match, last_valid_frame, input_ptr); 
  next_frame_to_match = last_valid_frame + 1;
}

/* ----------------------------------------------------------------------------- */
finish_match_display(s_ptr, output_fp)
     struct signature *s_ptr; FILE *output_fp;
{
  s_ptr->nframes = next_frame_to_match - 1;
  if (verbosep) fprintf(stderr," mmeat: processed %d frames\n", s_ptr->nframes);
  if (realtime_winp) {
    draw_y_axis(2, ntemplates);        /* should be redundant, but is required */
    draw_template_labels(2, ntemplates, templates);
    draw_right_template_labels(ntemplates, templates, s_ptr->nframes);
    draw_right_y_axis(ntemplates, s_ptr->nframes); 
    sg_flush(); }
  sort_and_output_templates(s_ptr, output_fp);
}

/* ----------------------------------------------------------------------------- */
match_cleanup() 
{
  if (realtime_winp) { 
    fprintf(stderr, "  all done ... just pausing 10 sec so you can see the window\n");
    sleep(10); 
    clean_up_realtime_canvas(); } }

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