package yi.iu.processor.clc;

import java.util.*;

import yi.ds.*;
import yi.util.config.*;
import yi.util.io.*;
import yi.util.log.*;

/**
 * Construct Orientation Consistent Line Cluster.
 */
public class CLC_Interrelationship extends CLC_Clustering
{
  public boolean DEMO_MERGE = false;

  /**
   * Merge regions having intersections together.  The merge is transitive.  (in
   * other words, if region1 intersects region2, and region2 intersects region3,
   * then those 3 regions will be merged together).  The Returned line
   * clusters include all the line (even those not intersecting with others)
   */
  public final static int MERGE_ALL_TOGETHER  = 1;
  /**
   * Merge each pair of regions having intersections.  The Returned line
   * clusters include only those intersecting lines)
   */
  public final static int MERGE_INTERSECTING_RGN_PAIR  = 2;
  /**
   * Merge regions having intersections together.  The merge is transitive.  (in
   * other words, if region1 intersects region2, and region2 intersects region3,
   * then those 3 regions will be merged together).  The Returned line
   * clusters include only those intersecting lines)
   */
  public final static int MERGE_INTERSECTING_LINES = 3;

  /**
   * Dont' merge.  Instead, taking inter-relationship as a filter.  Only those
   * clusters having intersection with others will be kept.
   */
  public final static int MERGE_NONE = 4;


  public final static int MERGE_AFTER_FILTER = 5;

  public int MERGE_OPTION = MERGE_INTERSECTING_LINES;

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

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

