/*
 * TopoPlot.java
 *
 * Copyright 2005-2006                                
 * Performance Research Laboratory, University of Oregon
 */
package edu.uoregon.tau.vis;

import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.Observable;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.GLU;
import javax.media.opengl.glu.GLUquadric;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Draws a scatterplot along 4 axes
 *
 * @author Alan Morris
 *
 * <P>CVS $Id: ScatterPlot.java,v 1.7 2009/08/20 22:09:34 amorris Exp $</P>
 * @author  Alan Morris
 * @version $Revision: 1.7 $
 */
public class TopoPlot extends ScatterPlot {


    // settings
    private float xSize = 15, ySize = 15, zSize = 15;
    private float sphereSize = 0.4f;
    private int sphereDetail = 8;
    private boolean visible = true;
    private boolean normalized = true;
    
    private float[][] values;
    private ColorScale colorScale;
    private Axes axes;

    // rendering details
    private int displayList;
    private boolean dirty = true;

    
    // grr... get rid of these
    private int selectedRow = 5;
    private int selectedCol = 5;
    
    private int minVis = 0;
    private int maxVis = 100;
    boolean isTopo=false;
    
    private float[] minValueArr={};
    private float[] maxValueArr={};

    public void setMinMax(float[] min,float[] max){
    	minValueArr=min;
    	maxValueArr=max;
    }
    
    private float minShown;
    private float maxShown;
    public float getMinShown(){
    	return minShown;
    }
    public float getMaxShown(){
    	return maxShown;
    }
    
//    private void resetVisRange(){
//    	minVis=0;
//    	maxVis=100;
//    }
    
    public TopoPlot() {
    }

    public void setIsTopo(boolean set){
    	isTopo=set;
    }
    public void setVisRange(int min, int max){
    	minVis = min;
    	maxVis = max;
    }
    
//    float tmpMin;
//    float tmpMax;
//    public void setMinMax(float min, float max){
//    	tmpMin=min;
//    	tmpMax=max;
//    }
//    float rangeMin;
//    float rangeMax;
//    public void setVisRangeValues(float min, float max){
//    	rangeMin=min;
//    	rangeMax=max;
//    }
    
    public void setSize(float xSize, float ySize, float zSize) {
        this.xSize = xSize;
        this.ySize = ySize;
        this.zSize = zSize;
        if (axes != null)
            axes.setSize(xSize, ySize, zSize);
        
        if (values != null)
            processValues();
        
        this.dirty = true;
    }

//    public void clearValues(){
//    	this.values=null;
//    }
    float[][] origValues=null;
    /**
     * Sets the values.  The 2nd dimension must be of size 4 (one value for each axis).
     * @param values
     */
    public void setValues(float values[][]) {
        this.values = values;
        
        if(isTopo)
        {
        	origValues=new float[values.length][];
        	for(int i=0;i<values.length;i++){
        		origValues[i]=values[i].clone();
        	}
        }
        else
        	origValues=null;
        processValues();
        isTopo=false;
        this.dirty = true;
    }

    /**
     * Returns the current sphere size.
     * @return the current sphere size.
     */
    public float getSphereSize() {
        return sphereSize;
    }

    /**
     * Sets the sphere size.
     * @param sphereSize the desired sphere size.
     */
    public void setSphereSize(float sphereSize) {
        this.sphereSize = sphereSize;
        this.dirty = true;
    }

    public Axes getAxes() {
        return axes;
    }

    public void setAxes(Axes axes) {
        this.axes = axes;
        axes.setSize(xSize, ySize, zSize);
    }
    
    private int[] topoVis=null;
    public void setTopoVis(final int [] tv){
    	topoVis=tv;
    }

    /**
     * Returns the current sphere detail level.
     * @return the current sphere detail level.
     */
    public int getSphereDetail() {
        return sphereDetail;
    }

    /**
     * Sets the sphere detail level.
     * @param sphereDetail level of detail.
     */
    public void setSphereDetail(int sphereDetail) {
        this.sphereDetail = sphereDetail;
        this.dirty = true;
    }

    /**
     * Get the current associated <tt>ColorScale</tt>.
     * @return the currently associated <tt>ColorScale</tt>.
     */
    public ColorScale getColorScale() {
        return colorScale;
    }

    /**
     * Sets the associated <tt>ColorScale</tt>.  
     * This plot will use this <tt>ColorScale</tt> to resolve colors.
     * @param colorScale The <tt>ColorScale</tt>
     */
    public void setColorScale(ColorScale colorScale) {
        // first, remove ourselves from the previous (if any) colorScale's observer list
        if (this.colorScale != null) {
            this.colorScale.deleteObserver(this);
        }
        this.colorScale = colorScale;
        // add ourselves to the new colorScale
        if (colorScale != null) {
            colorScale.addObserver(this);
        }
    }
    
    public float getWidth() {
        return xSize;
    }

    public float getDepth() {
        return ySize;
    }

    public float getHeight() {
        return zSize;
    }

    public String getName() {
        return "ScatterPlot";
    }

    public void cleanUp() {

    }

