import java.io.*;
import java.util.Vector;
import util.Median;
import util.Distance;
import util.DisplayMask;
import java.awt.Color;
import java.awt.Point;
import Targa.*;

/**
* The main ELM asymmetry class. Calculates variance and colour asymmetry...This
* version uses pixel comparisons for colour asymmetry, based on the human perception
* investigation. Variance asymmetry uses 20x20 squares in its assessment.
*/
public class Asymmetry2 {
  boolean DISPLAY = false;
  boolean ROTATE = true;
	int cx,cy;
	long area;
	TargaPicture tp;
  PrintWriter log;

	public Asymmetry2(String s, PrintWriter ps, boolean rotate) throws IOException {
    this.log = ps;
    this.ROTATE = rotate ;
		tp = new TargaPicture(s) ;
		tp.read();
	}

  public Asymmetry2(String s, PrintWriter ps, boolean display, boolean rotate) throws IOException {
    this(s,ps, rotate) ;
    this.DISPLAY = display;
  }


	void getCentroid(TargaPicture t) {
		long sumX=0,sumY=0,sum=0;
		boolean pt;
		int val =0;

		for (int i = 0; i < t.w; i++) {
			for (int j = 0; j < t.h; j++) {
				pt = t.getMaskData(i,j) ;
				if (pt== true) val = 1; else val = 0;
				sumX += i*val ;
				sumY += j*val ;
				sum += val;
			}
		}
		cx = (int)(sumX/sum);
		cy = (int)(sumY/sum);
		area = sum;
	}



	double getPrincipalAxisAngle () {
		double sumTop = 0, sumBot1 = 0, sumBot2 = 0;
		double sum = 0;
		int val = 0;
		boolean pt;

		for (int i =0; i < tp.w; i++){
			for (int j = 0; j < tp.h; j++) {
				pt = tp.getMaskData(i,j) ;
				if (pt == true) val = 1; else val = 0;
				sumTop += (i-cx)*(j-cy)*val;
				sumBot1 += ((i-cx)*(i-cx))*val;
				sumBot2 += ((j-cy)*(j-cy))*val;

				sum += val;
			}
		}
		sumTop /= sum;
		sumTop *= 2;
		sumBot1 /=sum;

		sumBot2 /=sum;

		sum = sumTop/(sumBot1-sumBot2) ;
		sum = java.lang.Math.atan(sum)/2;
		return sum;

	}



	TargaPicture rotate(double angle) {
System.out.println(" Rotate: "+this.ROTATE);
    if (this.ROTATE == false) return tp;
		double cosine = java.lang.Math.cos(-angle) ;
		double sine = java.lang.Math.sin(-angle) ;
    int diagonal = (int)java.lang.Math.sqrt(tp.w*tp.w+tp.h*tp.h);
		TargaPicture tp2 = new TargaPicture(diagonal+1,diagonal+1) ;
		int centreX = tp.w/2;
		int centreY = tp.h/2;
		boolean pt;
		int sourceX,sourceY;

		for (int i = 0; i < tp.h; i++) {
			for (int j = 0; j < tp.w; j++) {
				sourceX = (int)((j-cx)*cosine+(i-cy)*sine);
				sourceY = (int)((i-cy)*cosine-(j-cx)*sine);
				if ((int)cx+sourceX >=0 && (int)cx+sourceX < tp.w && (int)cy+sourceY >=0 && (int)cy+sourceY < tp.h) {
					pt = tp.getMaskData((int)cx+sourceX,(int)cy+sourceY);
 //	  			tp2.setImageData(j,i,tp.getImageData(sourceX+(int)cx,sourceY+(int)cy)) ;

					if (pt== true) {
            tp2.setMaskData(j,i,true) ;
	  			tp2.setImageData(j,i,tp.getImageData(sourceX+(int)cx,sourceY+(int)cy)) ;
					}
				}
			}
		}
		return tp2;

	}


