import java.io.*;
import java.awt.Point;
import java.util.Vector;

import Targa.*;

/**
* Class implementing a simple diameter calculating algorithm. Requires a boundary file
* that contains the locations of the boundary points. The algorithm calculates the centroid
* and works out the longest `almost straight' line that passes throught the centroid and
* has boundary points as start and end points.<p>
* @author Greg Day (grd@waikato.ac.nz)
*/


public class Diameter {

	float[] angles;
	Point centre;
	float area ;
	Vector boundaryPoints;
	TargaPicture tp;


	/**
	* Takes the name of a boundary file as input. This boundary file has been previously 
	* created with util.TraceContour. <p>
	* This procedure is not intended to be called, other than from main()
	*/

	Diameter(String boundaryFile) {
		int x, y;
		boundaryPoints = new Vector();
		try {
			FileReader fr = new FileReader(boundaryFile) ;
			StreamTokenizer st = new StreamTokenizer(fr);
			st.nextToken() ;
			while (st.nextToken() != st.TT_EOF) {
				st.pushBack();
				st.nextToken();
				x = (int)st.nval;
				st.nextToken();
				y = (int)st.nval;
				boundaryPoints.addElement(new Point(x,y)) ;
			}
		} catch (IOException e) {
System.out.println("Oops " + e) ;
			return ;
		}
		area = calculateArea(boundaryPoints);
		calculateAngles(boundaryPoints, centre);
	}

	Diameter(String boundaryFile, TargaPicture tp) {
		try {
			tp.read() ;
		} catch(IOException e) {
System.err.println("Error reading targa picture");
			return;
		}
		this(boundaryFile) ;
	}
	


	/**
	* This procedure takes the vector v of boundary points, and a specific point p. It 
	* examines v and returns the point closest to being opposite p. Opposite in this
	* sense is the point found when a line is drawn from p through the centroid and intersects
	* the boundary
	*/
	private int oppositeTo(Vector v, Point p) {
		Point p2 = null;
		long min = Integer.MAX_VALUE;
		int minVal = 0;
		boolean belowCY ;


		if (p.y > centre.y) belowCY = true ;
		else belowCY = false ;

		for (int i = 0; i < v.size(); i++) {
			p2 = (Point)v.elementAt(i) ;
			if ((belowCY == true && p2.y < centre.y) || (belowCY == false && p2.y > centre.y)) {
				if ((p2.x-p.x)*(p2.x-p.x) < min) {
					min = (p2.x-p.x)*(p2.x-p.x) ;
					minVal = i ;
				}
			}

		}
		return minVal ;
	}



	/**
	* Calculates area of the boundary polygon , and in particular calculates the centroid
	*/
	public float calculateArea(Vector v) {
		long xt,yt,at ;
		long a ;
		Point p1,p2;
		int numPts;
		a = 0 ;
		xt = 0 ;
		yt = 0 ;
		numPts = v.size() ;
System.out.println("NUM PTS " + numPts);
		for (int i = numPts-1, j = 0; j < numPts; i = j, j++) {
			p1 = (Point)v.elementAt(i) ;
			p2 = (Point)v.elementAt(j) ;

			at = p1.x*p2.y-p1.y*p2.x ;
			a += at ;
			xt += (p2.x+p1.x)*at ;
			yt += (p2.y+p1.y)*at ;
		}
		p1 = (Point)v.elementAt(0) ;
		p2 = (Point)v.elementAt(numPts-1) ;
		at = p1.x*p2.y-p1.y*p2.x ;
		a += at ;
		xt += (p2.x+p1.x)*at ;
		yt += (p2.y+p1.y)*at ;


		if (a != 0) {
			this.centre = new Point((int)(xt /(3*a)),(int)(yt /(3*a))) ;
		}
System.out.println("Centre " + centre + " " +java.lang.Math.abs(a/2.0));
		return (float)java.lang.Math.abs((a/2.0));
	}



  /**
  * Works out all of the angles that each point makes with the centroid and the x axis.
  */
	void calculateAngles(Vector v, Point p) {
		int x= 0,y =0;
		float len = 0;
		float angle;
		angles = new float[v.size()];
System.out.println("Centre " + centre + " " + p); 

		for (int i = 0; i < v.size(); i++) {
			x = ((Point)v.elementAt(i)).x ;
			y = ((Point)v.elementAt(i)).y ;
			len = (float)java.lang.Math.sqrt((x-p.x)*(x-p.x) + (y-p.y)*(y-p.y));

			angle = (float)java.lang.Math.asin((y-centre.y)/len);
			if ((x <= p.x) && (y >= p.y)) angles[i] = (float)java.lang.Math.PI - angle;
			else if ((x <= p.x) && (y <= p.y)) angles[i] = (float)java.lang.Math.PI - angle; // angle is negative
 			else if ((x >= p.x) && (y <= p.y)) angles[i] = (float)java.lang.Math.PI*2 + angle; // angle is negative
			else angles[i] = angle;
//System.out.println("Angle "+ x + " " + y + " " + p.x + " " + p.y +  " " + (angles[i]*180/java.lang.Math.PI) + " " + (angle*180/java.lang.Math.PI));
		}
	}