    public JPanel getControlPanel(final VisRenderer visRenderer) {
        JPanel panel = new JPanel();

        panel.setLayout(new GridBagLayout());
        panel.setBorder(BorderFactory.createLoweredBevelBorder());

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(5, 5, 5, 5);
        gbc.anchor = GridBagConstraints.WEST;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 0.2;
        gbc.weighty = 0.2;

        final JSlider sphereSizeSlider = new JSlider(0, 20, (int) (sphereSize * 10));
        final JSlider sphereDetailSlider = new JSlider(2, 30, sphereDetail);

        sphereSizeSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent event) {
                try {
                    TopoPlot.this.setSphereSize(sphereSizeSlider.getValue() / 10.0f);
                    visRenderer.redraw();
                } catch (Exception e) {
                    VisTools.handleException(e);
                }
            }
        });

        sphereDetailSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent event) {
                try {
                    TopoPlot.this.setSphereDetail(sphereDetailSlider.getValue());
                    visRenderer.redraw();
                } catch (Exception e) {
                    VisTools.handleException(e);
                }
            }
        });
        
        VisTools.addCompItem(panel, new JLabel("Point size"), gbc, 0, 0, 1, 1);
        VisTools.addCompItem(panel, sphereSizeSlider, gbc, 1, 0, 1, 1);
        VisTools.addCompItem(panel, new JLabel("Point detail"), gbc, 0, 1, 1, 1);
        VisTools.addCompItem(panel, sphereDetailSlider, gbc, 1, 1, 1, 1);

        return panel;
    }

    public void render(VisRenderer visRenderer) {
        GLAutoDrawable glDrawable = visRenderer.getGLAutoDrawable();
        //Vec direction = visRenderer.getViewDirection();

//    public void render(GLAutoDrawable glDrawable, Vec direction) {
        if (axes != null) {
            axes.render(visRenderer);
        }

        if (!visible)
            return;

        GL2 gl = glDrawable.getGL().getGL2();

        if (dirty || displayList == 0) {
            displayList = gl.glGenLists(1);
            gl.glNewList(displayList, GL2.GL_COMPILE);
            privateRender(glDrawable);
            gl.glEndList();
            dirty = false;
        }
        gl.glCallList(displayList);
    }

    private void processValues() {

        float[] norms = new float[4];
        norms[0] = xSize;
        norms[1] = ySize;
        norms[2] = zSize;
        norms[3] = 1.0f;

        float mincut=0;
        float maxcut=0;
        for (int f = 0; f < 4; f++) {
            float maxValue = maxValueArr[f];// Float.MIN_VALUE;
            float minValue = minValueArr[f];//Float.MAX_VALUE;
//            for (int i = 0; i < values.length; i++) {
//                maxValue = Math.max(maxValue, values[i][f]);
//                minValue = Math.min(minValue, values[i][f]);
//            }
            
//            if(maxValue!=tmpMax)
//            	System.out.println("Bad Max: "+maxValue+" vs. "+tmpMax);
//            if(minValue!=tmpMin)
//            	System.out.println("Bad Min: "+minValue+" vs. "+tmpMin);
            
            boolean useMinCutoff = false;
            boolean useMaxCutoff = false;
            mincut=0;
            maxcut=0;
            if(isTopo&&f==3)
            {
            	if(minVis>0)
            	{
            		useMinCutoff=true;
            		float tmp = maxValue-minValue;
            		mincut=minValue+tmp*(minVis/100.0f);
            	}
            	else
            		mincut=minValue;
            	if(maxVis<100){
            		useMaxCutoff=true;
            		float tmp = maxValue-minValue;
            		maxcut=minValue+tmp*(maxVis/100.0f);
            	}
            	else
            		maxcut=maxValue;
            

            }

            for (int i = 0; i < values.length; i++) {
                if (maxValue - minValue == 0) {
                    values[i][f] = 0;
                    continue;
                } 
                
                if(useMinCutoff&&values[i][f]<mincut){
                	if(useMaxCutoff&&values[i][f]<maxcut&&mincut>maxcut){
                		
                	}
                	else
                	{
                		values[i][f]=Float.NaN;
                		continue;
                	}
                }
                else if(useMaxCutoff&&values[i][f]>maxcut){
                	if(useMinCutoff&&values[i][f]>mincut&&mincut>maxcut){
                		
                	}
                	else
                	{
                		values[i][f]=Float.NaN;
                		continue;
                	}
                }
//                if(isTopo&&i>=0&&i<4){
//                	continue;
//                }
//                else
                {
                    if (normalized) {
                        values[i][f] = (values[i][f] - minValue) / (maxValue - minValue) * norms[f];
                    } else {
                        values[i][f] = values[i][f] / maxValue * norms[f];
                    }
                }
            }

        }
        minShown=mincut;
        maxShown=maxcut;
    }

    
    private float statMin=Float.MAX_VALUE;
    private float statMax=Float.MIN_VALUE;
    private int statCount=0;
    private float statAcc=0;
    
    public float getStatMean(){
    	if(statCount>0){
    		return statAcc/(float)statCount;
    	}
    	else return Float.NaN;
    }
    public float getStatMax(){
    	return statMax;
    }
    public float getStatMin(){
    	return statMin;
    }
    
    private void resetTopoVals(){
        statMin=Float.MAX_VALUE;
        statMax=Float.MIN_VALUE;
        statCount=0;
        statAcc=0;
    }
    
    private boolean showCoord(int dex){
    	boolean use = checkCoord(dex);
    	if(use&&origValues!=null){
    		statCount++;
    		statAcc+=origValues[dex][3];
    		statMin=Math.min(origValues[dex][3], statMin);
    		statMax=Math.max(origValues[dex][3], statMax);
    	}
    	return use;
    }
    
    private boolean checkCoord(int dex){
    	if(Float.compare(values[dex][3],Float.NaN)==0)
    		return false;
    	if(topoVis==null||origValues==null)
    		return true;
    	
    	//System.out.println(topoVis[0]+" "+topoVis[1]+" "+topoVis[2]+" vs "+origValues[dex][0]+" "+origValues[dex][1]+" "+origValues[dex][2]);
    	
    	for(int i=0;i<3;i++){
    		if(topoVis[i]!=-1&&(topoVis[i]<origValues[dex][i]||topoVis[i]>=origValues[dex][i]+1))//topoVis[i]!=origValues[dex][i])
    			return false;
    	}
    	
    	return true;
    }
    
    private void privateRender(GLAutoDrawable glDrawable) {
        if (values == null)
            return;
        
        GL2 gl = glDrawable.getGL().getGL2();
        GLU glu = new GLU();

        gl.glShadeModel(GL2.GL_SMOOTH);

        // Set to red, in case there is no colorScale
        gl.glColor3f(1.0f, 0, 0);


        resetTopoVals();

        if (sphereSize < 0.1f||sphereDetail<3) {
            gl.glDisable(GL2.GL_LIGHTING);
            float actualSize=2.5f;
            if(sphereSize>=0.1f){
            	actualSize=sphereSize*30;
            }
            gl.glPointSize(actualSize);
            gl.glBegin(GL.GL_POINTS);
            for (int i = 0; i < values.length; i++) {
            	if(showCoord(i)){
                if (colorScale != null) {
                    Color color = colorScale.getColor(values[i][3]);
                    gl.glColor3f(color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f);
                }
                gl.glVertex3f(values[i][0], values[i][1], values[i][2]);
            	}

            }
            gl.glEnd();
        } else {
            gl.glEnable(GL2.GL_LIGHTING);
            gl.glEnable(GL.GL_DEPTH_TEST);
            gl.glFrontFace(GL.GL_CCW);
            GLUquadric qobj = glu.gluNewQuadric();
            gl.glEnable(GL.GL_CULL_FACE);
            glu.gluQuadricDrawStyle(qobj, GLU.GLU_FILL);
            glu.gluQuadricOrientation(qobj, GLU.GLU_OUTSIDE);
            glu.gluQuadricNormals(qobj, GLU.GLU_SMOOTH);

            for (int i = 0; i < values.length; i++) {
            	if(showCoord(i)){
                gl.glPushMatrix();
                gl.glTranslatef(values[i][0], values[i][1], values[i][2]);
                if (colorScale != null) {
                    Color color = colorScale.getColor(values[i][3]);
                    gl.glColor3f(color.getRed() / 255.0f, color.getGreen() / 255.0f, color.getBlue() / 255.0f);
                }
                glu.gluSphere(qobj, sphereSize, sphereDetail, sphereDetail);
                gl.glPopMatrix();
            	}
            }
        }

    }

    public void update(Observable o, Object arg) {
        if (o instanceof ColorScale) {
            this.dirty = true;
        }
    }

    public int getSelectedRow() {
        return selectedRow;
    }

    public void setSelectedRow(int selectedRow) {
        this.selectedRow = selectedRow;
    }

    public int getSelectedCol() {
        return selectedCol;
    }

    public void setSelectedCol(int selectedCol) {
        this.selectedCol = selectedCol;
    }

    
    /**
     * Returns whether or not this <tt>ScatterPlot</tt> is normalizing values.
     * @return whether or not this <tt>ScatterPlot</tt> is normalizing values.
     */
    public boolean getNormalized() {
        return normalized;
    }
    /**
     * Sets whether or not this <tt>ScatterPlot</tt> should normalize the values along its axes.
     * @param normalized <tt>true</tt> to normalize; <tt>false</tt> to leave values alone.
     */
    public void setNormalized(boolean normalized) {
        this.normalized = normalized;
    }

    /**
     * Returns whether or not this <tt>ScatterPlot</tt> instance is visible.
     * @return whether or not this <tt>ScatterPlot</tt> instance is visible.
     */
    public boolean getVisible() {
        return visible;
    }

    /**
     * Makes this <tt>ScatterPlot</tt> instance visible or invisible.
     * @param visible <tt>true</tt> to make the <tt>ScatterPlot</tt> visible; <tt>false</tt> to make it invisible.
     */
    public void setVisible(boolean visible) {
        this.visible = visible;
    }

    public void resetCanvas() {
        dirty = true;
        displayList = 0;
        if (axes != null ) {
            axes.resetCanvas();
        }
    }
}
