/**
 * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT
 * All rights reserved. Use is subject to license terms. See LICENSE.TXT
 */
package org.diirt.javafx.graphene;

import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.diirt.javafx.tools.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import org.diirt.datasource.PVManager;
import org.diirt.datasource.PVReader;
import org.diirt.datasource.PVReaderEvent;
import org.diirt.datasource.PVReaderListener;
import org.diirt.datasource.graphene.Graph2DExpression;
import org.diirt.datasource.graphene.Graph2DResult;
import org.diirt.graphene.Graph2DRendererUpdate;
import org.diirt.javafx.util.Executors;
import static org.diirt.util.time.TimeDuration.ofHertz;

/**
 * Displays a graph, handles its resizing, and handles its connection
 * with a data source.
 * <p>
 * To produce a custom graph view, e.g. a <code>BubbleGraphView</code>, you must
 * first extends <code>BaseGraphView</code>. You must define the createExpression()
 * method which will be sent to <code>PVManager</code> to generate graph data. If
 * there are any properties relevant to the graph, you must create those 
 * property objects, and implement change listeners that handle what happens when
 * these graph properties change. For example, if the interpolation scheme changes,
 * you will need your listener to update the graph to use the new interpolation scheme.
 * Finally, you may also override onMouseMove() if you wish to handle mouse events
 * for the graph.
 * <p>
 * To summarize, in creating a custom graph view, there are three relevant things to be defined:
 * <ul>
 *  <li> <code>createExpression()</code> (mandatory)
 *  <li> Any graph properties you desire and their getters and setters
 *  <li> <code>onMouseMoved()</code> if you desire
 * </ul>
 * <p>
 * For <code>BaseGraphApp</code> demonstrations, we have also moved 
 * configuration dialogs to be handled by the <code>BaseGraphView</code>. If
 * there are any properties that the user should be allowed to configure,
 * also be sure to add those to a <code>ConfigurationDialog</code> when you
 * extend <code>BaseGraphView</code>.
 * 
 * @author mjchao, carcassi
 * @param <T> the type of renderer object used to create the graphs displayed
 * in by this graph view
 */
abstract public class BaseGraphView< T extends Graph2DRendererUpdate< T > > extends BorderPane {

    VImageView imagePanel = new VImageView();
    
    StringProperty formula = new SimpleStringProperty( "" );
    
    /**
     * stores the last exception that was reported by PVManager when it tried
     * to graph
     */
    final private Property< Exception > lastException = new SimpleObjectProperty< Exception >();
    
    public BaseGraphView() {
	
	//allow this panel to shrink -- for some reason, JavaFX doesn't default
	//to this
	setMinSize( 0 , 0 );
        setCenter(imagePanel);
	
	//watch for mouse movements, if necessary
	this.imagePanel.setOnMouseMoved( new EventHandler< MouseEvent >() {

	    @Override
	    public void handle(MouseEvent event) {
		onMouseMove( event );
	    }
	});
	
	this.imagePanel.widthProperty().addListener(new ChangeListener< Number >() {

	    @Override
	    public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
		if ( graph != null ) {
		    graph.update(graph.newUpdate().imageWidth(Math.max( 1 , newValue.intValue() ) ));
		}
	    }
	});
	
	this.imagePanel.heightProperty().addListener( new ChangeListener< Number >() {

	    @Override
	    public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
		if ( graph != null ) {
		    graph.update(graph.newUpdate().imageHeight(Math.max(1 , newValue.intValue())));
		}
	    }
	    
	});
	
	this.formula.addListener( new ChangeListener< String >() {

	    @Override
	    public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
		reconnect( newValue );
	    }
	});
    }
    
    /**
     * Sets the formula for the data this graph will display
     * 
     * @param formula a data formula for PVManager
     */
    public void setFormula( String formula ) {
	this.formula.setValue( formula );
    }
    
    /**
     * @return the data formula currently being displayed on the graph
     */
    public String getFormula() {
	return this.formula.getValue();
    }
    
    public StringProperty formulaProperty() {
	return this.formula;
    }
    
    public ReadOnlyProperty< Exception > lastExceptionProperty() {
	return this.lastException;
    }
    
    /**
     * @return the last exception that was reported by PVManager when it tried
     * to graph. It is null, if PVManager did not report an exception
     */
    public Exception getlastException() {
	return this.lastException.getValue();
    }
    
    /**
     * Called whenever the mouse moves over the graph image of this view. By
     * default, this does nothing. Override this method to provide custom
     * functionality.
     * 
     * @param e the event associated with the mouse movement
     */
    protected void onMouseMove( MouseEvent e ) {
	//do nothing by default
    }
    
    /**
     * Reads from the data source.
     */
    private PVReader<Graph2DResult> pv;
    
    /**
     * An expression to be sent to the data source to receive data back.
     */
    Graph2DExpression< T > graph;

    /**
     * Redraws the graph
     */
    protected void reconnect() {
	reconnect( this.getFormula() );
    }
    /**
     * Sends the given data formula to the data source and asks it for data. 
     * The received data is then graphed on this <code>BaseGraphView</code>.
     * 
     * @param dataFormula the data formula to use
     */
    protected void reconnect( String dataFormula ) {
	
        if (pv != null) {
            pv.close();
            imagePanel.setVImage(null);
            graph = null;
        }
	
	if ( dataFormula == null ) {
	    return;
	}
        
        graph = createExpression( dataFormula );
        
        graph.update(graph.newUpdate().imageHeight(Math.max(1 , imagePanel.heightProperty().intValue()))
                .imageWidth(Math.max( 1 , imagePanel.widthProperty().intValue() ) ));
        pv = PVManager.read(graph)
                .notifyOn(Executors.javaFXAT())
                .readListener(new PVReaderListener<Graph2DResult>() {

                    @Override
                    public void pvChanged(PVReaderEvent<Graph2DResult> event) {
			lastException.setValue( pv.lastException() );
                        if (pv.getValue() != null) {
                            imagePanel.setVImage(pv.getValue().getImage());
                        }
                    }
                })
                .maxRate(ofHertz(100));
	
    }
    
    /**
     * Creates an expression that the data source can understand from the
     * provided data formula.
     * 
     * @param dataFormula the data formula to use
     * @return an expression that the data source can understand
     */
    abstract public Graph2DExpression< T > createExpression( String dataFormula );
    
    
}

@@ -2711,16 +2711,30 @@
  %7B%0A%0A    
+private final 
 VImageVi
@@ -2776,16 +2776,30 @@
    %0A    
+private final 
 StringPr

improve access modifiers