	double getAreaAsymmetry() {
		double sum=0,sum1=0,sum2=0;
    int count = 0;
		getCentroid(this.tp) ;
		double principalAngle = getPrincipalAxisAngle() ;
		TargaPicture temp = rotate(principalAngle);
		getCentroid(temp);


		for (int i = 1; i < tp.h/2; i++) {
			for (int j = 0; j < tp.w; j++) {
				if ((temp.getMaskData(j,cy+i) ^ temp.getMaskData(j,cy-i))==true) {
					temp.setImageData(j,i+cy,0xFFFF0000);
					sum1++;
				}

			}
		}

		for (int i = 0; i < temp.h; i++) {
			for (int j = 1; j < tp.w/2; j++) {
				if ((temp.getMaskData(cx+j,i) ^ temp.getMaskData(cx-j,i))==true) {
					temp.setImageData(cx+j,i,0xFFffff00);
					sum2++;
				}
			}
		}
    log.println("------------------------------------------------");
    log.println("Area Asymmetry :\tVertical\thorizontal");
    log.println("\t\t" + sum1/area + "\t" + sum2/area);

    if (DISPLAY == true) new TargaView(temp);
    return 0;
	}



	public void getColourAsymmetry() {
System.out.println("Colour asymmetry") ;
		RGBPoint colour1, colour2;
		double rgbSum1 = 0,rgbSum2= 0;
		double lxAxBxSum1 = 0,lxAxBxSum2 = 0;
		double lAlphaBetaSum1 = 0, lAlphaBetaSum2= 0;
		int count = 0;
		getCentroid(this.tp) ;
		double principalAngle = getPrincipalAxisAngle() ;
		TargaPicture temp = rotate(principalAngle);
		getCentroid(temp);

		for (int i = 0; i < tp.h/2-1; i++) {
			for (int j = 0; j < tp.w-1; j++) {
				if ((temp.getMaskData(j,cy+i) == true && temp.getMaskData(j,cy-i))==true) {
					colour1 = new RGBPoint(temp.getImageData(j,cy+i)) ;//getMedianColour(temp,j,cy+i,21,1,1) ;
					colour2 = new RGBPoint(temp.getImageData(j,cy-i)) ;// getMedianColour(temp,j,cy-i,21,1,-1);
          colour1.calcLAB();
          colour1.calcSpherical();
          colour2.calcLAB();
          colour2.calcSpherical();

					rgbSum1 += util.Distance.getEuclidDistance(colour1.r,colour1.g,colour1.b,
                                                      colour2.r,colour2.g,colour2.b) ;
					lxAxBxSum1 += util.Distance.getEuclidDistance(colour1.Lx,colour1.ax,colour1.bx,
                                                      colour2.Lx,colour2.ax,colour2.bx) ;
//					lAlphaBetaSum1 += util.Distance.getEuclidDistance(colour1.r,colour1.g,colour1.beta,
  //                                                    colour2.L,colour2.alpha,colour2.beta) ;
					count++;
				}
			}
System.out.print("\b\b"+(i*100/(tp.h/2)));
		}
    log.print("\t" + rgbSum1/count + "\t" + lxAxBxSum1/count + "\t" + lAlphaBetaSum1/count+"\t");
System.out.print("Count " + count);

//    new TargaView(temp);
    count = 0;

		for (int j = 0; j < tp.w/2-1; j++) {
      for (int i = 0; i < tp.h-1; i++) {
				if ((temp.getMaskData(cx+j,i) == true && temp.getMaskData(cx-j,i))==true) {
					colour1 = new RGBPoint(temp.getImageData(cx+j,i)) ; //getMedianColour(temp,cx+j,i,21,1,1) ;
					colour2 = new RGBPoint(temp.getImageData(cx-j,i)) ; // getMedianColour(temp,cx-j,i,21,-1,1) ;
          colour1.calcLAB();
          colour1.calcSpherical();
          colour2.calcLAB();
          colour2.calcSpherical();


					rgbSum2 += util.Distance.getEuclidDistance(colour1.r,colour1.g,colour1.b,
                                                      colour2.r,colour2.g,colour2.b) ;
					lxAxBxSum2 += util.Distance.getEuclidDistance(colour1.Lx,colour1.ax,colour1.bx,
                                                      colour2.Lx,colour2.ax,colour2.bx) ;
//					lAlphaBetaSum2 += util.Distance.getEuclidDistance(colour1.L,colour1.alpha,colour1.beta,
//                                                      colour2.L,colour2.alpha,colour2.beta) ;
					count++;
				}
			}
System.out.print("\b\b"+(j*100/(tp.w/2)));
		}
    log.print("\t" + rgbSum2/count + "\t" + lxAxBxSum2/count + "\t" + lAlphaBetaSum2/count+"\t");

	}



