/**********************************************************************
 * File:        charsample.cpp  (Formerly charsample.c)
 * Description: Class to contain character samples and match scores
 *					to be used for adaption
 * Author:      Chris Newton
 * Created:     Thu Oct  7 13:40:37 BST 1993
 *
 * (C) Copyright 1993, Hewlett-Packard Ltd.
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 ** http://www.apache.org/licenses/LICENSE-2.0
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 *
 **********************************************************************/

#include "mfcpch.h"
#include <stdio.h>
#include          <ctype.h>
#include          <math.h>
#ifdef __UNIX__
#include <assert.h>
#include          <unistd.h>
#endif
#include "memry.h"
#include          "tessvars.h"
#include "statistc.h"
#include          "charsample.h"
#include "paircmp.h"
#include "matmatch.h"
#include          "adaptions.h"
#include          "secname.h"
#include          "notdll.h"

extern inT32 demo_word;          // Hack for demos

ELISTIZE (CHAR_SAMPLE) ELISTIZE (CHAR_SAMPLES) CHAR_SAMPLE::CHAR_SAMPLE () {
  sample_blob = NULL;
  sample_denorm = NULL;
  sample_image = NULL;
  ch = '\0';
  n_samples_matched = 0;
  total_match_scores = 0.0;
  sumsq_match_scores = 0.0;
}


CHAR_SAMPLE::CHAR_SAMPLE(PBLOB *blob, DENORM *denorm, char c) {
  sample_blob = blob;
  sample_denorm = denorm;
  sample_image = NULL;
  ch = c;
  n_samples_matched = 0;
  total_match_scores = 0.0;
  sumsq_match_scores = 0.0;
}


CHAR_SAMPLE::CHAR_SAMPLE(IMAGE *image, char c) {
  sample_blob = NULL;
  sample_denorm = NULL;
  sample_image = image;
  ch = c;
  n_samples_matched = 0;
  total_match_scores = 0.0;
  sumsq_match_scores = 0.0;
}


float CHAR_SAMPLE::match_sample(  // Update match scores
                                CHAR_SAMPLE *test_sample,
                                BOOL8 updating) {
  float score1;
  float score2;
  IMAGE *image = test_sample->image ();

  if (sample_blob != NULL && test_sample->blob () != NULL) {
    PBLOB *blob = test_sample->blob ();
    DENORM *denorm = test_sample->denorm ();

    score1 = compare_bln_blobs (sample_blob, sample_denorm, blob, denorm);
    score2 = compare_bln_blobs (blob, denorm, sample_blob, sample_denorm);

    score1 = (score1 > score2) ? score1 : score2;
  }
  else if (sample_image != NULL && image != NULL) {
    CHAR_PROTO *sample = new CHAR_PROTO (this);

    score1 = matrix_match (sample_image, image);
    delete sample;
  }
  else
    return BAD_SCORE;

  if ((tessedit_use_best_sample || tessedit_cluster_debug) && updating) {
    n_samples_matched++;
    total_match_scores += score1;
    sumsq_match_scores += score1 * score1;
  }
  return score1;
}


double CHAR_SAMPLE::mean_score() {
  if (n_samples_matched > 0)
    return (total_match_scores / n_samples_matched);
  else
    return BAD_SCORE;
}


double CHAR_SAMPLE::variance() {
  double mean = mean_score ();

  if (n_samples_matched > 0) {
    return (sumsq_match_scores / n_samples_matched) - mean * mean;
  }
  else
    return BAD_SCORE;
}


void CHAR_SAMPLE::print(FILE *f) {
  if (!tessedit_cluster_debug)
    return;

  if (n_samples_matched > 0)
    fprintf (f,
      "%c - sample matched against " INT32FORMAT
      " blobs, mean: %f, var: %f\n", ch, n_samples_matched,
      mean_score (), variance ());
  else
    fprintf (f, "No matches for this sample (%c)\n", ch);
}


void CHAR_SAMPLE::reset_match_statistics() {
  n_samples_matched = 0;
  total_match_scores = 0.0;
  sumsq_match_scores = 0.0;
}


CHAR_SAMPLES::CHAR_SAMPLES() {
  type = UNKNOWN;
  samples.clear ();
  ch = '\0';
  best_sample = NULL;
  proto = NULL;
}


