package yi.iu.processor.clc;

import java.util.*;

import yi.ai.cluster.*;
import yi.ds.*;
import yi.iu.processor.ort.*;
import yi.util.io.*;
import yi.util.config.Configuration;

/**
 * Construct Orientation Consistent Line Cluster.
 */
public class CLC_HorizontalPosition extends CLC_Clustering
{

  public boolean DEMO_HORIZONTAL_CLUSTERING = false;

  /**
   * Argument for histogram based clustering when clustering on horizontal
   * positions (breakWithTolerance).  This is the minimum allowed value for
   * breaking tolerance.  It is normalized by max(image_width, image_height)
   */
  public double MIN_HIST_BREAKING_TOLERANCE = 0.018; // of the image width (12 for 640)

  /**
   * Argument for histogram based clustering when clustering on horizontal
   * positions (breakWithTolerance).  This is the maximum allowed value for
   * breaking tolerance.  It is normalized by max(image_width, image_height)
   */
  public double MAX_HIST_BREAKING_TOLERANCE = 0.15;  // of the image width (96 for 640)

  public CLC_HorizontalPosition() {
    Configuration.configParameters(this);
  }

  public String[] getParameterNames() {
    return super.getParameterNames(new String[] {
      "DEMO_HORIZONTAL_CLUSTERING",
    });
  }

  public LineClusterSets getClusters(LineClusterSet lines, int[] rgb, int width, int height) {

    if (lines == null || lines.size() <= 0) new LineClusterSets(width, height);

//    ORTLine[] prjLines = lines.getLinesWithoutPerspectiveEffects();
    ORTLine[] prjLines = lines.getLinesWRTBaseLine();

    // get the x1 and x2 of the projected lines
    int[] qPos1= new int[prjLines.length];
    int[] qPos2= new int[prjLines.length];
    for (int i=0;i<qPos1.length;i++) {
        qPos1[i] = (int)Math.round(prjLines[i].Start.Col);
        qPos2[i] = (int)Math.round(prjLines[i].End.Col);
        if (qPos1[i] > qPos2[i]) {
          int swap = qPos1[i]; qPos1[i] = qPos2[i]; qPos2[i] = swap;
        }
    }

    // get the range of the position
    int min = yi.stat.StatUtil.minimum(qPos1);
    int max = yi.stat.StatUtil.maximum(qPos2);

    // construct the beginning and ending point histogram
    int numBin = max-min+1;
    int[] h_begin= new int[numBin];
    int[] h_end= new int[numBin];
    for (int i=0; i<lines.size(); i++) {
        int index_begin = qPos1[i] - min;
        h_begin[index_begin] ++;
        int index_end = qPos2[i] - min;
        h_end[index_end] ++;
    }

    // construct the horizontal position histogram
    int[] density = new int[numBin];
    int now=0;
    for (int i=0; i<numBin; i++) {
        now += h_begin[i];
        if (i>0) now -= h_end[i-1];
        density[i]=now;
    }

    // tolerance uses the average length
    double lenSum = 0;
    for (int i=0; i<lines.size(); i++) lenSum += (qPos2[i] - qPos1[i]);
    double tolerance = suitableTolerance(
      lenSum / lines.size(),
      (double)numBin / lines.size(),
      Math.max(lines.getImageHeight(), lines.getImageWidth()));

    // clustering based on the histogram using the tolerance
    OneDClusters clusters = OneDHistBasedCluster.breakWithTolerance(density, tolerance, 0);

    // adjust the cluster data range
    DataRange[] ranges = clusters.getClusters();
    for (int i=0; i<ranges.length; i++) ranges[i].shift(min);

    // result line cluster sets
    LineClusterSets line_clusters = new LineClusterSets(width, height);
    Iterator iter = lines.iterator();
    int count = 0;
    while (iter.hasNext()) {
      ORTLine l = (ORTLine)iter.next();
      // which cluster the line belongs to
      int label1 = clusters.getLabel(qPos1[count]); // beginning point belongs to
      int label2 = clusters.getLabel(qPos2[count]); // ending point belongs to
      if (label1 >= 0 && label1 == label2) {
        line_clusters.add(ranges[label1], l);
      }
      count ++;
    }

    // filter the result
    line_clusters.filter(
      CLCConfig.getMinQualifiedLineLen(),
      CLCConfig.getMinQualifiedCLCLineLenSum(lines.getImageWidth(), lines.getImageHeight()));

    // inherit the properties from the parent (clustered from)
    line_clusters.inheritProperties(lines);

    // demo the results
    if (DEMO_HORIZONTAL_CLUSTERING) {
      int[] cluster_img = lines.demo();
      int[] clusters_img = line_clusters.demo();
//      OutputResult.print("horizontal_histogram", density);
      OutputResult.outputGIFImage("a_horizontal_clustering", cluster_img, lines.getImageWidth(), lines.getImageHeight(), true);
      OutputResult.outputGIFImage("b_horizontal_clustering", clusters_img, lines.getImageWidth(), lines.getImageHeight(), true);
    }

    return line_clusters;
  }

  public double TOLERANCE_2_AVERAGE_LEN = 1.5;

  /**
   * Find the suitable tolerance for histogram based clustering on horizontal
   * position.  Exclusively used by clusteringByHorizontalPosition
   * @param average_len the average length of the lines
   * @param theta the orientation of the base line
   * @return
   */
  private double suitableTolerance(double average_len, double average_occupy_len, int image_size) {

    double tolerance = Math.max(average_len, average_occupy_len) * TOLERANCE_2_AVERAGE_LEN;

    // can't be too small or too large
    tolerance = Math.max(
      tolerance,
      (int)Math.round(image_size * MIN_HIST_BREAKING_TOLERANCE));
    tolerance = Math.min(
      tolerance,
      (int)Math.round(image_size * MAX_HIST_BREAKING_TOLERANCE));

//    //adjust the tolerance according to the angle of the virtual line
//    if (theta < 0) theta += Math.PI;
//    if (theta > Math.PI) theta -= Math.PI;
//    if (theta > Math.PI/2) theta -= Math.PI/2;
//    theta = Math.min(theta, Math.PI/2 - theta);
//    tolerance /= Math.cos(theta);
//
//    // tolerance is made bigger, so need to see whether it is too big again
//    tolerance = Math.min(
//      tolerance,
//      (int)Math.round(image_size * MAX_HIST_BREAKING_TOLERANCE));

    return tolerance;
  }
}