package yi.iu.processor.ort;

import java.io.*;
import java.awt.*;
import java.util.*;
import yi.util.io.OutputResult;
import yi.iu.processor.color.ColorUtil;

public class ORTLine implements java.io.Serializable
{
  int 	  ID;	  	/* ID number associated with the line */
  int 	  StringID;	/* ID of String from which the line was extracted*/
  public ORTPoint Start;	/* Start point of the line */
  public ORTPoint End;	  	/* End point of the line */
  /**
   * Midpoint of the line
   * @deprecated.  Should be generated on fly
   */
  public ORTPoint MidPoint;
  public double	Length;		/* Length of the line in pixels */
  public double	LengthParlVar;	/* Variance in the length of the line */
          /* parallel to the direction of the line (empirical) */
  public double	LengthPerpVar;	/* Variance in the length of the line */
          /* perpendicular to the direction of the line (empirical) */
  public double	Theta;		  	/* Orientation of the line pi < Theta <= 0 */
  public double	ThetaVar;		/* Variance in the line orientation (empirical) */
  public ORTLine()
  {
      Start=new ORTPoint();	/* Start point of the line */
      End=new ORTPoint();	  	/* End point of the line */
  }

  public static double _LengthParlVar = Math.sqrt(2.0);
  public static double _LengthPerpVar = Math.sqrt(2.0);

  public ORTLine(double x1, double y1, double x2, double y2)
  {
      Start= new ORTPoint(x1, y1);
      End=new ORTPoint(x2, y2);
      double dx=x1-x2, dy=y1-y2;
      Length= Math.sqrt(dx*dx+dy*dy);

      LengthParlVar  = 2.0*_LengthParlVar;
      LengthPerpVar  = _LengthPerpVar;
      Theta= LineSegTheta(x1,y1,x2,y2);
      ThetaVar 	= LineSegThetaVar(Length);

  }

  public void recal()
  {
      ThetaVar = LineSegThetaVar(Length);
  }

  public ORTLine(ORTPoint p1, ORTPoint p2)
  {
      this(p1.Col,p1.Row,p2.Col,p2.Row);
  }

  /**
   * Construct a new ORTLine with given middle point, given line direciton and
   * given length.
   * @param mid_point
   * @param line_direction
   * @param length
   */
  public ORTLine(ORTPoint mid_point, double line_direction, double length) {
    this(
      mid_point.Col + length * Math.cos(line_direction) / 2,
      mid_point.Row + length * Math.sin(line_direction) / 2,
      mid_point.Col - length * Math.cos(line_direction) / 2,
      mid_point.Row - length * Math.sin(line_direction) / 2);
  }

  public double getCentroidX() {
    return (Start.Col + End.Col) / 2;
  }

  public double getCentroidY() {
    return (Start.Row + End.Row) / 2;
  }