	public void getVarianceAsymmetry() {
		double varianceRedSum = 0, varianceGreenSum = 0, varianceBlueSum = 0;
		double varianceLSum = 0, varianceAlphaSum = 0, varianceBetaSum = 0;
		double varianceLxSum = 0, varianceAxSum = 0, varianceBxSum = 0;
		double varianceCxSum = 0, varianceHueSum = 0;
		double[] variance1 = null;
		double[] variance2 = null;

		int count = 0;
		getCentroid(this.tp) ;
		double principalAngle = getPrincipalAxisAngle() ;
		TargaPicture temp = rotate(principalAngle);
		getCentroid(temp);

		for (int i = 0; i < tp.h/2-21; i+=21) {
			for (int j = 0; j < tp.w-21; j+=21) {
				if ((temp.getMaskData(j+10,cy+i+10) && temp.getMaskData(j+10,cy-i-10))==true) {
					variance1 = getVariance(temp,j,cy+i,21,1,1) ;
					variance2 = getVariance(temp,j,cy-i,21,1,-1) ;

					varianceRedSum += (variance1[0] - variance2[0])*(variance1[0] - variance2[0]);
					varianceGreenSum += (variance1[1] - variance2[1])*(variance1[1] - variance2[1]);
					varianceBlueSum += (variance1[2] - variance2[2])*(variance1[2] - variance2[2]);
					varianceLSum += (variance1[3] - variance2[3])*(variance1[3] - variance2[3]);
					varianceAlphaSum += (variance1[4] - variance2[4])*(variance1[4] - variance2[4]);
					varianceBetaSum += (variance1[5] - variance2[5])*(variance1[5] - variance2[5]);
					varianceLxSum += (variance1[6] - variance2[6])*(variance1[6] - variance2[6]);
					varianceAxSum += (variance1[7] - variance2[7])*(variance1[7] - variance2[7]);
					varianceBxSum += (variance1[8] - variance2[8])*(variance1[8] - variance2[8]);
					varianceCxSum += (variance1[9] - variance2[9])*(variance1[9] - variance2[9]);
					varianceHueSum += (variance1[10] - variance2[10])*(variance1[10] - variance2[10]);
          count ++ ;
				}
			}
System.out.print("\b\b"+(i*100/(tp.h/2)));
		}

    log.print("\t" + varianceRedSum/count + "\t" + varianceGreenSum/count + "\t" + varianceBlueSum/count+"\t");
    log.print("\t" + varianceLSum/count + "\t" + varianceAlphaSum/count + "\t" + varianceBetaSum/count+"\t");
    log.print("\t" + varianceLxSum/count + "\t" + varianceAxSum/count + "\t" + varianceBxSum/count+"\t");
    log.print("\t" + varianceCxSum/count + "\t" + varianceHueSum/count + "\t");

    varianceRedSum = 0; varianceGreenSum = 0; varianceBlueSum = 0;
    varianceLSum = 0; varianceAlphaSum = 0; varianceBetaSum = 0;
	varianceLxSum = 0; varianceAxSum = 0; varianceBxSum = 0;
	varianceCxSum = 0; varianceHueSum = 0;

    count = 0;
		for (int j = 0; j < tp.w/2-20; j+=20) {
	  	for (int i = 0; i < tp.h-20; i+=20) {
				if ((temp.getMaskData(cx+j+10,i+10) && temp.getMaskData(cx-j-10,i+10))==true) {
					variance1 = getVariance(temp,cx+j,i,21,1,1) ;
					variance2 = getVariance(temp,cx-j,i,21,-1,1) ;

					varianceRedSum += (variance1[0] - variance2[0])*(variance1[0] - variance2[0]);
					varianceGreenSum += (variance1[1] - variance2[1])*(variance1[1] - variance2[1]);
					varianceBlueSum += (variance1[2] - variance2[2])*(variance1[2] - variance2[2]);
					varianceLSum += (variance1[3] - variance2[3])*(variance1[3] - variance2[3]);
					varianceAlphaSum += (variance1[4] - variance2[4])*(variance1[4] - variance2[4]);
					varianceBetaSum += (variance1[5] - variance2[5])*(variance1[5] - variance2[5]);
					varianceLxSum += (variance1[6] - variance2[6])*(variance1[6] - variance2[6]);
					varianceAxSum += (variance1[7] - variance2[7])*(variance1[7] - variance2[7]);
					varianceBxSum += (variance1[8] - variance2[8])*(variance1[8] - variance2[8]);
					varianceCxSum += (variance1[9] - variance2[9])*(variance1[9] - variance2[9]);
					varianceHueSum += (variance1[10] - variance2[10])*(variance1[10] - variance2[10]);
          count ++ ;
				}
			}
System.out.print("\b\b"+(j*100/(tp.w/2)));
		}
System.out.println("COUNT " + count);
    log.print("\t" + varianceRedSum/count + "\t" + varianceGreenSum/count + "\t" + varianceBlueSum/count+"\t");
    log.print("\t" + varianceLSum/count + "\t" + varianceAlphaSum/count + "\t" + varianceBetaSum/count+"\t");
    log.print("\t" + varianceLxSum/count + "\t" + varianceAxSum/count + "\t" + varianceBxSum/count+"\t");
    log.print("\t" + varianceCxSum/count + "\t" + varianceHueSum/count + "\t");


	}