  /**
  * Given an angle, this function finds the point whose angle is closest to this
  */
	int findAngle(float angleToFind) {
		float minAngErr = Float.MAX_VALUE ;
		float angErr = 0.0f;
		int minAngleIndex = 0;


		for (int i = 0; i < angles.length; i++) {
			angErr = (angleToFind-angles[i])*(angleToFind-angles[i]);
			if (angErr < minAngErr) {
				minAngleIndex = i;
				minAngErr = angErr;
			}
		}
		return minAngleIndex ;
	}


  /**
  * Finds the maximum (or minimum) axis. This is the main procedure of this class
  */ 
	public float getAxis(boolean MAXIMUM_AXIS) {
		int opposite ;

		float len, extremaLength,angleToFind;
		int minAngleIndex= 0;
		int lengthIndex1 = 0;
		int lengthIndex2 = 0;

		int numberOfPoints=boundaryPoints.size();
		Point store1=null, store2=null;

		if (MAXIMUM_AXIS == true) extremaLength = 0;
		else extremaLength = Float.MAX_VALUE;

		for (int i = 0; i < numberOfPoints; i++) {
			angleToFind = (float)(angles[i] + java.lang.Math.PI);
			if (angleToFind > (2*java.lang.Math.PI)) angleToFind -= 2*java.lang.Math.PI ;
			minAngleIndex = findAngle(angleToFind);

			store1 = (Point)boundaryPoints.elementAt(minAngleIndex) ;
			store2 = (Point)boundaryPoints.elementAt(i);

			len = (float)java.lang.Math.sqrt((float)((store1.x-store2.x)*(store1.x-store2.x)+(store1.y-store2.y)*(store1.y-store2.y)));

			if (MAXIMUM_AXIS == true) {
				if (len > extremaLength) {
					extremaLength = len;
					lengthIndex1 = i ;
					lengthIndex2 = minAngleIndex;
				}
			 } else {
				if (len < extremaLength) {
					extremaLength = len ;
					lengthIndex1 = i ;
					lengthIndex2 = minAngleIndex;
				}
			}
		}
		if (tp != null) {
			tp.addLine((Point)boundaryPoints.elementAt(lengthIndex1), (Point)boundaryPoints.elementAt(lengthIndex2));
		}
		return extremaLength;
	}


        /**
        * Main adds all of the associated structure to enable the procedure to be called
        * from the command line. Can either iterate through a number of files, or be called
        * on a single file. Single file use also requires a TargaPicture so that the line
        * can be displayed.
        * @param args[0] equals iterate if iteration is required, otherwise image file name
        * @param args[1] equals script file if iteration, boundary file if no iteration
        * @param args[2] equals output file if iteration, not used if no iteration
        */

	public static void main(String[] args) {
		if (args[0].equals("iterate") == true) {
			Vector v = new Vector() ;
			FileReader fr = null;
			StreamTokenizer st = null;
			int file = 0, numberOfFiles = 0;
			String dir1=null, dir2=null;

			try {
				fr = new FileReader(args[1]) ;
				st = new StreamTokenizer(fr) ;

				st.nextToken() ;
				numberOfFiles = (int)st.nval;

				st.nextToken() ;
				dir1 = (String)st.sval;
				st.nextToken() ;
				dir2 = (String)st.sval;

				for (int i = 0; i < numberOfFiles; i++) {
					st.nextToken() ;
					file = (int)st.nval;
					v.addElement(new Integer(file)) ;
				}
			} catch (IOException e) {
System.out.println("Read Error in Specification File: ACTION: EXIT") ;
System.exit(1);
			}

			// Create log stream
			FileWriter fw = null;
			PrintWriter logFile = null;
			try {
				fw = new FileWriter(args[2]) ;
				logFile = new PrintWriter(fw, true);
			} catch (IOException e) {
System.out.println("Error opening Log File: ACTION: Use System.out") ;
			// true turns automatic flushing on, so every println causes something to print
				logFile = new PrintWriter(System.out, true) ;
			}

			Diameter d = null;
			for (int i = 0; i < numberOfFiles; i++) {
				d = new Diameter(dir1+((Integer)v.elementAt(i)).toString()+".bnd");
				logFile.println(((Integer)v.elementAt(i)).toString()+ "\t" +d.getAxis(true));
			}
		} else {
			Diameter d = null;
			TargaPicture tp = null;
			try {
				tp = new TargaPicture(args[0]);
				d = new Diameter(args[1], tp);
			} catch (IOException e) {
System.out.println("Error initialising Diameter - Action: Continuing");
			}
System.out.println(d.getAxis(true)) ;
System.out.println(d.getAxis(false)) ;

			if (tp != null) {
				new TargaView(tp);
			}
		}
  	}
}
