import java.awt.*;
import javax.swing.*;

import java.text.DecimalFormat;
import java.util.ArrayList;

import cds.aladin.*;
import cds.tools.*;

public class Centroid extends AladinPlugin implements VOObserver {
   
   public String menu() { return "Centroid"; }
   public String description()   {
      return "Centroid:\n" + 
      		 "\tThis plugin is designed to find the centroid of a star.  " + 
      		 "While running this plugin, click on a star and the centroid calculations will be placed in a JFrame Text Box.  " +
      		 "Also an Aladin tag will be placed on the image near the centroid.";
   }   
   public String author()        { return "Mark Cusick"; }
   public String version()       { return "1.0 - 4, December, 2008"; }
   public String category()      { return "Prototype"; }

   private boolean active = false;
   private JFrame f;
   private JTextArea text;
   private static int SIZE = 15;
   private static int BACK = 7;
   
   /*
    * Creates a JTextArea inside a JFrame.
    * Uses this text area to display all centroid information calculated
    * Registered as a VOObserver
    */
   public void exec() {
      
      if(active) 	  // Already active?
    	  return;
      active = true;  //Plugin is now active
      
      // Create the JFrame and add the JTextArea in a JScrollPane
      f = new JFrame();

      text = new JTextArea();
      text.setEditable(false);
      JScrollPane s = new JScrollPane(text);
      s.setPreferredSize(new Dimension(650,500));
      
      f.add(s);
      
      f.pack();
      f.setVisible(true);
      f.setSize(s.getPreferredSize());
      text.append("Program Started\n");
//      text.append("How Calculations were made:\n");
//      text.append("x sum is sum of all pixel intensities * their distance from X Val\n");
//      text.append("y sum is sum of all pixel intensities * their distance from Y Val\n");
//      text.append("sum is sum of all pixel intensities\n");
//      text.append("x centroid = x sum / sum\n");
//      text.append("y centroid = y sum / sum\n\n");
//      text.append("Note: Once highest intensity pixel was found, all sum data was restricted\n");
//      text.append("      to a 15x15 box around it, as per the algorithm.\n\n");
      // Register itself has a VOObserver of Aladin positions
      aladin.addObserver(this,VOApp.POSITION);
   }
   
   
   /** VOObserver methods (called by aladin when the user clicks on the mouse */
   public void position(double ra,double dec) {
      try {
         
         // Get information on current image
         AladinData sd = aladin.getAladinImage();
         
         // get the (x, y) coords based on the (ra, dec) input from mouse click
         double xy[] = sd.getXY(ra,dec);
         int X = (int)xy[0];
         int Y = (int)xy[1];

         //Get pixel values for image
         double [][] pix = sd.getPixels();
         
         // *********************************************
         // *											*
         // * Find most intense pixel near mouse click  *
         // *											*
         // *********************************************
         boolean search = true;	
         double max;			//highest intensity pix val
         double oldMax;			//previous highest intensity pix val
         //newX and newY are used to keep track of next iteration (x, y) values
         int newX = 0;
         int newY = 0;
         
         max = pix[X][Y];	//intensity set to clicked pixel value
         //begin searching for most intense pixel in SIZExSIZE radius around mouse click
         while(search){
        	 oldMax = max;	//save max value
        	 
        	//compare the current (x, y) intensity to the surrounding pixels in a SIZExSIZE grid
        	//if a surrounding pixel has a greater intensity, save the value and coords
        	 int loopX = X - BACK;	//by setting loopX and loopY, we get the pixel in the top left of the SIZE*SIZE grid
        	 int loopY = Y - BACK;
        	 
        	 //starting at the top left pixel in a SIZExSIZE grid, find the most intense pixel
        	 for(int i = 0; i < SIZE; i++){
        		 for(int j = 0; j < SIZE; j++){
        			 if(pix[loopX+i][loopY+j] > max){
        				 max = pix[loopX+i][loopY+j];
        				 newX = loopX+i;
        				 newY = loopY+j;
        			 }
        		 }
        	 }
        	//if a higher intensity was not found, the centroid algorithm is done
        	if(max == oldMax) { search = false; }
        	//else set (x, y) to the highest intensity pixel and loop
        	else{
        		X = newX;
        		Y = newY;
        	}
         }
         
         // *********************************************
         // *											*
         // * Create information arrays for grid pixels *
         // *											*
         // *********************************************
         double vals[] = new double[SIZE*SIZE];	//this array will contain all the intensities of the pixels in the SIZE*SIZE grid
         double dist[] = new double[SIZE*SIZE]; //contains all distances for each pixel to the central pixel
         double distX[] = new double[SIZE*SIZE]; // contains the X distance for each pixel to the central pixel
         double distY[] = new double[SIZE*SIZE]; // contains the Y distance for each pixel to the central pixel
         
         //X and Y are now central brightest pixel in SIZExSIZE grid
         int centerX = X - BACK;  //same purpose as loopX and loopY
         int centerY = Y - BACK;
         int count = 0;	//keeps track of pixel count
         for(int i = 0; i < SIZE; i++){
        	 for(int j = 0; j < SIZE; j++){
        		 vals[count] = pix[centerX+i][centerY+j]; //save pixel intensity
        		 int sideX = centerX+i - X; //x distance of pixel
        		 int sideY = centerY+j - Y; //y distance of pixel
        		 dist[count] = Math.sqrt(Math.pow(sideX, 2) + Math.pow(sideY, 2)); //distance of pixel from center, uses x and y to find hypotenuse
        		 distX[count] = sideX; //save x dist
        		 distY[count] = sideY; //save y dist
        		 count++;
        	 }
         }
         
         
         // *********************************************
         // *											*
         // * Find value_Low and value_High in grid     *
         // *											*
         // *********************************************
         double level_Low = vals[0];
         double level_High = vals[0];
         for(int i = 0; i < vals.length; i++){
        	 if(vals[i] < level_Low)
        		 level_Low = vals[i];
        	 if(vals[i] > level_High)
        		 level_High = vals[i];
         }
         
         // steps 2 - 6
         // *********************************************
         // *											*
         // * Create second_Moment array				*
         // *											*
         // *********************************************
         ArrayList<Double> secondMoment = new ArrayList<Double>();
         double level_CurrHighest = level_High;
         double level_NextHighest = 0;
         double currentSum = 0;
         //find the second moment for level_High
         //for every pixel in the grid, check if intesity = current intensity level, find next highest intensity in grid
         for(int i = 0; i < vals.length; i++){
        	 if(vals[i] == level_CurrHighest){
        		 currentSum += Math.sqrt(dist[i]); //sum the squares of the distances of the pixel from the center
        	 }
        	 if(vals[i] < level_CurrHighest && vals[i] > level_NextHighest){
        		 level_NextHighest = vals[i]; // next highest intensity in grid, used for iterations
        	 }
         }
         level_CurrHighest = level_NextHighest; //set for next iteration
         secondMoment.add(currentSum); //add currentSum to the secondMoment array
         
         //find second moment for all remaining levels
         count = 1; //keep track of level
         while(level_CurrHighest >= level_Low){
        	 currentSum = 0;
        	 level_NextHighest = 0;
        	 //for every pixel in the grid, check if intesity = current intensity level, find next highest intensity in grid
        	 for(int i = 0; i < vals.length; i++){
        		 if(vals[i] == level_CurrHighest)
        			 currentSum += Math.sqrt(dist[i]); //sum the squares of the distances of the pixel from the center
        		 if(vals[i] < level_CurrHighest && vals[i] > level_NextHighest)
        			 level_NextHighest = vals[i]; //next highest intensity in grid, used for iterations
        	 }
        	 currentSum += secondMoment.get(count - 1);
        	 secondMoment.add(currentSum);
        	 count++;
        	 level_CurrHighest = level_NextHighest;
         }
         
         //step 7
         // *********************************************
         // *											*
         // * Create filtered_secondMoment array	 	*
         // *											*
         // *********************************************
         double filtered_secondMoment[] = new double[secondMoment.size()];
         //for each value in second moment array, compute a normalized moving 5 point center weighted filter 
         //(weights of 1,2,3,2,1, Normalization factor = 9)
         //does not compute the first two or last two values of second moment
         for(int i = 2; i < secondMoment.size() - 2; i++){
        	 double filter_Val = (secondMoment.get(i - 2) + secondMoment.get(i - 1)*2 + secondMoment.get(i)*3 + secondMoment.get(i + 1)*2 + secondMoment.get(i + 2)) / 9;
        	 filtered_secondMoment[i] = filter_Val;
         }
         //save the first two and last two values of second moment directly to filtered_secondMoment
         filtered_secondMoment[0] = secondMoment.get(0);
         filtered_secondMoment[1] = secondMoment.get(1);
         filtered_secondMoment[secondMoment.size() - 2] = secondMoment.get(secondMoment.size() - 2);
         filtered_secondMoment[secondMoment.size() - 1] = secondMoment.get(secondMoment.size() - 1);
         
         //step 9
         // *****************************
         // *							*
         // * Find the Threshold	 	*
         // *							*
         // *****************************
         int threshInt = 0; //holds the threshold pixel
         boolean check = true;
         int level = filtered_secondMoment.length - 3; //set search index to the level_Low and begin search there
         while(check){
        	 //if the difference between level and lower level is < 100, threshold has been found
        	 double checkVal = Math.abs(filtered_secondMoment[level] - filtered_secondMoment[level - 1]);
        	 if(checkVal < 100){
        		 threshInt = level;
        		 check = false;
        	 }
        	 level--;
        	 if(level <= 1){
        		 text.append("Threshold could not be found.\nPlease find programer and make him write better code.");
        	 	check = false;
        	 }
         }
         
         //step 10
         //set threshold value
         double threshold = vals[threshInt];
         
         //step 11 & sum of all intensities, xSum, ySum
         // *************************************
         // *									*
         // * Threshold & Calculate Centroid	*
         // *									*
         // *************************************
         double sum = 0; //sum of all of the intensities in the grid
         double xSum = 0; //sum of the product of the intensity of a pixel times it's x-distance from the center of the grid
         double ySum = 0; //sum of the product of the intensity of a pixel times it's y-distance from the center of the grid
         //threshold data and calculate the sums
         for(int i = 0; i < vals.length; i++){
        	 if(vals[i] <= threshold){
        		 vals[i] = 0; //pix intensity <= thres, set to 0
        	 }
        	 else{
        		 vals[i] = vals[i] - threshold; //pix intensity > thres, calc subtraction
        	 }
        	 sum += vals[i]; // sum of intensities
        	 xSum += vals[i]*distX[i]; //sum of intensities * distance X
        	 ySum += vals[i]*distY[i]; //sum of intensities * distance Y
         }
         
         DecimalFormat shorten = new DecimalFormat("#0.000");
         
         //calculate centroid
         double x_Centroid = xSum / sum;
         double y_Centroid = ySum / sum;
         double endX = X + x_Centroid;
         double endY = Y + y_Centroid;
                 
         //print results to the text area
         text.append("\n--------------------------------\n");
         
//       text.append("Centroid Data Input:\n");
//       text.append("X Sum: " + shorten.format(xSum));
//       text.append("\nY Sum: " + shorten.format(ySum));
//       text.append("\nSum: " + shorten.format(sum) + "\n\n");
//       text.append("Centroid Data Output:\n");
//       text.append("X Centroid: " + shorten.format(x_Centroid) + "\n");
//       text.append("Y Centroid: " + shorten.format(y_Centroid) + "\n\n");
//       text.append("Highest Intensity Pixel:\n");
//       text.append("X: " + X + "\n");
//       text.append("Y: " + Y + "\n\n");
         
         text.append("Centroids\n");
         text.append("X Centroid: " + shorten.format(endX) + "\n");
         text.append("Y Centroid: " + shorten.format(endY) + "\n");
         
         text.append("--------------------------------\n\n");
         
         //draw a tag on the centroid, not as accurate as the previously printed centroid information
         double[] coords = sd.getCoord(endX, endY);
         double finalRA = coords[0];
         double finalDec = coords[1];
         aladin.execCommand("draw tag(" + finalRA + " +" + finalDec + ")");
         
      } catch( AladinException e ) { e.printStackTrace(); }  
   }
   
   /** VOObserver method
    * Not used by Centroid
    */
   public void pixel(double pixValue) { }
   
   /** Can be called by Aladin to know the state of plugin */
   public boolean isRunning() { return active; }
   
   /** Called by Aladin during a manual plugin stop */
   public void cleanup() {
      if(!active) 
    	  	return;
      aladin.addObserver(this,0);
      f.dispose();
      active = false;
   }
   

   
}
