

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

import cds.aladin.AladinData;
import cds.aladin.AladinException;
import cds.aladin.AladinPlugin;
import cds.tools.VOApp;
import cds.tools.VOObserver;

/** Essai de développement d'un plugin pour Aladin
 * Calcul de la photométrie d'ouverture
 * @author Thomas Boch [CDS]
 */
public class AperturePhotometryPlugin extends AladinPlugin implements VOObserver {
	// TODO : ajout frame pour afficher les 3 params et changer leur valeur
	// TODO : voir ce qu'a fait atv.pro dans IDL et comparer résultat
	// TODO : prendre en compte centroide
	
	// aperture in number of pixels
	static int APERTURE = 5;
	// diamètre interne du tore pour calcul du fond (en pixels)
	static int INNER = 10;
	// diamètre externe du tore pour calcul du fond (en pixels)
	static int OUTER = 20;
	
	// inutile pour le moment, j'aurais aimé pouvoir choisir la couleur des cercles
	static final Color APERTURE_COLOR = Color.green;
	static final Color INNER_COLOR = Color.blue;
	static final Color OUTER_COLOR = Color.red;
	
	boolean mustRegister = true;
	private String planeToRemove; // ID du plan à effacer
	private FramePhot framePhot;
	
	// implémentation de l'interface AladinPlugin
	public String menu() { return "Aperture photometry"; }
	public String description() { return "This plugin computes aperture photometry for the reticle position"; }   
	public String scriptCommand() { return "aper_phot"; }  
        public String author() { return "Thomas Boch"; }
        public String category() { return "Prototype"; }
        public String version()     { return "1.1 - march 2008"; }

	public void exec() {
	      
		if( mustRegister ) {
			aladin.addObserver(this,VOApp.POSITION);
			mustRegister = false;
		}
		showFrame();
	}
	   
	private void showFrame() {
	   	if( framePhot==null ) {
	   		framePhot = new FramePhot();
	   	}
	   	framePhot.setVisible(true);
	   	framePhot.toFront();
	}
	
	/** VOObserver methods */
	public void pixel(double pixValue) {}

	public void position(double ra,double dec) {
		try {
//			System.out.println(ra+"\t"+dec);
			AladinData sd;
			try {
				sd = aladin.getAladinImage();
			}
			catch(AladinException ae) {return;}
	         
			double xy[] = sd.getXY(ra,dec);
			// TODO : recherche du centroïde ?
			// rem : j'ai l'impression d'avoir un décalage   => Il faut enlever 0.5
			int X = (int)xy[0];
			int Y = (int)xy[1];
//	         System.out.println(X+"\t"+Y);
			
			framePhot.reticlePosLabel.setText("x="+X+" y="+Y);
	         
			if( planeToRemove!=null ) {
				aladin.execCommand("rm \""+planeToRemove+"\"");
//	         	System.out.println(planeToRemove);
				planeToRemove = null;
			}
	
			int aperture = APERTURE;
			int inner = INNER;
			int outer = OUTER;
			try {
				aperture = Integer.parseInt(framePhot.apertureTF.getText());
				inner = Integer.parseInt(framePhot.innerSkyTF.getText());
				outer = Integer.parseInt(framePhot.outerSkyTF.getText());
				
				if( inner>outer ) throw new NumberFormatException();
			}
			catch(NumberFormatException nfe) {
				framePhot.apertureTF.setText(aperture+"");
				framePhot.innerSkyTF.setText(inner+"");
				framePhot.outerSkyTF.setText(outer+"");
			}
			
			// dessins des 3 cercles
			aladin.execCommand("draw mode(XY)");
			aladin.execCommand("draw circle("+X+","+Y+","+aperture+")");
			aladin.execCommand("draw mode(XY)");
			aladin.execCommand("draw circle("+X+","+Y+","+inner+")");
			aladin.execCommand("draw circle("+X+","+Y+","+outer+")");
	         
	        // juste pour des tests pour pb de décalage
			int width = sd.getWidth();
			int height = sd.getHeight();
			byte pix[] = sd.seeCodedPixels();
			int bitpix = sd.getFitsBitPix();
			double bzero = sd.getFitsBzero();
			double bscale = sd.getFitsBscale();
			
			// décalage de (1,1), je ne comprends pas
			System.out.println("flux at clicked position : "+AladinData.CodedPixelsToDouble(pix, bitpix, Y*width + X)*bscale+bzero);
			
			// on recherche le plan qu'on vient de créer avec les commandes draw
			String [] planeID = aladin.getAladinStack();
			if( planeID!=null && planeID.length>0 ) {
				for( int k=planeID.length-1; k>=0; k++ ) {
					AladinData tmp = aladin.getAladinData(planeID[k]);
					if( tmp.getPlaneType().startsWith("Overlay") ) {
						planeToRemove = planeID[k];
						break;
					}
				}
			}
	         
			DecimalFormat df = new DecimalFormat();
			df.setMaximumFractionDigits(6);
			df.setMinimumFractionDigits(2);
			df.setDecimalSeparatorAlwaysShown(true);
			
	        // calcul du flux du fond
	        double bkgdFlux = computeBkgdFlux(sd, X, Y, inner, outer);
//	        System.out.println("flux fond: "+bkgdFlux);
	        framePhot.skyLevelLabel.setText(df.format(bkgdFlux)+"");
	        
	        double flux = computeFlux(sd, X, Y, 0, aperture, bkgdFlux);
	        framePhot.countLabel.setText(df.format(flux)+"");
//	        System.out.println("total flux: "+flux);
	         
		}
		catch( AladinException e ) { e.printStackTrace(); }
	}