  /**
   * Return the middle point of the line
   * @return
   */
  public ORTPoint midPoint() {
    return new ORTPoint(
      (Start.Col + End.Col) / 2,
      (Start.Row + End.Row) / 2);
  }
  //make a new line.  The end points of the new line keep the same orientation of old line
  //and take the "distance" to the old end points.  The line connecting the old end point and
  //the new end point takes "angle" to the orientation of the lines
  /*
  public ORTLine makeNewLineByAngleShift(double distance, double angle)
  {
      double shiftAngle= Theta+angle;
      double sx= Math.cos(shiftAngle)*distance;
      double sy= Math.sin(shiftAngle)*distance;
      ORTLine l= new ORTLine();
      l.Start= new ORTPoint(Start.Col+sx, Start.Row+sy);
      l.End= new ORTPoint(End.Col+sx, End.Row+sy);
      l.MidPoint= new ORTPoint(MidPoint.Col+sx, MidPoint.Row+sy);
      l.Length= Length;
      l.LengthParlVar  = LengthParlVar;
      l.LengthPerpVar  = LengthPerpVar;
      l.Theta= Theta;
      l.ThetaVar 	= ThetaVar;
        l.ID= ID;
        l.StringID= StringID;

      return l;
  }
  */
  /**
   * Make a new line paralleled to the old line and the connecting line from the
   * center point of the new line and the center point of the old line have the
   * specified "distance" and the angle between the connecting line and the old
   * line is the specicfied "angle"
   * @param distance
   * @param angle
   * @return
   */
  public ORTLine makeNewLineByAngleShift(double distance, double angle)
  {
      double shiftAngle;
      if (Start.Row<End.Row) shiftAngle= Theta+angle;
      else if (Start.Row>End.Row) shiftAngle= Theta+angle+Math.PI;
      else if (Start.Col<End.Col) shiftAngle= Theta+angle;
      else shiftAngle= Theta+angle+Math.PI;

      double sx= Math.cos(shiftAngle)*distance;
      double sy= Math.sin(shiftAngle)*distance;
      ORTLine l= new ORTLine();
      l.Start= new ORTPoint(Start.Col+sx, Start.Row+sy);
      l.End= new ORTPoint(End.Col+sx, End.Row+sy);
      l.Length= Length;
      l.LengthParlVar  = LengthParlVar;
      l.LengthPerpVar  = LengthPerpVar;
      l.Theta= Theta;
      l.ThetaVar 	= ThetaVar;
      l.ID= ID;
      l.StringID= StringID;

      return l;
  }

  //make a new line.  exchage the end points
  public ORTLine makeNewLineByReverse()
  {
      ORTLine l= new ORTLine();
      l.Start= new ORTPoint(End.Col, End.Row);
      l.End= new ORTPoint(Start.Col, Start.Row);
      l.Length= Length;
      l.LengthParlVar  = LengthParlVar;
      l.LengthPerpVar  = LengthPerpVar;
      l.Theta= Theta;
      l.ThetaVar 	= ThetaVar;
        l.ID= ID;
        l.StringID= StringID;

      return l;
  }

  //make a new line.  rotate by 90 degree
  public ORTLine makeNewLineByRotate90()
  {
      ORTLine l= new ORTLine();

      ORTPoint MidPoint = midPoint();
      double dx = Start.Col - MidPoint.Col;
      double dy = Start.Row - MidPoint.Row;
      l.Start= new ORTPoint(MidPoint.Col-dy, MidPoint.Row+dx);

      dx = End.Col - MidPoint.Col;
      dy = End.Row - MidPoint.Row;
      l.End= new ORTPoint(MidPoint.Col-dy, MidPoint.Row+dx);
      l.Length= Length;
      l.LengthParlVar  = LengthParlVar;
      l.LengthPerpVar  = LengthPerpVar;
      l.Theta= Theta+Math.PI/2;
      if (l.Theta>Math.PI) l.Theta-= Math.PI;
      l.ThetaVar 	= ThetaVar;
      l.ID= ID;
      l.StringID= StringID;

      return l;
  }


    public static double LineSegTheta(double StartCol,
            double StartRow,
            double EndCol,
            double EndRow)
    {

        double dx,dy; 			/* Buffers */

        double Orientation;

        dx = EndCol - StartCol;
        dy = EndRow - StartRow;

        if (dx == 0.0) return(PIBY2); 	/* Line is vertical */
        if (dy == 0.0) return(0.0);     	/* Line is Horizontal */

        Orientation =  Math.atan2( dy, dx );

        /*
        Make it so line orientation is always between 0 and pi.

        */

        if (Orientation < 0.0 )
            Orientation = Orientation + Math.PI;

        if (Orientation >= Math.PI)
            Orientation = Orientation - Math.PI;

        return(Orientation);

    }

    /**
     * Return the direction of the line [0, Math.PI);
     * @return
     */
    public double getDirection() {
      return LineSegTheta(Start.Col, Start.Row, End.Col, End.Row);
    }