CHAR_SAMPLES::CHAR_SAMPLES(CHAR_SAMPLE *sample) {
  CHAR_SAMPLE_IT sample_it = &samples;

  ASSERT_HOST (sample->image () != NULL || sample->blob () != NULL);

  if (sample->image () != NULL)
    type = IMAGE_CLUSTER;
  else if (sample->blob () != NULL)
    type = BLOB_CLUSTER;

  samples.clear ();
  sample_it.add_to_end (sample);
  if (tessedit_mm_only_match_same_char)
    ch = sample->character ();
  else
    ch = '\0';
  best_sample = NULL;
  proto = NULL;
}


void CHAR_SAMPLES::add_sample(CHAR_SAMPLE *sample) {
  CHAR_SAMPLE_IT sample_it = &samples;

  if (tessedit_use_best_sample || tessedit_cluster_debug)
    for (sample_it.mark_cycle_pt ();
  !sample_it.cycled_list (); sample_it.forward ()) {
    sample_it.data ()->match_sample (sample, TRUE);
    sample->match_sample (sample_it.data (), TRUE);
  }

  sample_it.add_to_end (sample);

  if (tessedit_mm_use_prototypes && type == IMAGE_CLUSTER) {
    if (samples.length () == tessedit_mm_prototype_min_size)
      this->build_prototype ();
    else if (samples.length () > tessedit_mm_prototype_min_size)
      this->add_sample_to_prototype (sample);
  }
}


void CHAR_SAMPLES::add_sample_to_prototype(CHAR_SAMPLE *sample) {
  BOOL8 rebuild = FALSE;
  inT32 new_xsize = proto->x_size ();
  inT32 new_ysize = proto->y_size ();
  inT32 sample_xsize = sample->image ()->get_xsize ();
  inT32 sample_ysize = sample->image ()->get_ysize ();

  if (sample_xsize > new_xsize) {
    new_xsize = sample_xsize;
    rebuild = TRUE;
  }
  if (sample_ysize > new_ysize) {
    new_ysize = sample_ysize;
    rebuild = TRUE;
  }

  if (rebuild)
    proto->enlarge_prototype (new_xsize, new_ysize);

  proto->add_sample (sample);
}


void CHAR_SAMPLES::build_prototype() {
  CHAR_SAMPLE_IT sample_it = &samples;
  CHAR_SAMPLE *sample;
  inT32 proto_xsize = 0;
  inT32 proto_ysize = 0;

  if (type != IMAGE_CLUSTER
    || samples.length () < tessedit_mm_prototype_min_size)
    return;

  for (sample_it.mark_cycle_pt ();
  !sample_it.cycled_list (); sample_it.forward ()) {
    sample = sample_it.data ();
    if (sample->image ()->get_xsize () > proto_xsize)
      proto_xsize = sample->image ()->get_xsize ();
    if (sample->image ()->get_ysize () > proto_ysize)
      proto_ysize = sample->image ()->get_ysize ();
  }

  proto = new CHAR_PROTO (proto_xsize, proto_ysize, 0, 0, '\0');

  for (sample_it.mark_cycle_pt ();
    !sample_it.cycled_list (); sample_it.forward ())
  this->add_sample_to_prototype (sample_it.data ());

}


void CHAR_SAMPLES::find_best_sample() {
  CHAR_SAMPLE_IT sample_it = &samples;
  double score;
  double best_score = MAX_INT32;

  if (ch == '\0' || samples.length () < tessedit_mm_prototype_min_size)
    return;

  for (sample_it.mark_cycle_pt ();
  !sample_it.cycled_list (); sample_it.forward ()) {
    score = sample_it.data ()->mean_score ();
    if (score < best_score) {
      best_score = score;
      best_sample = sample_it.data ();
    }
  }
  #ifndef SECURE_NAMES
  if (tessedit_cluster_debug) {
    tprintf ("Best sample for this %c cluster:\n", ch);
    best_sample->print (debug_fp);
  }
  #endif
}


float CHAR_SAMPLES::match_score(CHAR_SAMPLE *sample) {
  if (tessedit_mm_only_match_same_char && sample->character () != ch)
    return BAD_SCORE;

  if (tessedit_use_best_sample && best_sample != NULL)
    return best_sample->match_sample (sample, FALSE);
  else if ((tessedit_mm_use_prototypes
    || tessedit_mm_adapt_using_prototypes) && proto != NULL)
    return proto->match_sample (sample);
  else
    return this->nn_match_score (sample);
}


