package yi.iu.processor.clc;

import java.util.*;
import yi.iu.processor.ort.*;

/**
 * Calculate and store the intrarelationship of a line cluster.  The saved
 * information includes overlapping line numbers, overlapping line length total
 * and overlapping line weighted length total
 */
public class Intrarelationship implements java.io.Serializable{

  // when the lines are overlapped, their midpoints will not be exactly on one
  // line due to the different reasons.  This parameter is defined to make sure
  // the overlapping threshold will consider this effect.  The overlapping
  // threshold is basically the sqrt(size of line cluster).
  public static double MIDPOINT_SHIFT = 0.75;

  public int m_overlappingLineNum;
  public double m_overlappingLineWeightedLenTotal, m_overlappingLineLenTotal;
  public LineClusterSet m_overlappingLines;

  /**
   * To improve the efficient, when finding the overlapping theta hypothesis,
   * only the longest lines are considered
   */
  public static int MAX_LINE_NUM_4_FINDING_OVERLAP_THETA = 16;

  /**
   * When sorting the lines to find the overlapping theta  of a line cluster,
   * if the length of a line is lower than STANDARD_LINE_LEN, but the number of
   * lines are still lower than MIN_LINE_NUM_4_FINDING_CONVERGING_POINT, it will
   * still be used althgouh it may be not that reliable.
   */
  public static int MIN_LINE_NUM_4_FINDING_OVERLAP_THETA = 8;

  /**
   * The lines must be overlapped by other length consistent lines
   */
  public static boolean LENGTH_CONSISTENT_FORCED = false;

  public Intrarelationship(int overlappingLineNum,
    double overlappingLineLenTotal, double overlappingLineWeightedLenTotal,
    LineClusterSet overlappingLines) {
      m_overlappingLineNum = overlappingLineNum;
      m_overlappingLineWeightedLenTotal = overlappingLineWeightedLenTotal;
      m_overlappingLineLenTotal = overlappingLineLenTotal;
      m_overlappingLines = overlappingLines;
  }

  /**
   * Construct the intrarelationship of the given line cluster
   * @param lc
   * @param overlap_threshold when a line is considered as heavily overlapped,
   *                          it must be overlapped by enough number of lines.
   *                          that threshold is
   *                          max(overlap_threshold, sqrt(size of lc))
   * @return
   */
  public static Intrarelationship intrarelation(LineClusterSet lc, int overlap_threshold) {
    LineClusterSet overlapped = lc.newInstanceWithSameProperties();

    overlap_threshold = Math.max(overlap_threshold, lc.size());

    if (lc.size() <= 0) {
      return new Intrarelationship(0, 0, 0, overlapped);
    }

    while (lc.size() > 0) {
      overlapped = pickupHeavilyOverlappedLines(lc, overlap_threshold);
      if (lc.size() == overlapped.size()) break;
      lc = overlapped;
    }

    return new Intrarelationship(
        overlapped.size(),
        overlapped.getTotalLen(),
        overlapped.getTotalWeightedLength(),
        overlapped);
  }