    /**
     * Return the length of the line
     * @return
     */
    public double getLength() {
      return GeometricUtil.distance(Start, End);
    }

    static double PIBY2= Math.PI/2;     //1.570796
    static double SIXPI= Math.PI*6;     //18.84956

    public double LineSegThetaVar(double Length)
    {
        ThetaVar = ( SIXPI)/(Length*Length);
        return(ThetaVar);

    }

    public double getDistortionCompensate(int image_size) {
      double length = getLength();
      double compensate = (length < image_size / 4) ?
          length * 8 / image_size :
          (length < image_size / 2) ? length * 4 / image_size + 1 :
          length * 2 / image_size + 2;
      return compensate;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
    }

    /**
     *
     * @param standard
     * @param maxWeight
     * @return
     * @deprecated replaced by double CLCUtil.lineWeight(double len)
     */
    public double weightedLength(double standard, double maxWeight) {
        double weight= Length/standard;
        if (weight>maxWeight) return maxWeight;
        return weight;
    }

    public boolean cross(int XSize, int YSize, int x, int y) {
        double XCenter = ((double) (XSize))/2.0;
        double YCenter = ((double) (YSize))/2.0;

        return cross(
            (int)Math.round (Start.Col + XCenter),
            (int)Math.round (Start.Row + YCenter),
            (int)Math.round (End.Col   + XCenter),
            (int)Math.round (End.Row   + YCenter),
            x,y);
    }

    public boolean cross(int x1, int y1, int x2, int y2, int px, int py)
    {
      int temp, deltax, deltay, xincrement, yincrement, e, a, b, i;
        int k,l;
        int LineThickness=0;

        if (Math.abs(x2-x1) > Math.abs(y2-y1)) {
            if (x1 > x2) {
                temp = x1; x1 = x2; x2 = temp;
                temp = y1; y1 = y2; y2 = temp;
            }
            deltax = x2 - x1;
            deltay = Math.abs (y2 - y1);
            e = (2 * deltay) - deltax;
            a = (2 * deltay) - (2 * deltax);
            b = (2 * deltay);
            if (y2 > y1) {
              yincrement = 1;
            } else {
              yincrement = -1;
            }
            for (i= 0;i<=deltax;i++) {
                for(k =(x1-LineThickness); k<=(x1+LineThickness); k++){
                    for(l =(y1-LineThickness); l<=(y1+LineThickness); l++){
                        if (l==py&&k==px) return true;
                    }
                }
                if (e > 0) {
                    y1 = y1 + yincrement;
                    e = e + a;
                } else {
                    e = e + b;
                }
                x1 = x1 + 1;
            }
        }
        else {
            if (y1 > y2) {
                temp = x1; x1 = x2; x2 = temp;
                temp = y1; y1 = y2; y2 = temp;
            }
            deltay = y2 - y1;
            deltax = Math.abs(x2 - x1);
            e = (2 * deltax) - deltay;
            a = (2 * deltax) - (2 * deltay);
            b = (2 * deltax);
            if (x2 > x1) {
              xincrement = 1;
            } else {
              xincrement = -1;
            }
            for (i= 0;i<=deltay;i++) {
                for(k =(x1-LineThickness); k<=(x1+LineThickness); k++){
                    for(l =(y1-LineThickness); l<=(y1+LineThickness); l++){
                        if (l==py&&k==px) return true;                    }
                }
                if (e > 0) {
                    x1 = x1 + xincrement;
                    e = e + a;
                } else {
                    e = e + b;
                }
                    y1 = y1 + 1;
            }
        }
        return false;
    }