	double[] getVariance(TargaPicture targPic, int x, int y, int wid, int xDirection, int yDirection) {
		RGBPoint pt = null;
		double sumRed = 0, sumGreen = 0, sumBlue = 0;
		double sumL = 0, sumAlpha = 0, sumBeta = 0;
		double sumLx = 0, sumAx = 0, sumBx = 0, sumCx = 0, sumHue = 0;
		double varianceRed = 0, varianceGreen = 0, varianceBlue = 0;
		double varianceL = 0, varianceAlpha = 0, varianceBeta = 0;
		double varianceLx = 0, varianceAx = 0, varianceBx = 0;
		double varianceCx = 0, varianceHue = 0;
		sumRed = 0; sumBlue = 0; sumGreen = 0;
		int count = 0;
		for (int i = 0; i < wid; i++) {
			for (int j=0; j < wid; j++) {
        try {
  				pt = new RGBPoint(targPic.getImageData(x+(j*xDirection),y+(i*yDirection))) ;
        } catch (ArrayIndexOutOfBoundsException e) {
          continue;
        }
				if (pt.r> 0) {
          pt.calcSpherical();
          pt.calcLAB();
					sumRed += pt.r;
					sumGreen += pt.g;
					sumBlue += pt.b;
          sumL += pt.L;
					sumAlpha += pt.alpha;
					sumBeta += pt.beta;
          sumLx += pt.Lx;
					sumAx += pt.ax;
					sumBx += pt.bx;
					sumCx += pt.Cx;
					sumHue += pt.hue;

					count++ ;
				}
			}
		}
		if (count < 1) return new double[3];
		sumRed /= count;
		sumGreen /= count;
		sumBlue /= count;
    sumL /=count;
		sumAlpha /=count;
		sumBeta /=count;
    sumLx /=count;
		sumAx /=count;
		sumBx /=count;
		sumCx /=count;
		sumHue /=count;

		 varianceRed = 0; varianceGreen = 0; varianceBlue = 0;
		 varianceL = 0; varianceAlpha = 0; varianceBeta = 0;
		 varianceLx = 0; varianceAx = 0; varianceBx = 0; varianceCx = 0; varianceHue = 0;

		for (int i = 0; i < wid; i++) {
			for (int j=0; j < wid; j++) {
        try {
  				pt = new RGBPoint(targPic.getImageData(x+(j*xDirection),y+(i*yDirection))) ;
        } catch (ArrayIndexOutOfBoundsException e) {
          continue;
        }
				if (pt.r > 0 || pt.g > 0 || pt.b > 0) {
          pt.calcLAB();
          pt.calcSpherical();
					varianceRed += (pt.r-sumRed)*(pt.r-sumRed);
					varianceGreen += (pt.g-sumGreen)*(pt.g-sumGreen);
					varianceBlue += (pt.b-sumBlue)*(pt.b-sumBlue);
					varianceL += (pt.L-sumL)*(pt.L-sumL);
					varianceAlpha += (pt.alpha-sumAlpha)*(pt.alpha-sumAlpha);
					varianceBeta += (pt.beta-sumBeta)*(pt.beta-sumBeta);
					varianceLx += (pt.Lx-sumLx)*(pt.Lx-sumLx);
					varianceAx += (pt.ax-sumAx)*(pt.ax-sumAx);
					varianceBx += (pt.bx-sumBx)*(pt.bx-sumBx);
					varianceCx += (pt.Cx-sumCx)*(pt.Cx-sumCx);
					varianceHue += (pt.hue-sumHue)*(pt.hue-sumHue);

				}
			}
		}

		varianceRed /= count;
		varianceBlue /= count;
		varianceGreen /= count;
		varianceL /= count;
		varianceAlpha /= count;
		varianceBeta /= count;
		varianceLx /= count;
		varianceAx /= count;
		varianceBx /= count;
		varianceCx /= count;
		varianceHue /= count;

		double[] variance = new double[11];
		variance[0]= java.lang.Math.sqrt(varianceRed);
		variance[1]= java.lang.Math.sqrt(varianceGreen);
		variance[2]= java.lang.Math.sqrt(varianceBlue);
		variance[3]= java.lang.Math.sqrt(varianceL);
		variance[4]= java.lang.Math.sqrt(varianceAlpha);
		variance[5]= java.lang.Math.sqrt(varianceBeta);
		variance[6]= java.lang.Math.sqrt(varianceLx);
		variance[7]= java.lang.Math.sqrt(varianceAx);
		variance[8]= java.lang.Math.sqrt(varianceBx);
		variance[9]= java.lang.Math.sqrt(varianceCx);
		variance[10]= java.lang.Math.sqrt(varianceHue);
		return variance;

	}