  public static Intrarelationship intrarelation2(LineClusterSet lc,
                                                double overlap_threshold) {
    overlap_threshold = (Math.max(overlap_threshold,Math.sqrt(lc.size()))-1)*MIDPOINT_SHIFT;
    LineClusterSet filtered = lc.newInstanceWithSameProperties();

    // recalculate the base line
    lc.getBaseLine(false);

    // get the projected lines
//    ORTLine[] prjLines = lc.getLinesWithoutPerspectiveEffects();
    ORTLine[] prjLines = new ORTLine[lc.size()];
    int i_count = 0;
    for (Iterator i=lc.iterator(); i.hasNext();) prjLines[i_count++] = (ORTLine)i.next();

    // first sort the lines according to their length in the line cluster since
    // the longer a line, the more reliable perspective information it can
    // provide.  Only the longest MAX_LINE_NUM_4_FINDING_OVERLAP_THETA lines
    // are considered
    int line_2_use = 0;
    for (line_2_use = 0; line_2_use < prjLines.length && line_2_use < MAX_LINE_NUM_4_FINDING_OVERLAP_THETA;
         line_2_use ++) {
      ORTLine line1 = prjLines[line_2_use];
      //get the i-th longest
      for (int j = line_2_use + 1; j < prjLines.length; j++) {
        ORTLine line2 = prjLines[j];
        if (line1.getLength() < line2.getLength()) {
          prjLines[j] = line1;
          line1 = line2;
        }
      }
      prjLines[line_2_use] = line1;
      // stop the sorting if the length of the line is less than the standard (
      // they are not that useful anymore).  This is to save the sorting time
      if (line1.getLength() < CLCConfig.getNormalLineLength() &&
          line_2_use >= MIN_LINE_NUM_4_FINDING_OVERLAP_THETA)
        break;
    }

    // cache the middle points of the projected lines (for later usage)
    ORTPoint[] mid = new ORTPoint[prjLines.length];
    for (int i = 0; i < mid.length; i++) {
      mid[i] = prjLines[i].midPoint();
    }

    // cache the half of the length of the projected lines (for later usage)
    double[] half_len = new double[prjLines.length];
    for (int i = 0; i < half_len.length; i++) {
      half_len[i] = prjLines[i].getLength()/2;
    }

    double[] weight = new double[prjLines.length];
    for (int i = 0; i < weight.length; i++) {
      weight[i] = CLCUtil.lineWeight(prjLines[i].getLength());
    }


    double best_score = 0;
    double best_theta = 0;
    for (int i = 0; i < line_2_use; i++) {
      for (int j = 0; j < line_2_use; j++) {
        if (i==j) continue;
        // construct a possible direction for each pair of the lines
        double theta = ORTLine.LineSegTheta(mid[i].Col, mid[i].Row, mid[j].Col, mid[j].Row);
        // vote for that orientation
        double score = 0;
        for (int m=0; m<prjLines.length; m++) {
          double score_m = 0;
          ORTLine hypothesis = new ORTLine(mid[m], theta, 255); // the length doesn't matter
          for (int n=0; n<prjLines.length; n++) {
            if (n==m) continue;
            // calculate the distance from the middle point of line "n" to
            // the possible "theta"
            // first get the intercepting point
            ORTPoint intercept = new ORTPoint();
            boolean intercepted = GeometricUtil.getInterceptPoint(hypothesis, prjLines[n], intercept);
            // then get the distance from the intercepting point to its middle point
            // if no intercept, then they are on the same line
            // construct a score (1 is the best, 0 is nothing)
            double a_score = 0;
            if (intercepted) {
              double d = GeometricUtil.distance(intercept, mid[n]);
              if (d <= half_len[n]) a_score = 1 - d / half_len[n];
            }
            // 0 means the two line segments are on the same line (the best)
            double d2 = GeometricUtil.PtLinePerpDistance(mid[n], mid[m], theta);
            if (d2 < 1 && a_score < 1-d2 ) {
                a_score = 1 - d2;
            }
            score_m += a_score * weight[n];
          }
          score += score_m * weight[m];
        }
        if (score > best_score) {
          best_score = score;
          best_theta = theta;
        }
      }
    }

    if (best_score <= 0) {
      return new Intrarelationship(0, 0, 0, filtered);
    }

    // find those lines overlapped by others
    for (int m = 0; m<prjLines.length; m++) {
//    for (Iterator i = lc.iterator(); i.hasNext(); m++) {
      ORTLine line = prjLines[m];
      double score = 0;
      ORTLine hypothesis = new ORTLine(mid[m], best_theta, 255); // the length doesn't matter
      for (int n=0; n<prjLines.length; n++) {
        if (n==m) continue;
        if (LENGTH_CONSISTENT_FORCED && !consistentLength(half_len[m], half_len[n])) continue;
        // calculate the distance from the middle point of line "n" to
        // the possible "theta"
        // first get the intercepting point
        ORTPoint intercept = new ORTPoint();
        GeometricUtil.getInterceptPoint(hypothesis, prjLines[n], intercept);
        // then get the distance from the intercepting point to its middle point
        double d = GeometricUtil.distance(intercept, mid[n]);
        if (d <= half_len[n]) { // really get overlapped
          // construct a score (1 is the best, 0 is nothing)
          double a_score = 1 - d / half_len[n];
          score += a_score * weight[n];
        }
      }
      score *= weight[m];
      if (score >= overlap_threshold) {
        filtered.add(line);
      }
    }

    return new Intrarelationship(
        filtered.size(),
        filtered.getTotalLen(),
        filtered.getTotalWeightedLength(),
        filtered);

  }