float CHAR_SAMPLES::nn_match_score(CHAR_SAMPLE *sample) {
  CHAR_SAMPLE_IT sample_it = &samples;
  float score;
  float min_score = MAX_INT32;

  for (sample_it.mark_cycle_pt ();
  !sample_it.cycled_list (); sample_it.forward ()) {
    score = sample_it.data ()->match_sample (sample, FALSE);
    if (score < min_score)
      min_score = score;
  }

  return min_score;
}


void CHAR_SAMPLES::assign_to_char() {
  STATS char_frequency(FIRST_CHAR, LAST_CHAR);
  CHAR_SAMPLE_IT sample_it = &samples;
  inT32 i;
  inT32 max_index = 0;
  inT32 max_freq = 0;

  if (samples.length () == 0 || tessedit_mm_only_match_same_char)
    return;

  for (sample_it.mark_cycle_pt ();
    !sample_it.cycled_list (); sample_it.forward ())
  char_frequency.add ((inT32) sample_it.data ()->character (), 1);

  for (i = FIRST_CHAR; i <= LAST_CHAR; i++)
  if (char_frequency.pile_count (i) > max_freq) {
    max_index = i;
    max_freq = char_frequency.pile_count (i);
  }

  if (samples.length () >= tessedit_cluster_min_size
    && max_freq > samples.length () * tessedit_cluster_accept_fraction)
    ch = (char) max_index;
}


void CHAR_SAMPLES::print(FILE *f) {
  CHAR_SAMPLE_IT sample_it = &samples;

  fprintf (f, "Collected " INT32FORMAT " samples\n", samples.length ());

  #ifndef SECURE_NAMES
  if (tessedit_cluster_debug)
    for (sample_it.mark_cycle_pt ();
    !sample_it.cycled_list (); sample_it.forward ())
  sample_it.data ()->print (f);

  if (ch == '\0')
    fprintf (f, "\nCluster not used for adaption\n");
  else
    fprintf (f, "\nCluster used to adapt to '%c's\n", ch);
  #endif
}


CHAR_PROTO::CHAR_PROTO() {
  xsize = 0;
  ysize = 0;
  ch = '\0';
  nsamples = 0;
  proto_data = NULL;
  proto = NULL;
}


CHAR_PROTO::CHAR_PROTO(inT32 x_size,
                       inT32 y_size,
                       inT32 n_samples,
                       float initial_value,
                       char c) {
  inT32 x;
  inT32 y;

  xsize = x_size;
  ysize = y_size;
  ch = c;
  nsamples = n_samples;

  ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);

  for (y = 0; y < ysize; y++)
    for (x = 0; x < xsize; x++)
      proto[x][y] = initial_value;
}


CHAR_PROTO::CHAR_PROTO(CHAR_SAMPLE *sample) {
  inT32 x;
  inT32 y;
  IMAGELINE imline_s;

  if (sample->image () == NULL) {
    xsize = 0;
    ysize = 0;
    ch = '\0';
    nsamples = 0;
    proto_data = NULL;
    proto = NULL;
  }
  else {
    ch = sample->character ();
    xsize = sample->image ()->get_xsize ();
    ysize = sample->image ()->get_ysize ();
    nsamples = 1;

    ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);

    for (y = 0; y < ysize; y++) {
      sample->image ()->fast_get_line (0, y, xsize, &imline_s);
      for (x = 0; x < xsize; x++)
        if (imline_s.pixels[x] == BINIM_WHITE)
          proto[x][y] = 1.0;
      else
        proto[x][y] = -1.0;
    }
  }
}


CHAR_PROTO::~CHAR_PROTO () {
  if (proto_data != NULL)
    FREE_2D_ARRAY(proto_data, proto);
}


float CHAR_PROTO::match_sample(CHAR_SAMPLE *test_sample) {
  CHAR_PROTO *test_proto;
  float score;

  if (test_sample->image () != NULL) {
    test_proto = new CHAR_PROTO (test_sample);
    if (xsize > test_proto->x_size ())
      score = this->match (test_proto);
    else {
      demo_word = -demo_word;    // Flag different call
      score = test_proto->match (this);
    }
  }
  else
    return BAD_SCORE;

  delete test_proto;

  return score;
}