    public double getX1() {
      return Start.Col;
    }
    public double getY1() {
      return Start.Row;
    }
    public double getX2() {
      return End.Col;
    }
    public double getY2() {
      return End.Row;
    }
    public void setX1(double v) {
      Start.Col = v;
    }
    public void setY1(double v) {
      Start.Row = v;
    }
    public void setX2(double v) {
      End.Col = v;
    }
    public final static double SQRT2 = Math.sqrt(2.0);
    public void setY2(double v) {
      End.Row = v;
      // suppose the set up is finished
      Length 		= LineLength(Start.Col,Start.Row, End.Col,End.Row);
      LengthParlVar  = 2.0*SQRT2;
      LengthPerpVar  = SQRT2;
      Theta 		= LineSegTheta(Start.Col, Start.Row, End.Col, End.Row);
      ThetaVar = LineSegThetaVar(Length);
    }
    public int getId() {
      return ID;
    }
    public void setId(int id) {
      ID = id;
    }

    double hypot(double x, double y)
    {
        return Math.sqrt(x*x+y*y);
    }

    double LineLength(double StartCol,
        double StartRow,
        double EndCol,
        double EndRow)
    {

        double dx,dy;

        double Length;

        dx = EndCol - StartCol;
        dy = EndRow - StartRow;

        Length = hypot( dx , dy );

        return(Length);

    }

    /**
     * Return a set containingall the points on the line.  Remember
     * the returned points are based on the coordinate has (0,0) on the top-left
     * corner of the image; while the ORTLine itself is based on the coordinate
     * which has (0,0) in the center.  See the inner class {@link #TestGetPoints}
     * for a full example
     * @param d the dimension of the image.  This is useful because ORTLine is
     *          based on the coordinate which has (0,0) in the center, but the
     *          returned points should based on the coordinate has (0,0) on the
     *          top-left corner.
     * @param LineThickness the thickness of the line.  LineThickness = 0 means
     *                      the singel pixel line.  LineThickness = n means the
     *                      line grows n pixel on both sides. (e.g.
     *                      LineThickness = 1 means 3-pixels line)
     * @return the iterator used to go through the points
     */
    public Set getPoints(int width, int height, int LineThickness) {

      HashSet points = new HashSet();

      int temp, deltax, deltay, xincrement, yincrement, e, a, b, i;
      int k,l;

      int xsize=width, ysize=height;
      double XCenter = ((double) (xsize))/2.0;
      double YCenter = ((double) (ysize))/2.0;

      int x1= (int)(Math.round(Start.Col+XCenter)), y1= (int)Math.round(Start.Row+YCenter);
      int x2= (int)(Math.round(End.Col+XCenter)), y2= (int)(Math.round(End.Row+YCenter));

      if (Math.abs(x2-x1) > Math.abs(y2-y1)) {
        if (x1 > x2) {
            temp = x1; x1 = x2; x2 = temp;
            temp = y1; y1 = y2; y2 = temp;
        }

        deltax = x2 - x1;
        deltay = Math.abs (y2 - y1);
        e = (2 * deltay) - deltax;
        a = (2 * deltay) - (2 * deltax);
        b = (2 * deltay);
        if (y2 > y1) {
          yincrement = 1;
        } else {
          yincrement = -1;
        }

        for (i= 0;i<=deltax;i++) {
          for(k =(x1-LineThickness); k<=(x1+LineThickness); k++){
            for(l =(y1-LineThickness); l<=(y1+LineThickness); l++){
              if ((k>=0) && (k<xsize) && (l>=0) && (l<ysize)) {
                    points.add(new Point(k, l));
              }
            }
          }
          if (e > 0) {
              y1 = y1 + yincrement;
              e = e + a;
          } else {
              e = e + b;
          }
          x1 = x1 + 1;
        }
      }
      else { // Math.abs(x2-x1) <= Math.abs(y2-y1)
        if (y1 > y2) {
            temp = x1; x1 = x2; x2 = temp;
            temp = y1; y1 = y2; y2 = temp;
        }
        deltay = y2 - y1;
        deltax = Math.abs(x2 - x1);
        e = (2 * deltax) - deltay;
        a = (2 * deltax) - (2 * deltay);
        b = (2 * deltax);
        if (x2 > x1) {
          xincrement = 1;
        } else {
          xincrement = -1;
        }
        for (i= 0;i<=deltay;i++) {
          for(k =(x1-LineThickness); k<=(x1+LineThickness); k++){
            for(l =(y1-LineThickness); l<=(y1+LineThickness); l++){
              if ((k>=0) && (k<xsize) && (l>=0) && (l<ysize)) {
                    points.add(new Point(k, l));
              }
            }
          }
          if (e > 0) {
                x1 = x1 + xincrement;
                e = e + a;
          } else {
              e = e + b;
          }
          y1 = y1 + 1;
        }
      }

      return points;
    }