  /**
   * Since interrelationship is based on multiple line clusters, this method
   * is useless
   * @param lines
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters(LineClusterSet lines, int[] rgb, int width, int height) {
    String error_msg = "CLC_Interrelationship.getClusters(LineClusterSet, ...) is meaningless " +
      " since it must based on multiple line clusters";
    LogHandler.log(this, LOG_LEVEL, 1, error_msg);
    throw new RuntimeException(error_msg);
  }


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

    switch (MERGE_OPTION) {
      case MERGE_ALL_TOGETHER:
        return getClusters1(line_clusters, rgb, width, height);
      case MERGE_INTERSECTING_RGN_PAIR:
        return getClusters2(line_clusters, rgb, width, height);
      case MERGE_INTERSECTING_LINES:
        return getClusters3(line_clusters, rgb, width, height);
      case MERGE_NONE:
        return getClusters4(line_clusters, rgb, width, height);
      case MERGE_AFTER_FILTER:
        return getClusters5(line_clusters, rgb, width, height);
    }
    return null;
  }

  /**
   * Clustering line clusters based on interrelationship.  Those clusters having
   * lots of intersections will be merged into one cluster.
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters1(LineClusterSets line_clusters, int[] rgb, int width, int height) {

    int lcNum= line_clusters.size();  //the number of line cluster

    Interrelationship[][] relations = new Interrelationship[lcNum][lcNum];

    // convert lcs to array for each operations
    LineClusterSet[] lcs = new LineClusterSet[lcNum];
    Iterator it = line_clusters.values().iterator();
    int it_count = 0;
    while (it.hasNext()) lcs[it_count++] = (LineClusterSet)it.next();

    // merge two clusters if most lines of one are intersect with another
    Merge merge=new Merge(lcNum);
    for (int i=0; i<lcNum; i++) {
        for (int j = i + 1; j < lcNum; j++) {
          relations[i][j] = Interrelationship.interrelation(lcs[i], lcs[j]);
          if (inter_related(relations[i][j])) {
            merge.merge(i, j);
            // demo the merge
            if (DEMO_MERGE) {
              int w = lcs[i].getImageWidth(), h = lcs[i].getImageHeight();
              int[] lineimg = new int[w*h];
              java.util.Arrays.fill(lineimg, 0xff000000);
              lcs[i].draw(lineimg, w, h, 0xffffffff);
              lcs[j].draw(lineimg, w, h, 0xffff7f7f);
              OutputResult.outputGIFImage("merge." + i + "." + j + ".gif", lineimg, w, h);
            }
          }
        }
    }

    // construct the merging results.  Those clusters not merging with others will be
    // discarded
    int[] mergeRst= merge.result();
    LineClusterSets[] mrg_rst = new LineClusterSets[lcs.length];
    boolean[] roots = merge.getRoots();
    for (int i = 0; i < lcs.length; i ++) {
        LineClusterSet lc = lcs[i];
        int newIndex = mergeRst[i];
        if (! roots[newIndex]) continue;  // it didn't merged with others
        if (mrg_rst[newIndex] == null) mrg_rst[newIndex] = new LineClusterSets(width, height);
        mrg_rst[newIndex].put(lc, lc);    // put in itself
    }

    // construct the results
    LineClusterSets rst = new LineClusterSets(width, height);
    for (int i = 0; i < mrg_rst.length; i ++) {
      if (mrg_rst[i] != null) {
        LineClusterSet intersecting_lc = new LineClusterSet(mrg_rst[i]);
        rst.put(intersecting_lc, intersecting_lc);
      }
    }
    return rst;
  }

  /**
   * Clustering line clusters based on interrelationship.  Those clusters having
   * lots of intersections will be merged into one cluster.
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters2(LineClusterSets line_clusters, int[] rgb, int width, int height) {

    int lcNum = line_clusters.size(); //the number of line cluster

    Interrelationship[][] relations = new Interrelationship[lcNum][lcNum];

    // convert lcs to array for each operations
    LineClusterSet[] lcs = new LineClusterSet[lcNum];
    Iterator it = line_clusters.values().iterator();
    int it_count = 0;
    while (it.hasNext())
      lcs[it_count++] = (LineClusterSet) it.next();

    LineClusterSets rst = new LineClusterSets(width, height);

    // merge two clusters if most lines of one are intersect with another
    for (int i = 0; i < lcNum; i++) {
      for (int j = i + 1; j < lcNum; j++) {
        relations[i][j] = Interrelationship.interrelation(lcs[i], lcs[j]);
        if (inter_related(relations[i][j])) {
          LineClusterSet intersecting_lines1 = relations[i][j].m_intersectingLines1;
          LineClusterSet intersecting_lines2 = relations[i][j].m_intersectingLines2;
          // make sure line set 1 is "bigger" than set 2
          if (intersecting_lines1.getTotalWeightedLength() <
              intersecting_lines2.getTotalWeightedLength()) {
            LineClusterSet temp = intersecting_lines1;
            intersecting_lines1 = intersecting_lines2;
            intersecting_lines2 = temp;
          }
          // put them together
          LineClusterSet merged = (LineClusterSet)intersecting_lines1.clone();
          merged.addAll(intersecting_lines2);
          // add merged to result
          rst.put(merged, merged);
        }
      }
    }

    return rst;
  }

  /**
   * Clustering line clusters based on interrelationship.  Those clusters having
   * lots of intersections will be merged into one cluster.
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters3(LineClusterSets line_clusters, int[] rgb, int width, int height) {

    int lcNum = line_clusters.size(); //the number of line cluster

    Interrelationship[][] relations = new Interrelationship[lcNum][lcNum];

    // convert lcs to array for each operations
    LineClusterSet[] lcs = new LineClusterSet[lcNum];
    Iterator it = line_clusters.values().iterator();
    int it_count = 0;
    while (it.hasNext())
      lcs[it_count++] = (LineClusterSet) it.next();

    // to store the intersecting lines
    LineClusterSet[] intersect_lcs = new LineClusterSet[lcNum];
    for (int i= 0; i < lcNum; i++) {
      intersect_lcs[i] = lcs[i].newInstanceWithSameProperties();
    }

    // merge two clusters if most lines of one are intersect with another
    Merge merge = new Merge(lcNum);

    for (int i = 0; i < lcNum; i++) {
      for (int j = i + 1; j < lcNum; j++) {
        relations[i][j] = Interrelationship.interrelation(lcs[i], lcs[j]);
        if (inter_related(relations[i][j])) {
          merge.merge(i, j);
          intersect_lcs[i].addAll(relations[i][j].m_intersectingLines1);
          intersect_lcs[j].addAll(relations[i][j].m_intersectingLines2);
//          System.out.println("" + i + ":" + j + ": " + relations[i][j]);
        }
      }
    }

    // construct the merging results.  Those clusters not merging with others will be
    // discarded
    int[] mergeRst= merge.result();
//    OutputResult.print(System.out, mergeRst);
    LineClusterSets[] mrg_rst = new LineClusterSets[lcs.length];
    boolean[] roots = merge.getRoots();
    for (int i = 0; i < lcs.length; i ++) {
        LineClusterSet lc = intersect_lcs[i];
        int newIndex = mergeRst[i];
        if (! roots[newIndex]) continue;  // it didn't merged with others
        if (mrg_rst[newIndex] == null) mrg_rst[newIndex] = new LineClusterSets(width, height);
        mrg_rst[newIndex].put(lc, lc);    // put in itself
    }

    // construct the results
    LineClusterSets rst = new LineClusterSets(width, height);
    for (int i = 0; i < mrg_rst.length; i ++) {
      if (mrg_rst[i] != null) {
        LineClusterSet intersecting_lc = new LineClusterSet(mrg_rst[i]);
        rst.put(intersecting_lc, intersecting_lc);
      }
    }
    rst = postProcess(rst, rgb, width, height);
    return rst;
  }

  /**
   * Filtered each cluster using inter-relation criteria.  Compared to
   * the implementation of getClusters3, they are the same except that this one
   * don't have the step of merge.
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets getClusters4(LineClusterSets line_clusters, int[] rgb, int width, int height) {

    int lcNum = line_clusters.size(); //the number of line cluster

    Interrelationship[][] relations = new Interrelationship[lcNum][lcNum];

    // convert lcs to array for each operations
    LineClusterSet[] lcs = new LineClusterSet[lcNum];
    Iterator it = line_clusters.values().iterator();
    int it_count = 0;
    while (it.hasNext())
      lcs[it_count++] = (LineClusterSet) it.next();

    // to store the intersecting lines
    LineClusterSet[] intersect_lcs = new LineClusterSet[lcNum];
    for (int i= 0; i < lcNum; i++) {
      intersect_lcs[i] = lcs[i].newInstanceWithSameProperties();
    }

    for (int i = 0; i < lcNum-1; i++) {
      for (int j = i + 1; j < lcNum; j++) {
        relations[i][j] = Interrelationship.interrelation(lcs[i], lcs[j]);
        if (inter_related(relations[i][j])) {
          intersect_lcs[i].addAll(relations[i][j].m_intersectingLines1);
          intersect_lcs[j].addAll(relations[i][j].m_intersectingLines2);
        }
      }
    }

    // construct the results
    LineClusterSets rst = new LineClusterSets(width, height);
    for (int i = 0; i < lcs.length; i ++) {
      LineClusterSet lc = intersect_lcs[i];
      if (lc.size() > 0) {
        rst.put(lc, lc);
      }
    }

    // filter the result
    rst.filter(
        CLCConfig.getMinQualifiedLineLen(),
        CLCConfig.getMinQualifiedCLCLineLenSum(width, height));

    rst = postProcess(rst, rgb, width, height);
    return rst;
  }

  public LineClusterSets getClusters5(LineClusterSets line_clusters, int[] rgb, int width, int height) {
    LineClusterSets rst = getClusters4(line_clusters, rgb, width, height);
    return getClusters3(rst, rgb, width, height);
  }

//  boolean inter_related(int s, int t) {
//      if (s>=8  && (double)t/s > 0.52) return true;
//      if (s>=19 && (double)t/s > 0.10) return true;
//    return false;
//  }

  /*
   * s: size of the cluster
   * t: intersecting line number
   * rules:
   *      Ninter(10) > 34% and Ninter(19)>10% => building
   *      Ninter(10) > 34% and Ninter(8) > 52% => building
  */
  boolean inter_related(int s, int t) {
    if (s <= 2) return false;
    if (s>=5 && (double)t/s >= 0.5) return true;
    if (s>=10 && (double)t/s >= 0.33) return true;
    return false;
  }
//
//  /**
//   * Ninter(8)>80, or Ninter(14)>60 or Ninter(30)>31
//   * @param s
//   * @param t
//   * @return
//   */
//  boolean inter_related(int s, int t) {
//    if (s>=8 && (double)t/s >= 0.8) return true;
//    if (s>=14 && (double)t/s >= 0.6) return true;
//    if (s>=30 && (double)t/s >= 0.31) return true;
//    return false;
//  }

  boolean inter_related(Interrelationship relation) {
    int s1 = relation.m_lineNum1;
    int s2 = relation.m_lineNum2;
    int t1 = relation.m_intersectingLineNum1;
    int t2 = relation.m_intersectingLineNum2;
    return inter_related(s1, s2, t1, t2);
  }

  boolean inter_related(int s1, int s2, int t1, int t2) {
    if (inter_related(s1, t1) || inter_related(s2, t2)) return true;
    return false;
  }
  /**
   * Merge those clusters having common lines
   * @param line_clusters
   * @param rgb
   * @param width
   * @param height
   * @return
   */
  public LineClusterSets postProcess(LineClusterSets line_clusters, int[] rgb, int width, int height) {
//    line_clusters = LineClusterSets.mergeThoseHaveCommon(line_clusters);
    return line_clusters;
  }

}