/*
 * DataPoint.java
 *
 * Created on 29. toukokuuta 2006, 23:58
 *
 * To change this template, choose Tools | Options and locate the template under
 * the Source Creation and Management node. Right-click the template and choose
 * Open. You can then make changes to the template in the Source Editor.
 */

package fi.csc.microarray.client.visualisation.methods.threed;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.util.LinkedList;

import fi.csc.microarray.client.visualisation.methods.threed.CoordinateArea.PaintMode;

/**
 * Class for singel point of data. This is mainly from the Viski project 
 * (http://www.cs.helsinki.fi/group/viski/) but some new drawing modes are added.
 *
 * @author esa, Petri Klemelä
 */
public class DataPoint 
        extends Drawable {
    
    private double radius;
    private double effectiveRadius;
    private int index;
    
    private static final int SELECTION_DISTANCE = 4;
    
    /**
     * Creates a new instance of DataPoint
     * @param xData 
     * @param yData 
     * @param zData 
     * @param color 
     * @param radius 
     * @param text 
     */
    public DataPoint(double xData, double yData, double zData, 
            Color color, double radius, String text, int index) {
        this.radius = this.effectiveRadius = radius;
        this.visualisationCoords = new double[1][3];
        this.projectedCoords = new double[1][2];
        this.deviceCoords = new int[1][2];
        this.dataCoords = new double[1][3];
        this.distanceFromCamera = new double[1];
        this.index = index;        
        
        dataCoords[0][0] = visualisationCoords[0][0] = xData;
        dataCoords[0][1] = visualisationCoords[0][1] = yData;
        dataCoords[0][2] = visualisationCoords[0][2] = zData;
        this.color = color;
        this.text = text;
    }
    
    public int getIndex(){
    	return index;
    }
    
    /**
     * 
     * @param xData 
     * @param yData 
     * @param zData 
     * @param color 
     * @param radius 
     */
    public DataPoint(double xData, double yData, double zData, 
            Color color, double radius, CoordinateArea coordianteArea, String geneId) {
        this(xData, yData, zData, color, radius, null, -1);
    }

    /**
     * 
     * @param g 
     * @param width 
     * @param height 
     */
    public void draw(Graphics g, int width, int height, PaintMode paintMode) {
    	Graphics2D g2d = (Graphics2D)g; 
    	
        if (Math.abs(projectedCoords[0][0]) > 0.5 || 
                Math.abs(projectedCoords[0][1]) > 0.5 ||
                this.hidden == true) {
            return;
        }
        g2d.setColor(color);
        deviceCoords[0][0] = (int)((projectedCoords[0][0] + 0.5) * width);
        deviceCoords[0][1] = (int)((projectedCoords[0][1] + 0.5) * height);
        double screenRadius = 0.5;
        if (effectiveRadius * width > 0.5) {
            screenRadius = effectiveRadius * width;
        }       
        
        int x = deviceCoords[0][0]-(int)screenRadius;
        int y = deviceCoords[0][1]-(int)screenRadius;
        int w = (int)(screenRadius*2);
        int h = (int)(screenRadius*2);
     
        if(paintMode == CoordinateArea.PaintMode.RECT){
        	g2d.setPaint(color);        
        	g2d.fillRect(x, y, w, h);
        	
        } else {
        	paintBall(x, y, w, h, color, g2d);
        }
                              
        if (selected == true) {
        	g2d.setPaint(Color.gray);
        	g2d.drawOval(x-2, y-2, w+4, h+4);
        }
    }
    
    public static void paintBall(int x, int y, int w, int h, Color c, Graphics2D g2d) {    	  
    	
    	int radius = (int)(w * 1);
    	radius = radius > 0 ? radius : 1;
    	
    	g2d.setPaint(new RoundGradientPaint(
    		x + w / 4.0,
    		y + h / 4.0,
    		c,
    		new Point( radius, radius),
    		Color.BLACK));
    	
    	g2d.fillOval(x, y, w, h);
    }

    /**
     * 
     * @param camera 
     * @param planeDistance 
     * @param viewWindowWidth 
     * @param viewWindowHeight 
     */
    public void setDistanceFromCamera(
            double[] camera, 
            double planeDistance,
            double viewWindowWidth,
            double viewWindowHeight) {
        this.distanceFromCamera[0] = 
                DataPoint.pointDistance(camera, visualisationCoords[0]);
        
        double distanceScalar = planeDistance / distanceFromCamera[0];
        this.effectiveRadius = (radius / viewWindowWidth) * distanceScalar;
    }
    
    /**
     * 
     * @param x 
     * @param y 
     * @param points 
     * @param maxDist 
     * @return 
     */
    static final public LinkedList<DataPoint> getNearest(
            int x, 
            int y, 
            Drawable[] points, 
            double maxDist
            ) {
        LinkedList<DataPoint> list = new LinkedList<DataPoint>();
        DataPoint dp = null;
        double dist = Double.MAX_VALUE;
        
        for (Drawable d : points) {
            if (d instanceof DataPoint) {
                int xDiff = x-d.deviceCoords[0][0];
                int yDiff = y-d.deviceCoords[0][1];
                double tmp = Math.sqrt(xDiff*xDiff + yDiff*yDiff);
                if (tmp < dist && tmp <= maxDist) {
                    dist = tmp;
                    dp = (DataPoint)d;
                }
            }
        }
        if (dp != null)
            list.add(dp);
        return list;
    }
    
    /**
     * 
     * @param x 
     * @param y 
     * @param points 
     * @return 
     */
    static final public LinkedList<DataPoint> getNearest(
            int x, 
            int y, 
            Drawable[] points
            ) {
        return DataPoint.getNearest(x, y, points, SELECTION_DISTANCE);
    }
            
    /**
     * 
     * @param x1 
     * @param y1 
     * @param x2 
     * @param y2 
     * @param points 
     * @return 
     */
    static final public LinkedList<DataPoint> getGroup(
            int x1, 
            int y1, 
            int x2, 
            int y2, 
            Drawable[] points
            ) {
        LinkedList<DataPoint> list = new LinkedList<DataPoint>();
        //double dist = Double.MAX_VALUE;
        
        if (x1 > x2) {
            int tmp = x2;
            x2 = x1;
            x1 = tmp;
        }
        if (y1 > y2) {
            int tmp = y2;
            y2 = y1;
            y1 = tmp;
        }
        
        for (Drawable d : points) {
            if (d instanceof DataPoint) {
                if (d.deviceCoords[0][0] >= x1 && d.deviceCoords[0][0] <= x2 && 
                        d.deviceCoords[0][1] >= y1 && d.deviceCoords[0][1] <= y2) {
                    list.add((DataPoint)d);
                }
            }
        }
        return list;
    }
    
    /**
     * 
     * @param a 
     * @param b 
     * @return 
     */
    static final public double pointDistance(double[] a, double[] b) {
        double xD = a[0] - b[0];
        double yD = a[1] - b[1];
        double zD = a[2] - b[2];
        return Math.sqrt(xD*xD + yD*yD + zD*zD);
    }
    
    /*
     * In AnnotateListPanel this is used as gene identifier. If toString is changed, separate 
     * method for giving identifier should be done. 
     */
    @Override
    public String toString(){
    	return text;
    }
}