	RGBPoint getMedianColour(TargaPicture tp, int x, int y, int wid, int xDirection, int yDirection) {
		RGBPoint pt = null;
    RGBPoint[] colour = new RGBPoint[wid*wid];
    float[]rgbDistance = new float[wid*wid];
    float[]lAlphaBetaDistance = new float[wid*wid];
    float[] lxAxBxDistance = new float[wid*wid];

    int count = 0;
    double[] temp = null;
//System.out.println("X " + x + " Y " + y);
		for (int i =0; i< wid; i++) {
			for (int j =0; j< wid; j++) {
        try {
  				pt = new RGBPoint(tp.getImageData(x+(j*xDirection),y+(i*yDirection)));
        } catch (ArrayIndexOutOfBoundsException e) {
          continue ;
        }
        if (pt.r > 0) {
          pt.calcLAB();
          temp = pt.getSphericalCoords() ;
          rgbDistance[count] = (float)Distance.getEuclidDistance(0,0,0,pt.r,pt.g,pt.b);
          colour[count] = pt;
  				lxAxBxDistance[count] = (float)Distance.getEuclidDistance(0,0,0,pt.Lx,pt.ax,pt.bx);
  				lAlphaBetaDistance[count] = (float)Distance.getEuclidDistance(0,0,0,temp[0],temp[1],temp[2]);
          count ++;
        }
      }
		}
		RGBPoint rgbMedianColour = (RGBPoint)Median.getMedianValue(colour,rgbDistance, count);
    RGBPoint lxAxBxMedianColour = (RGBPoint)Median.getMedianValue(colour,lxAxBxDistance, count);
    RGBPoint lAlphaBetaMedianColour = (RGBPoint)Median.getMedianValue(colour, lAlphaBetaDistance, count);

    lxAxBxMedianColour.calcLAB();
    lAlphaBetaMedianColour.calcSpherical();

    pt = new RGBPoint(rgbMedianColour.r, rgbMedianColour.g, rgbMedianColour.b);
    pt.Lx = lxAxBxMedianColour.Lx;
    pt.ax = lxAxBxMedianColour.ax;
    pt.bx = lxAxBxMedianColour.bx;
    pt.L = lAlphaBetaMedianColour.L;
    pt.alpha = lAlphaBetaMedianColour.alpha;
    pt.beta = lAlphaBetaMedianColour.beta;


		return pt;

	}