    /**
     * Draw the line on the given image pixel array
     * @param img the image
     * @param width image width
     * @param height image height
     * @param LineThickness line thickness (0: 1-pixel line, 1: 3-pixel line)
     * @param color the color of the line
     */
    public void draw(int[] img, int width, int height, int LineThickness, int color) {
      Set points = getPoints(width, height, LineThickness);
      Iterator j = points.iterator();
      while (j.hasNext()) {
        Point p = (Point)j.next();
        img[p.y * width + p.x] = color;
      }
    }

    /**
     * Test whether two ORTLine are the same.  Important for Set, Map, etc.
     * @param o the object to be compared
     * @return whetherh there are the same
     */
    public boolean equals (Object o) {
      if(this == o) {  // Step 1: Perform an == test
        return true;
      }

      if(!(o instanceof ORTLine)) {  // Step 2: Instance of check
        return false;
      }

      ORTLine l2 = (ORTLine) o;  //Step 3: Cast argument

      // Step 4: For each important field, check to see if they are equal
      // For primitives use ==
      // For objects use equals() but be sure to also
      // handle the null case first
      return (l2.Start.Col == Start.Col && l2.Start.Row == Start.Row &&
              l2.End.Col == End.Col && l2.End.Row == End.Row) ||
             (l2.Start.Col == End.Col && l2.Start.Row == End.Row &&
              l2.End.Col == Start.Col && l2.End.Row == Start.Row);
    }

    /**
     * Return the hash code for the ORTLine object
     * @return the hash code
     */
    public int hashCode() {
      return (int)Math.round(Start.Col + Start.Row + End.Col + End.Row);
    }

    /**
     * Return the direction difference of two angle (of line).  The returned
     * value is [0, PI/2]
     * @param theta1
     * @param theta2
     * @return
     */
    public static double directionDifference(double theta1, double theta2) {
      double diff = Math.abs(theta1 - theta2) % Math.PI;
      if (diff > Math.PI/2) return Math.PI - diff;
      return diff;
    }

    /**
     * Get the mean color (r,g,b) on the line with specified thickness
     * @param LineThickness
     * @param pixels
     * @param img_width
     * @param img_height
     * @return
     */
    public int[] rgbMeanColor(int LineThickness, int[] pixels, int img_width, int img_height)
    {
      int[] rgb_sum = new int[3];
      Set points = getPoints(img_width, img_height, LineThickness);
      Iterator i = points.iterator();
      while (i.hasNext()) {
        Point p = (Point) i.next();
        int rgb = pixels[p.x + p.y * img_width];
        int[] rgb_components = ColorUtil.RGB2IntArray(rgb);
        rgb_sum[0] += rgb_components[0];
        rgb_sum[1] += rgb_components[1];
        rgb_sum[2] += rgb_components[2];
      }
      rgb_sum[0] /= points.size();
      rgb_sum[1] /= points.size();
      rgb_sum[2] /= points.size();
      return rgb_sum;
    }