float CHAR_PROTO::match(CHAR_PROTO *test_proto) {
  inT32 xsize2 = test_proto->x_size ();
  inT32 y_size;
  inT32 y_size2;
  inT32 x_offset;
  inT32 y_offset;
  inT32 x;
  inT32 y;
  CHAR_PROTO *match_proto;
  float score;
  float sum = 0.0;

  ASSERT_HOST (xsize >= xsize2);

  x_offset = (xsize - xsize2) / 2;

  if (ysize < test_proto->y_size ()) {
    y_size = test_proto->y_size ();
    y_size2 = ysize;
    y_offset = (y_size - y_size2) / 2;

    match_proto = new CHAR_PROTO (xsize,
      y_size,
      nsamples * test_proto->n_samples (),
      0, '\0');

    for (y = 0; y < y_offset; y++) {
      for (x = 0; x < xsize2; x++) {
        match_proto->data ()[x + x_offset][y] =
          test_proto->data ()[x][y] * nsamples;
        sum += match_proto->data ()[x + x_offset][y];
      }
    }

    for (y = y_offset + y_size2; y < y_size; y++) {
      for (x = 0; x < xsize2; x++) {
        match_proto->data ()[x + x_offset][y] =
          test_proto->data ()[x][y] * nsamples;
        sum += match_proto->data ()[x + x_offset][y];
      }
    }

    for (y = y_offset; y < y_offset + y_size2; y++) {
      for (x = 0; x < x_offset; x++) {
        match_proto->data ()[x][y] = proto[x][y - y_offset] *
          test_proto->n_samples ();
        sum += match_proto->data ()[x][y];
      }

      for (x = x_offset + xsize2; x < xsize; x++) {
        match_proto->data ()[x][y] = proto[x][y - y_offset] *
          test_proto->n_samples ();
        sum += match_proto->data ()[x][y];
      }

      for (x = x_offset; x < x_offset + xsize2; x++) {
        match_proto->data ()[x][y] =
          proto[x][y - y_offset] * test_proto->data ()[x - x_offset][y];
        sum += match_proto->data ()[x][y];
      }
    }
  }
  else {
    y_size = ysize;
    y_size2 = test_proto->y_size ();
    y_offset = (y_size - y_size2) / 2;

    match_proto = new CHAR_PROTO (xsize,
      y_size,
      nsamples * test_proto->n_samples (),
      0, '\0');

    for (y = 0; y < y_offset; y++)
    for (x = 0; x < xsize; x++) {
      match_proto->data ()[x][y] =
        proto[x][y] * test_proto->n_samples ();
      sum += match_proto->data ()[x][y];
    }

    for (y = y_offset + y_size2; y < y_size; y++)
    for (x = 0; x < xsize; x++) {
      match_proto->data ()[x][y] =
        proto[x][y] * test_proto->n_samples ();
      sum += match_proto->data ()[x][y];
    }

    for (y = y_offset; y < y_offset + y_size2; y++) {
      for (x = 0; x < x_offset; x++) {
        match_proto->data ()[x][y] =
          proto[x][y] * test_proto->n_samples ();
        sum += match_proto->data ()[x][y];
      }

      for (x = x_offset + xsize2; x < xsize; x++) {
        match_proto->data ()[x][y] =
          proto[x][y] * test_proto->n_samples ();
        sum += match_proto->data ()[x][y];
      }

      for (x = x_offset; x < x_offset + xsize2; x++) {
        match_proto->data ()[x][y] = proto[x][y] *
          test_proto->data ()[x - x_offset][y - y_offset];
        sum += match_proto->data ()[x][y];
      }
    }
  }

  score = (1.0 - sum /
    (xsize * y_size * nsamples * test_proto->n_samples ()));

  if (tessedit_mm_debug) {
    if (score < 0) {
      tprintf ("Match score %f\n", score);
      tprintf ("x: %d, y: %d, ns: %d, nt: %d, dx %d, dy: %d\n",
        xsize, y_size, nsamples, test_proto->n_samples (),
        x_offset, y_offset);
      for (y = 0; y < y_size; y++) {
        tprintf ("\n%d", y);
        for (x = 0; x < xsize; x++)
          tprintf ("\t%d", match_proto->data ()[x][y]);

      }
      tprintf ("\n");
      fflush(debug_fp);
    }
  }

#ifndef GRAPHICS_DISABLED
  if (tessedit_display_mm) {
    tprintf ("Match score %f\n", score);
    display_images (this->make_image (),
      test_proto->make_image (), match_proto->make_image ());
  }
  else if (demo_word != 0) {
    if (demo_word > 0)
      display_image (test_proto->make_image (), "Test sample",
        300, 400, FALSE);
    else
      display_image (this->make_image (), "Test sample", 300, 400, FALSE);

    display_image (match_proto->make_image (), "Best match",
      700, 400, TRUE);
  }
#endif

  delete match_proto;

  return score;
}