	public static void main(String []args) {
    PrintWriter logFile = null;

    if (args[0].equals("iterate") == true) {
  		Vector files = new Vector() ;
      Vector rotate = 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;
            st.nextToken();
          }
          files.addElement(file) ;
          rotate.addElement(new Boolean(st.sval)) ;
			  }
  		} catch (IOException e) {
System.out.println("Read Error in Specification File: ACTION: EXIT") ;
System.exit(1);
	  	}

      // Create log stream
      FileWriter fw = 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) ;
      }

      Asymmetry2 a = null;
      logFile.print("\tvVarRed\tvVarGreen\tvVarBlue\tvVarL\tvVarAlpha\tvVarBeta\tvVarLx\tvVarAx\tvVarBx\tvVarCx\tvVarHue");
      logFile.print("\tHVarRed\tHVarGreen\tHVarBlue\tHVarL\tHVarAlpha\tHVarBeta\tHVarLx\tHVarAx\tHVarBx\tHVarCx\tvVarHue");
      logFile.print("\tVColAsymRGB\tVColAsymSpher\tVColAsymLAB");
      logFile.println("\tHColAsymRGB\tHColAsymSpher\tHColAsymLAB");

      for (int i = 0; i < numberOfFiles; i++) {
        try {
          a = new Asymmetry2(dir1+(String)files.elementAt(i)+".tga",
                        logFile, ((Boolean)rotate.elementAt(i)).booleanValue());
          } catch (IOException e) {
            logFile.println("File not found "+e) ;
            continue;
          }
          if (a.tp.readMask(dir2+(String)files.elementAt(i)+".msk") == -1) {
            logFile.println("Mask Error");
            continue;
          } else {
            logFile.print((String)files.elementAt(i)+"\t");
     	    	a.getVarianceAsymmetry();
     	    	a.getColourAsymmetry();
            logFile.println();
          }

      }
    }



    else { // Only iterate once, and print output to std out
      logFile =new PrintWriter(System.out,true);
      Asymmetry2 a = null;
      try {
    		a = new Asymmetry2(args[0]+".tga",logFile, true,
          new Boolean(args[2]).booleanValue());
      } catch (IOException e) {
System.out.println("Error loading file: " + e +" action EXIT") ;
        System.exit(1);
      }
      if (a.tp.readMask(args[1]+".msk") == -1) {
        logFile.println("Mask Error");
        System.exit(1);
      }



      //		  a.getAreaAsymmetry();
      logFile.print("\tVVarRed\tVVarGreen\tVVarBlue\tVVarL\tVVarAlpha\tVVarBeta\tVVarLx\tVVarAx\tVVarBx");
      logFile.print("\tHVarRed\tHVarGreen\tHVarBlue\tHVarL\tHVarAlpha\tHVarBeta\tHVarLx\tHVarAx\tHVarBx");
      logFile.print("\tVColAsymRGB\tVColAsymSpher\tVColAsymLAB");
      logFile.println("\tHColAsymRGB\tHColAsymSpher\tHColAsymLAB");
//  		a.getVarianceAsymmetry();
  		a.getColourAsymmetry();
      logFile.println();
    }
	}
}