	/**
	 * calcule le flux dans l'image im pour le tore centré en (x,y) de rayon interne innerRadius et externe outerRadius
	 * @param image
	 * @param xPos
	 * @param yPos
	 * @param innerRadius
	 * @param outerRadius
	 * @param bkgdVal valeur du fond du ciel (à soustraire)
	 * @return
	 */
	// TODO : throws Exception si en bord de l'image
	public double computeFlux(AladinData im, int xPos, int yPos, int innerRadius, int outerRadius, double bkgdValue) {
		double totalFlux = 0.0;
		try {
			int width = im.getWidth();
			int height = im.getHeight();
			byte pix[] = im.seeCodedPixels();
			int bitpix = im.getFitsBitPix();
			double bzero = im.getFitsBzero();
			double bscale = im.getFitsBscale();
			
			int size = outerRadius+1;
			double dist;
			int k=0;
			double pixVal;
			double flux;
			for( int j=0, y=yPos+size; j<size*2; j++,y-- ) {
				for( int i=0, x=xPos-size; i<size*2; i++,x++ ) {
					dist = computeDist(xPos, yPos, x, y);
//	        		System.out.println("x,y): "+x+"\t"+y);
//	        		System.out.println(dist);
					// check if the current pixel is within the torus
					if( dist>outerRadius || dist<innerRadius ) continue;
					pixVal = AladinData.CodedPixelsToDouble(pix, bitpix, y*width + x);
					flux = pixVal*bscale+bzero-bkgdValue;
					totalFlux += flux;
					k++;
				}
			}
		}
		catch(AladinException e) {e.printStackTrace();}
		return totalFlux;
	}
	
	/** retourne le flux du background
	 * on va retourner la médiane des valeurs de flux trouvé dans le tore défini par innerRadius et outerRadius
	 * @param im
	 * @param xPos
	 * @param yPos
	 * @param innerRadius
	 * @param outerRadius
	 * @return
	 */
	// TODO : throw AladinException si en bord d'image
	public double computeBkgdFlux(AladinData im, int xPos, int yPos, int innerRadius, int outerRadius) {
		Vector v = new Vector();
		try {
			int width = im.getWidth();
			int height = im.getHeight();
			byte pix[] = im.seeCodedPixels();
			int bitpix = im.getFitsBitPix();
			double bzero = im.getFitsBzero();
			double bscale = im.getFitsBscale();
			
			int size = outerRadius+1;
			double dist;
			int k=0;
			double pixVal;
			double flux;
			for( int j=0, y=yPos+size; j<size*2; j++,y-- ) {
				for( int i=0, x=xPos-size; i<size*2; i++,x++ ) {
					dist = computeDist(xPos, yPos, x, y);
//	        		System.out.println("x,y): "+x+"\t"+y);
//	        		System.out.println(dist);
					// check if the current pixel is within the torus
					if( dist>outerRadius || dist<innerRadius ) continue;
					pixVal = AladinData.CodedPixelsToDouble(pix, bitpix, y*width + x);
					flux = pixVal*bscale+bzero;
					v.addElement(new Double(flux));
					k++;
				}
			}
        }
		catch(AladinException e) {e.printStackTrace();}
		Double[] d = new Double[v.size()];
		v.copyInto(d);
		Arrays.sort(d);
        // on retourne la médiane
        return d[d.length/2].doubleValue();
        
	}

	
	
	/**
	 * Calcule la distance entre les points (x1,y1) et (x2,y2)
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	private double computeDist(int x1, int y1, int x2, int y2) {
		return Math.sqrt(Math.pow(x1-x2, 2)+Math.pow(y1-y2, 2));
	}
	
	class FramePhot extends JFrame {
		JLabel reticlePosLabel;
		JTextField apertureTF, innerSkyTF, outerSkyTF;
		JLabel skyLevelLabel, countLabel;
		
		FramePhot() {
			super("Aperture photometry");
			buildFrame();
		}
		
		void buildFrame() {
			getContentPane().setLayout(new BorderLayout());
			
			JPanel options = new JPanel(new GridLayout(0,2));
			options.add(new JLabel("Reticle position"));
			reticlePosLabel = new JLabel("x=\ty=");
			options.add(reticlePosLabel);
			
			options.add(new JLabel("Aperture radius"));
			apertureTF = new JTextField(APERTURE+"");
			options.add(apertureTF);
			options.add(new JLabel("Inner sky radius"));
			innerSkyTF = new JTextField(INNER+"");
			options.add(innerSkyTF);
			options.add(new JLabel("Outer sky radius"));
			outerSkyTF = new JTextField(OUTER+"");
			options.add(outerSkyTF);
			
			options.setBorder(BorderFactory.createEtchedBorder());
			
			getContentPane().add(options, BorderLayout.WEST);
			
			JPanel values = new JPanel(new GridLayout(0,2));
			values.add(new JLabel("Sky level: "));
			values.add(skyLevelLabel = new JLabel());
			values.add(new JLabel("Object counts:   "));
			values.add(countLabel = new JLabel());
			
			values.setBorder(BorderFactory.createEtchedBorder());
			
			getContentPane().add(values, BorderLayout.CENTER);
			
			
			pack();
		}
	}
	
}
