package yi.math;

public class MathEllipse {
  /**
   * centroid x and y
   */
  double x, y;
  /**
   * major length, minor length
   */
  double a, b;
  /**
   * angle of major axis, in radians
   */
  double angle;

  public MathEllipse(double x, double y, double a, double b, double angle) {
    this.x = x;
    this.y = y;
    this.a = a;
    this.b = b;
    this.angle = angle;
  }

  /**
   * Calculate the overlap area of this ellipse and the given ellipse.  The
   * method returns approximate result.  It first transforms the ellipse to a
   * normalized form with centroid point on the coordinate origin and major
   * axis align with the x-axis.  Within the outer boundary of the ellipse,
   * x_num * y_num reference points are considered whether they are fallen into
   * the given ellipse.  The percentage of referecne points fallen into the
   * given ellipse are returned.
   * @param e the given ellipse
   * @param x_num the number of the reference points on the x-direction, better
   *              be odd number.  e.g. 11
   * @param y_num the number of the reference points on the y-direction, better
   *              be odd number.  e.g. 11
   * @return the percentage
   */
  public double overlap(MathEllipse e, int x_num, int y_num) {
    double step_x = 2.0 * a / (x_num - 1);
    double step_y = 2.0 * b / (y_num - 1);

//    // demonstration
//    boolean demo = true;
//    int img_width = 256, img_height = 171;
//    int[][] img = new int[img_height][img_width];

    int inside_num = 0;
    int overlap_num = 0;
    for (int i=0; i<x_num; i++) {
      for (int j=0; j<y_num; j++) {
        // normalized coordinators
        double ref_x = step_x * i - a;
        double ref_y = step_y * j - b;

//        if (demo) {
//          MathPoint2D ref_p = MathTransform2D.rotate(ref_x, ref_y, angle);
//          ref_p = MathTransform2D.shift(ref_p, x, y);
//          int y1 = (int)ref_p.y, x1 = (int)ref_p.x;
//          if (y1>=0 && y1<img_height && x1>=0 && x1<img_width) img[y1][x1] = 1;
//        }

        if (!inside(a, b, ref_x, ref_y)) continue;

        inside_num ++;  // reference point inside the first ellipse

        // transform the point to real coordinators
        MathPoint2D ref_p = MathTransform2D.rotate(ref_x, ref_y, angle);
        ref_p = MathTransform2D.shift(ref_p, x, y);

        // whether inside the second sllipse too
        if (inside(e, ref_p.x, ref_p.y)) overlap_num ++;

//        if (demo) {
//          int y1 = (int)ref_p.y, x1 = (int)ref_p.x;
//          if (inside(e, ref_p.x, ref_p.y)) {
//            if (y1>=0 && y1<img_height && x1>=0 && x1<img_width) img[y1][x1] = 3;
//          } else {
//            if (y1>=0 && y1<img_height && x1>=0 && x1<img_width) img[y1][x1] = 2;
//          }
//        }
      }
    }
//    if (demo) {
//      int[] img1D = yi.util.data.Conversion.from2Dto1D(img, img_width, img_height);
//      img1D = yi.util.ImageUtil.makeNewImageFromLabel(img1D);
//      yi.util.io.OutputResult.outputGIFImage("overlap", img1D, img_width, img_height, true);
//    }
    return (double)overlap_num/(double)inside_num;
  }

  /**
   * Decide whether a point given by (px, py) is inside a ellipse given by major
   * axis of a and minor axis of b.  If the point is on the bounday, return
   * true.
   * @param a major axis
   * @param b minor axis
   * @param px the x component of the point
   * @param py the y component of the point
   * @return whether the given point is inside the given ellipse
   */
  public boolean inside(double a, double b, double px, double py) {
    return (px * px) / (a * a) + (py * py) / (b * b) <= 1;
  }

  public boolean inside(MathEllipse e, double px, double py) {
    MathPoint2D p = MathTransform2D.shift(px, py, -e.x, -e.y);
    p = MathTransform2D.rotate(p, -e.angle);
    return inside(e.a, e.b, p.x, p.y);
  }

  public String toString() {
    return "centroid = (" + x + "," + y + "), a = " + a + ", b = " + b + ", angle = " + angle;
  }

  /**
   * Test overlap method
   */
  public static class TestOverlap {
    public static void main(String[] args) {
      MathEllipse e1 = new MathEllipse(-10, -20, 50, 30, Math.PI * 0.25);
      MathEllipse e2 = new MathEllipse(0, 10, 75, 40, Math.PI * 0.75);
//      MathEllipse e3 = new MathEllipse(-70, 50, 50, 48, Math.PI * 0.5);
      System.out.println(e1.overlap(e2, 11, 11));
      System.out.println(e2.overlap(e1, 11, 11));
//      System.out.println(e1.overlap(e3, 11, 11));
//      System.out.println(e3.overlap(e1, 11, 11));
//      System.out.println(e2.overlap(e3, 11, 11));
//      System.out.println(e3.overlap(e2, 11, 11));

      e1 = new MathEllipse(97.47740936279297,48.24365997314453,284.6882580205689/2,128.79993738435581/2,0.3912381356460461);
      e2 = new MathEllipse(99.93910217285156,151.08653259277344,134.96077974699574/2,46.259902238146864/2,0.04974723974075508);
      System.out.println(e1.overlap(e2, 11, 11));
      System.out.println(e2.overlap(e1, 11, 11));

      System.exit(0);
    }
  }
}