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

import Targa.*;
import util.QuickSort;

/**
* This class calculates border blur for a lesion. It divides the lesion into eight
* sectors (45 degrees each), and takes five equidistant measurements from each sector.
* It works out the least squared slope of the lightness(L*) values for each of the
* five measurements, and outputs this data.
* Subsequent processing is performed in Excel, where the slope threshold is decided.
* The slope threshold decides what slope represents sharp contrast, and what slope
* represents blur...
*/

public class BorderBlur {

	float[] angles;
	Point centre;
	float area ;
	TargaPicture tp;
  PrintWriter logFile;
  Vector points;
  //---------------------------------------------------
  public BorderBlur(String tp, String boundaryFile, PrintWriter pw) throws IOException {
    this.tp = new TargaPicture(tp);
    this.tp.read() ;
    logFile = pw;
    points = new Vector();

    //------------- read boundary points ------------
    int x = 0, y = 0;
		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;
			points.addElement(new Point(x,y)) ;
		}

  }

  //---------------------------------------------------
	public void calculateArea() {
		long xt,yt,at ;
		long a ;
		Point p1,p2;
		int numPts;
		a = 0 ;
		xt = 0 ;
		yt = 0 ;
		numPts = points.size() ;
		for (int i = numPts-1, j = 0; j < numPts; i = j, j++) {
			p1 = (Point)points.elementAt(i) ;
			p2 = (Point)points.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)points.elementAt(0) ;
		p2 = (Point)points.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))) ;
		}

		this.area = (float)java.lang.Math.abs((a/2.0));
	}



  //---------------------------------------------------
	void calculateAngles() {
		int x= 0,y =0;
		float len = 0;
		float angle;
		angles = new float[points.size()];
    Point p = this.centre;

		for (int i = 0; i < points.size(); i++) {
			x = ((Point)points.elementAt(i)).x ;
			y = ((Point)points.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));
		}
	}


  //---------------------------------------------------
	int closestAngleIndex(int angle) {
		double radianAngle = (angle*java.lang.Math.PI)/180;
		double minError = Float.MAX_VALUE;
		int minErrorIndex = 0;
		double error = 0.0;

		for (int i = 0; i < angles.length; i++) {
			error = (angles[i]-radianAngle)*(angles[i]-radianAngle);
			if (error < minError) {
				minError = error;
				minErrorIndex = i;
			}
		}
		return minErrorIndex;
	}


  //---------------------------------------------------
	public double calculateBorderBlur() {
		Point p1 = null;
		Point farP = new Point(0,0);
		Point closeP = new Point(0,0);
		float angle = 0.0f;
		float len = 30.0f;
    double currentLuminance = 0.0f;
		RGBPoint[] rgbPoints = null;
    float sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
    int count = 0;
	int pointIndex = 0;
    Vector m = new Vector();


    //-------- angle increment is arbitrarily 9 - cos 5*9 = 45 which is one sector of stolz
		for (int i = 0; i < 360; i+= 9) {
			pointIndex = closestAngleIndex(i);
			p1 = (Point)points.elementAt(pointIndex) ;
			angle = angles[pointIndex] ;

			farP.x = (int)(p1.x+len*java.lang.Math.cos(angle)) ;
			farP.y = (int)(p1.y+len*java.lang.Math.sin(angle)) ;
			closeP.x = (int)(p1.x-len*java.lang.Math.cos(angle)) ;
			closeP.y = (int)(p1.y-len*java.lang.Math.sin(angle)) ;

      //------ Read 30 data points and store in rgbPoints
			rgbPoints = readData(closeP,angle, 60) ;
      //------ if null, then had ArrayIndexOutOfBounds exception, so jump to next
      if (rgbPoints == null) {
        tp.addLine(new Point(farP.x,farP.y),new Point(closeP.x,closeP.y), Color.red) ;
	logFile.print("\t");
        continue;
      } else {
        sumX = 0; sumY = 0; sumXY = 0; sumX2 = 0;
	  		for (int j = 0; j < rgbPoints.length; j++) {
          rgbPoints[j].calcLAB();
          currentLuminance = rgbPoints[j].Lx;
//System.out.println(currentLuminance) ;
          sumX += j+1;
          sumY += currentLuminance ;
          sumXY += currentLuminance*(j+1);
          sumX2 += (j+1)*(j+1);
			  }
        //----------------------- add the slope of the least squares line (Line of best fit) of the fifteen data points------
        m.addElement(new Float((rgbPoints.length*sumXY-sumX*sumY)/((rgbPoints.length*sumX2)-(sumX*sumX))));
	logFile.print(m.elementAt(m.size()-1) + "\t");
      tp.addLine(new Point(farP.x,farP.y),new Point(closeP.x,closeP.y), Color.blue) ;

//if (angle < java.lang.Math.PI/4)tp.addLine(farP.x,farP.y,closeP.x+20,closeP.y+20) ;
  		}   //---- END IF
    }     //---- END FOR

    float[] mTemp = new float[m.size()] ;
    for (int i = 0; i < m.size(); i++) {
      mTemp[i] = ((Float)m.elementAt(i)).floatValue();
    }

    QuickSort.sort(mTemp) ;
    float sumUpperQuartile = 0.0f;

    float quartileValue = (3*m.size())/4;
    for (int i = (int)java.lang.Math.ceil(quartileValue); i < m.size(); i++) {
       sumUpperQuartile += mTemp[i];
       count ++;
    }
/*    for (int i = 0; i < m.size(); i++) {
	    logFile.print(mTemp[i]+"\t");
    }*/
    logFile.println();

    if (quartileValue < java.lang.Math.ceil(quartileValue)) {
      sumUpperQuartile += (quartileValue - java.lang.Math.ceil(quartileValue)) *
                          mTemp[(int)quartileValue] - mTemp[(int)java.lang.Math.ceil(quartileValue)];
      count ++ ;
    }
//logFile.println(sumUpperQuartile/count);
		return sumUpperQuartile/count;    // Theoretically, this is the average of the 75 percentile of the border blur
	}



  //------------------------------------------------------------------
	RGBPoint[] readData(Point p, double angle, int length) {
		RGBPoint rgbPt = null;
		RGBPoint[] array = null;
    Vector v = new Vector() ;

		double x=p.x;
		double y=p.y;
		int count = 0;
		int nx,ny,ox,oy;
		ox = (int)x;
		oy = (int)y;
loop:		do {
			x += java.lang.Math.cos(angle);
			y += java.lang.Math.sin(angle);

			nx = (int)x;
			ny = (int)y;
			if ((nx != ox) || (ny != oy)) {
        try {
  				rgbPt = new RGBPoint(tp.getImageData(nx,ny));

          if (rgbPt.r > 0 || rgbPt.g > 0 || rgbPt.b > 0 ) {
            v.addElement(rgbPt);
//	tp.setImageData(nx,ny,0xffffffff);
//	tp.setImageData(nx,ny+1,0xffffffff);
//	tp.setImageData(nx+1,ny,0xffffffff);
//	tp.setImageData(nx+1,ny+1,0xffffffff);

            count ++ ;
          }
          ox = nx; oy = ny;
        } catch (ArrayIndexOutOfBoundsException e) {
System.out.println("AIOOBE");
        // IF this occurs, then off edge of image - therefore no blur, so flag whole array
          return null;
        }
			}

		} while (count < length) ;
    array = new RGBPoint[v.size()];
    for (int i = 0; i < v.size(); i++) {
      array[i] = (RGBPoint)v.elementAt(i) ;
    }
		return array;
	}



  //------------------------------------------------------------------
	public static void main(String[] args) {
		int count = 0, x= 0, y=0;
		Vector points = new Vector();
		BorderBlur b= null;
    FileWriter fw = null;
    PrintWriter logFile = null;
    // Create log stream
    try {
      fw = new FileWriter(args[2]) ;
      logFile = new PrintWriter(fw, true);
    } catch (Exception 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) ;
    }


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

 	  	try {
		  	fr = new FileReader(args[1]) ;
			  st = new StreamTokenizer(fr) ;
        st.slashStarComments(true);
  			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 = (new Integer((int)st.nval)).toString();
			          st.nextToken() ;
          if (st.sval != null && st.sval.length() == 1) {
            file += st.sval;
        	}
	else {
		st.pushBack();
	}
		files.addElement(file) ;
	
System.out.println("File " + file);
			  }
  		} catch (IOException e) {
System.out.println("Read Error in Specification File: ACTION: EXIT") ;
System.exit(1);
	  	}
      String tgaFile = null;
      String boundaryFile = null;
      for (int i = 0; i < numberOfFiles; i++) {
        try {
          tgaFile =dir1+((String)files.elementAt(i))+".tga";
          boundaryFile =dir2+((String)files.elementAt(i))+".bnd";
          b = new BorderBlur(tgaFile,boundaryFile,logFile);
        } catch (IOException e) {
          logFile.println("File not found "+e) ;
          continue;
        }
        logFile.print(((String)files.elementAt(i))+"\t");
     	  b.calculateArea();
    		b.calculateAngles();
		    b.calculateBorderBlur();
      }
    }
    else { //------------ Only single iteration
  		try {
	  		b = new BorderBlur(args[0]+".tga", args[1]+".bnd",logFile);

		  	b.calculateArea();
  			b.calculateAngles();
	  		b.calculateBorderBlur();
		  	TargaView tv = new TargaView(b.tp);
  			b.tp.setBoundary(b.points);
	  	} catch (IOException e) {
  System.out.println("Oops:"+e);
	  	}
  	}
  }
}