  private static LineClusterSet pickupHeavilyOverlappedLines(LineClusterSet lc, int Tonline) {

    LineClusterSet filtered = lc.newInstanceWithSameProperties();

    // recalculate the base line
    lc.getBaseLine(false);

    // get the projected lines
    ORTLine[] prjLines = lc.getLinesWithoutPerspectiveEffects();

    ORTPoint[] mid = new ORTPoint[prjLines.length];
    for (int i = 0; i < mid.length; i++) {
      mid[i] = prjLines[i].midPoint();
    }

    //get the average width of the length
    double mean_len = 0;
    for (int i = 0; i < prjLines.length; i++) {
      mean_len += prjLines[i].getLength();
    }
    mean_len = mean_len / prjLines.length;

    //get the upper center and the bottom center
    double ux = 0, uy = 0;
    double uc = 0;
    double bx = 0, by = 0;
    double bc = 0;
    for (int i = 0; i < prjLines.length; i++) {
      ORTLine line = prjLines[i];
      if (line.getCentroidY() > 0) {
        ux += line.getCentroidX() * line.getLength();
        uy += line.getCentroidY() * line.getLength();
        uc += line.getLength();
      }
      else if (line.getCentroidY() < 0) {
        bx += line.getCentroidX() * line.getLength();
        by += line.getCentroidY() * line.getLength();
        bc += line.getLength();

      }
    }

    if (uc > 0) {
      ux /= uc;
      uy /= uc;
    }
    else {
      ux = 0;
      uy = 10;
    }
    if (bc > 0) {
      bx /= bc;
      by /= bc;
    }
    else {
      bx = 0;
      by = -10;
    }

    // the orientation of the line connecting the upper middle point and the
    // lower middle point
    double theta = ORTLine.LineSegTheta(ux, uy, bx, by);
    double Tdistance = mean_len / 2 * Math.sin(theta);
    /*
       if (theta<Math.PI/6||theta>Math.PI*5/6) {
        lines.removeAllElements();
        return;
       }
     */
    /*
      Dimension dm= new Dimension(400,400);
      int[] img= new int[dm.width*dm.height];
      for (i=0;i<img.length;i++) img[i]=0xffffffff;
      DrawUtil.ort2image(img, dm,
              new ORTLine(-120,0,120,0),0xff00ff00, 1);
      DrawUtil.ort2image(img, dm,
              new ORTLine(0,-100,0,100),0xff00ff00, 1);
      for (i=0;i<prjLines.length;i++) {
          DrawUtil.ort2image(img, dm,
         new ORTLine(prjLines[i][0], prjLines[i][2], prjLines[i][1], prjLines[i][2]),
              0xff000000, 1);
      }
      DrawUtil.ort2image(img, dm,
              new ORTLine(ux, uy, bx, by),
              0xff0000ff, 1);
      DrawUtil.drawMark(img, dm, ux, uy, 4, 0xffff0000);
      DrawUtil.drawMark(img, dm, bx, by, 4, 0xffff0000);
      (new DisplayFrame("theta", img, dm)).show();
//*/
     boolean[] verified = new boolean[prjLines.length];
    int verfiedc = 0;
    //connect every two middle point
    for (int i = 0; i < prjLines.length; i++) {
      ORTLine l1 = prjLines[i];
      double length1 = l1.getLength();
      int count = 0;
      for (int j = 0; j < prjLines.length; j++) {
        ORTLine l2 = prjLines[j];
        double length2 = l2.getLength();
        double d = GeometricUtil.PtLinePerpDistance(
            l2.midPoint(), l1.midPoint(), theta);
        //if (d<=Tdistance) count++;
        //if (d<=(length2+1)/2*Math.sin(theta))
        //if (d<=(length2+1)/2*Math.sin(theta)
        //    && (Math.abs(length2-length)<3))
        if (d <= (length2 + 1) / 2 * Math.sin(theta) &&
            (!LENGTH_CONSISTENT_FORCED || consistentLength(length1, length2)))
          count++;
      }
      if (count >= Tonline) {
        verified[i] = true;
        verfiedc++;
      }
    }
    if (false)
      System.err.println("verified " + verfiedc + "/" + verified.length);

    int i_count = 0;
    for (Iterator i = lc.iterator(); i.hasNext(); i_count++) {
      ORTLine l = (ORTLine) i.next();
      if (verified[i_count])
        filtered.add(l);
    }
    return filtered;
  }

  private static boolean consistentLength(double l1, double l2) {
    return (l2 >= l1 * 3 / 4 && l2 <= l1 * 4 / 3);
  }

}