void CHAR_PROTO::enlarge_prototype(inT32 new_xsize, inT32 new_ysize) {
  float *old_proto_data = proto_data;
  float **old_proto = proto;
  inT32 old_xsize = xsize;
  inT32 old_ysize = ysize;
  inT32 x_offset;
  inT32 y_offset;
  inT32 x;
  inT32 y;

  ASSERT_HOST (new_xsize >= xsize && new_ysize >= ysize);

  xsize = new_xsize;
  ysize = new_ysize;
  ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
  x_offset = (xsize - old_xsize) / 2;
  y_offset = (ysize - old_ysize) / 2;

  for (y = 0; y < y_offset; y++)
    for (x = 0; x < xsize; x++)
      proto[x][y] = nsamples;

  for (y = y_offset + old_ysize; y < ysize; y++)
    for (x = 0; x < xsize; x++)
      proto[x][y] = nsamples;

  for (y = y_offset; y < y_offset + old_ysize; y++) {
    for (x = 0; x < x_offset; x++)
      proto[x][y] = nsamples;

    for (x = x_offset + old_xsize; x < xsize; x++)
      proto[x][y] = nsamples;

    for (x = x_offset; x < x_offset + old_xsize; x++)
      proto[x][y] = old_proto[x - x_offset][y - y_offset];
  }

  FREE_2D_ARRAY(old_proto_data, old_proto);
}


void CHAR_PROTO::add_sample(CHAR_SAMPLE *sample) {
  inT32 x_offset;
  inT32 y_offset;
  inT32 x;
  inT32 y;
  IMAGELINE imline_s;
  inT32 sample_xsize = sample->image ()->get_xsize ();
  inT32 sample_ysize = sample->image ()->get_ysize ();

  x_offset = (xsize - sample_xsize) / 2;
  y_offset = (ysize - sample_ysize) / 2;

  ASSERT_HOST (x_offset >= 0 && y_offset >= 0);

  for (y = 0; y < y_offset; y++)
    for (x = 0; x < xsize; x++)
      proto[x][y]++;             // Treat pixels outside the
  // range as white
  for (y = y_offset + sample_ysize; y < ysize; y++)
    for (x = 0; x < xsize; x++)
      proto[x][y]++;

  for (y = y_offset; y < y_offset + sample_ysize; y++) {
    sample->image ()->fast_get_line (0,
      y - y_offset, sample_xsize, &imline_s);
    for (x = x_offset; x < x_offset + sample_xsize; x++) {
      if (imline_s.pixels[x - x_offset] == BINIM_WHITE)
        proto[x][y]++;
      else
        proto[x][y]--;
    }

    for (x = 0; x < x_offset; x++)
      proto[x][y]++;

    for (x = x_offset + sample_xsize; x < xsize; x++)
      proto[x][y]++;
  }

  nsamples++;
}


IMAGE *CHAR_PROTO::make_image() {
  IMAGE *image;
  IMAGELINE imline_p;
  inT32 x;
  inT32 y;

  ASSERT_HOST (nsamples != 0);

  image = new (IMAGE);
  image->create (xsize, ysize, 8);

  for (y = 0; y < ysize; y++) {
    image->fast_get_line (0, y, xsize, &imline_p);

    for (x = 0; x < xsize; x++) {
      imline_p.pixels[x] = 128 +
        (uinT8) ((proto[x][y] * 128.0) / (0.00001 + nsamples));
    }

    image->fast_put_line (0, y, xsize, &imline_p);
  }
  return image;
}