    /**
     * Get the mean color (r,g,b) on the line with thickness = 0.  If the
     * specified LineThickness is not 0, then the line will be shifted, and
     * multiple rgb mean color will be returned.
     * @param LineThickness
     * @param pixels
     * @param img_width
     * @param img_height
     * @return
     */
    public int[][] rgbMeanColors(int LineThickness, int[] pixels, int img_width, int img_height)
    {
      // calculate the color pairs
      int[][] cps = new int[LineThickness * 2 + 1][];
      for (int i=-LineThickness, count=0; i<=LineThickness; i++, count++) {
        ORTLine shift = (i==0)? this: makeNewLineByAngleShift(i, Math.PI / 2);
        cps[count] = shift.rgbMeanColor(0, pixels, img_width, img_height);
      };
      return cps;
    }

    /**
     * To test the method {@link #makeNewLineByAngleShift}
     */
    public static class TestAngleShift {
      public static void main(String[] args) {
        int len = 20;
        int num = 18;
        int w = len * 2 + 2, h=len * 2 + 2;
        int[] img = new int[w * h];
        java.util.Arrays.fill(img, 0xff000000);

        // draw 18 lines
        for (int i=0; i<num; i++) {
          double angel = 2 * Math.PI / num * i;
          double x1 = 0, y1 = 0;
          double y2 = len * Math.sin(angel), x2 = len * Math.cos(angel);
          int color = java.awt.Color.HSBtoRGB((float)i/num, 1.0f, 1.0f);
          // draw it
          ORTLine l = new ORTLine(x1, y1, x2, y2);
          l.draw(img, w, h, 0, color);
          ORTLine l1 = l.makeNewLineByAngleShift(1, Math.PI/2);
          l1.draw(img, w, h, 0, 0xff8f8f8f);
          ORTLine l2 = l.makeNewLineByAngleShift(1, -Math.PI/2);
          l2.draw(img, w, h, 0, 0xff4f4f4f);
        }

        OutputResult.outputGIFImage("lines_shift.gif", img, w, h);
        System.exit(0);

      }
    }

    public String toString() {
      return new String("ORTLine[(" + Start.Col + ", " + Start.Row + "), (" +
                        End.Col + ", " + End.Row + ")]");
    }

    /**
     * To test the method {@link #pointIterator}
     */
    public static class TestGetPoints {
      public static void main(String[] args) {
        int len = 20;
        int num = 18;
        int w = len * 2 + 2, h=len * 2 + 2;
        int[] img = new int[w * h];
        java.util.Arrays.fill(img, 0xff000000);

        // draw 18 lines
        for (int i=0; i<num; i++) {
          double angel = 2 * Math.PI / num * i;
          double x1 = 0, y1 = 0;
          double y2 = len * Math.sin(angel), x2 = len * Math.cos(angel);
          int color = java.awt.Color.HSBtoRGB((float)i/num, 1.0f, 1.0f);
          // draw it
          ORTLine l = new ORTLine(x1, y1, x2, y2);
          Set points = l.getPoints(w, h, 1);
//          System.out.println("total = " + points.size());
          Iterator j = points.iterator();
          while (j.hasNext()) {
            Point p = (Point)j.next();
            img[p.y * w + p.x] = color;
//            System.out.print(p);
//            System.out.print(" ");
          }
//          System.out.println();
        }

        OutputResult.outputGIFImage("lines.gif", img, w, h);
        System.exit(0);
      }
    }

    public static class TestHash {
      public static void main(String[] args) {
        HashSet set = new HashSet();
        ORTLine l1 = new ORTLine(1, 2, 10, 20);
        ORTLine l2 = new ORTLine(1, 2, 10, 20); // same as l1
        ORTLine l3 = new ORTLine(10, 20, 1, 2); // same as l1
        ORTLine l4 = new ORTLine(20, 10, 1, 2); // different
        set.add(l1);
        set.add(l2);
        set.add(l3);
        set.add(l4);
        set.add(l1);
        set.add(l2);
        set.add(l3);
        set.add(l4);

        Iterator i = set.iterator();
        while (i.hasNext()) {
          ORTLine l = (ORTLine)i.next();
          System.out.println(l);
        }
      }
    }
}