package vgp.tutor.ode;

import java.awt.Color;

import jv.object.PsDebug;
import jv.geom.PgElementSet;
import jv.geom.PgPointSet;
import jv.geom.PgPolygon;
import jv.geom.PgVectorField;
import jv.loader.PsXmlLoader;
import jv.number.PdVector_IP;
import jv.number.PuDouble;
import jv.project.PvCameraIf;
import jv.project.PvDisplayIf;
import jv.project.PvPickEvent;
import jv.project.PjProject;
import jv.project.PvViewerIf;
import jv.thirdParty.ruler.PgAxes;
import jv.vecmath.PdVector;
import jvx.numeric.PnOdeExpr;

/**
 * Demo project for solving an ODE using class PnOde.
 * Ode expression may be interactively typed or passed as argument from
 * an Html page containing the applet.
 * 
 * @see			jvx.numeric.PnOdeExpr
 * @author		Konrad Polthier
 * @version		27.02.03, 1.50 revised (kp) Blackboard added for picking, replacing picking on background.<br>
 *					23.04.01, 1.20 revised (kp) Initial point added to display, Html parameter added.<br>
 *					02.10.99, 1.10 revised (kp) PnExprOde renamed to PnOdeExpr and moved to jvx.numeric.<br>
 *					00.00.98, 1.00 created (kp)
 */
public class PjExprOde extends PjProject {
	/** Flag whether optional initial point is displayed. */
	protected	boolean					m_bShowInitialPoint;
	/** Visible initial point of differential equation. */
	protected	PgPointSet				m_initialPoint;
	/** Numerically computed solution of differential equation. */
	protected	PgPolygon				m_solution;
	/** 2D blackboard used for picking. */
	protected	PgElementSet			m_blackboard;
	/** Flag whether optional vector field is displayed. */
	protected	boolean					m_bShowVectorField;
	/** Display an optional vector field showing the direction of the ODE. */
	protected	PgPointSet				m_vectorField;
	/** Differential equation structure. */
	protected	PnOdeExpr				m_exprOde;
	/** String representation of differential equation. */
	protected	String					m_equation;
	/** Default string representation of differential equation. */
	private		String					m_defaultEquation = "-dy/2-y";
	/** Order of differential equation. */
	protected	int						m_order;
	/** Default order of differential equation. */
	private		int						m_defaultOrder = 2;
	/** Stepsize used in numerical integration method. */
	protected	PuDouble					m_h;
	/** Length of integration interval on x-axis. */
	protected	PuDouble					m_length;
	/** X-initial argument. */
	protected	PuDouble					m_xStart;
	/** Default x-initial argument. */
	protected	double					m_defaultXStart	= -8.;
	/** Y-initial arguments, length of vector is equal to the order of the ODE. */
	protected	PdVector					m_yStart;
	/** Default y-initial arguments, length of vector is equal to the order of the ODE. */
	protected	PdVector					m_defaultYStart	= new PdVector(6., 1.);
	/** Panel of y-initial arguments. */
	protected	PdVector_IP				m_pYStart;

	public PjExprOde() {
		super("ODE Solver");
		m_exprOde				= new PnOdeExpr();
		m_blackboard			= new PgElementSet(2);
		m_initialPoint			= new PgPointSet(2);
		m_solution				= new PgPolygon(3);
		m_h						= new PuDouble("Step Size", this);
		m_length					= new PuDouble("Length", this);
		m_xStart					= new PuDouble("Initial x", this);

		m_yStart					= new PdVector(m_defaultOrder);
		m_pYStart				= new PdVector_IP();
		m_pYStart.setTitle("Initial y");
		m_pYStart.setParent(this);
		m_pYStart.setVector(m_yStart);

		if (getClass() == PjExprOde.class)
		  init();
	}
	public void init() {
		super.init();

		m_blackboard.computePlane(9, 9, -20., -20., 20., 20.);
		m_blackboard.setName("X-Y Plane");
		m_blackboard.showElements(false);
		m_blackboard.setGlobalEdgeColor(new Color(161, 161, 161));
		m_blackboard.setGlobalElementColor(Color.white);
		
		setOrder(m_defaultOrder);
		m_h.setDefBounds(0.01, 2., 0.1, 1.0);
		m_h.setDefValue(.2);
		m_h.init();
		m_length.setDefBounds(1., 50., 0.1, 1.0);
		m_length.setDefValue(15.);
		m_length.init();
		m_xStart.setDefBounds(-20., +20., 0.1, 1.0);
		m_xStart.setDefValue(m_defaultXStart);
		m_xStart.init();
		m_yStart.copy(m_defaultYStart);
		m_pYStart.update(m_yStart);
		setEquation(m_defaultEquation);

		m_initialPoint.init();
		m_initialPoint.setName("Initial Point");
		m_initialPoint.showName(true);
		m_initialPoint.setGlobalVertexSize(3.);
		m_initialPoint.setGlobalVertexColor(Color.blue);
		m_initialPoint.setNumVertices(1);
		m_initialPoint.setVertex(0, m_xStart.getValue(), m_yStart.getEntry(0));
		showInitialPoint(true);

		m_solution.init();
		m_solution.setName("ODE solution");
		
		showVectorField(false);
	}
	public void start() {
		// Parse parameters from Html page or command line, if available
		PvViewerIf viewer = getViewer();
		if (viewer != null) {
			String sEquation = viewer.getParameter("vgp.tutor.ode.PjExprOde#equation");
			if (sEquation != null) {
				m_defaultEquation = sEquation;
				setEquation(m_defaultEquation);
			}
			String sOrder = viewer.getParameter("vgp.tutor.ode.PjExprOde#order");
			if (sOrder != null) {
				try {
					int order = Integer.parseInt(sOrder);
					m_defaultOrder = order;
					setOrder(m_defaultOrder);
				} catch (NumberFormatException e) {
					if (PsDebug.WARNING) PsDebug.warning("wrong format = "+sOrder);
				}
			}
			String sInitial = viewer.getParameter("vgp.tutor.ode.PjExprOde#initial");
			if (sInitial != null) {
				PdVector initial = PsXmlLoader.parsePdVector(sInitial);
				if (initial!=null) {
					if (initial.getSize()-1 != m_defaultOrder) {
						if (PsDebug.WARNING) PsDebug.warning("number of initial values incompatible with current order = "+m_defaultOrder);
					} else {
						double xVal = initial.getEntry(0);
						m_xStart.setDefValue(xVal);
						m_xStart.setDefBounds(-20., +20., 0.1, 1.0);
						m_xStart.init();
						m_defaultYStart.setSize(initial.getSize()-1);
						for (int i=1; i<initial.getSize(); i++)
							m_defaultYStart.setEntry(i-1, initial.getEntry(i));
						m_yStart.setSize(initial.getSize()-1);
						m_yStart.copy(m_defaultYStart);
						m_pYStart.update(m_yStart);
					}
				} else {
					if (PsDebug.WARNING) PsDebug.warning("wrong format = "+sInitial);
				}
			}
		}
		solve();
		update(this);

		addGeometry(m_blackboard);
		addGeometry(m_initialPoint);
		addGeometry(m_solution);
		selectGeometry(m_blackboard);
		PvDisplayIf disp = getDisplay();
		if (disp != null) {
			// disp.showGrid(true);
			// HACK: steering the axes might be put into PvDisplayIf.
			PgAxes axes = ((jv.viewer.PvDisplay)disp).getAxes();
			if (axes == null)
				axes = new PgAxes(2);
			axes.setMode(PgAxes.AXES_2DXY_CENTER);
			PdVector [] bndbox = PdVector.realloc(null, 2, 3);
			double eps = 2.0;
			bndbox[0].set(-20.-eps,-20.-eps, 0.); 
			bndbox[1].set( 20.+eps, 20.+eps, 0.); 
			axes.configure(bndbox, null);
			axes.setEnabledAutoBounds(false);
			((jv.viewer.PvDisplay)disp).setAxes(axes);
			disp.showAxes(true);
			disp.selectCamera(PvCameraIf.CAMERA_ORTHO_XY);		// project onto xy-plane
			disp.getCamera().setDist(21.);
			setEnabledAutoFit(false);
			disp.setMajorMode(PvDisplayIf.MODE_INITIAL_PICK);	// force picking of initial point
		}
		super.start();
	}
	public void showInitialPoint(boolean flag) {
		m_bShowInitialPoint = flag;
		if (m_initialPoint == null)
			return;
		if (m_bShowInitialPoint) {
			addGeometry(m_initialPoint);
		} else {
			removeGeometry(m_initialPoint);
		}
	}
	public boolean isShowingVectorField() { return m_bShowVectorField; }
	public void showVectorField(boolean flag) {
		m_bShowVectorField = flag;
		if (m_bShowVectorField) {
			if (m_vectorField == null) {
				m_vectorField	= new PgPointSet(2);
				m_vectorField.setName("Vectors");
				m_vectorField.showVertices(false);
			}
			double size = m_xStart.getMax()-m_xStart.getMin();
			int num = (int)(size / m_xStart.getPageIncr());
			num++;
			m_vectorField.setNumVertices(num*num);
			PgVectorField vf = m_vectorField.getVectorField(0);
			if (vf == null) {
				vf = new PgVectorField(2);
				vf.setGeometry(m_vectorField);
				vf.setBasedOn(PgVectorField.VERTEX_BASED);
				m_vectorField.addVectorField(vf);
				m_vectorField.setGlobalVectorColor(Color.blue);
				m_vectorField.showVectorFields(true);
			}
			vf.setNumVectors(num*num);
			double xPos = m_xStart.getMin();
			PdVector y	= new PdVector(m_yStart.getSize());
			y.copy(m_yStart);
			PdVector dy = new PdVector(m_yStart.getSize());
			int ind = 0;

			for (int i=0; i<num; i++) {
				double yPos = m_xStart.getMin();
				for (int j=0; j<num; j++) {
					y.setEntry(0, yPos);
					m_vectorField.setVertex(ind, xPos, yPos);
					m_exprOde.diffEquation(xPos, y.m_data, dy.m_data);
					vf.setVector(ind, 1., dy.m_data[0]);
					ind++;
					yPos += size/num;
				}
				xPos += m_xStart.getPageIncr();
			}
			addGeometry(m_vectorField);
			m_vectorField.update(m_vectorField);
		} else {
			if (m_vectorField != null) {
				removeGeometry(m_vectorField);
			}
		}
	}
	public void setOrder(int order) {
		m_order = order;
		m_yStart.setSize(order);
		m_pYStart.setVector(m_yStart);
		m_exprOde.setOrder(order);
		if (order != 1)
			showVectorField(false);
	}
	public void setEquation(String eq) {
		m_equation = eq;
		m_exprOde.setEquation(m_equation);
		// Refresh vector field if visible
		if (isShowingVectorField())
			showVectorField(true);
	}
	public void setEquation(String eq, String [] parm) {
		m_equation = eq;
		m_exprOde.setEquation(m_equation, parm);
		// Refresh vector field if visible
		if (isShowingVectorField())
			showVectorField(true);
	}
	public void solve() {
		m_initialPoint.setVertex(0, m_xStart.getValue(), m_yStart.getEntry(0));
		m_initialPoint.update(m_initialPoint);
		m_exprOde.setInitialData(m_xStart.getValue(), m_yStart.m_data, m_h.getValue(), m_length.getValue());
		m_exprOde.solve(m_solution);
		m_solution.update(m_solution);
	}
	public void pickInitial(PvPickEvent pos) {
		if (pos==null || pos.getGeometry()==null ||
			 pos.getGeometry()!=m_blackboard) {
			if (PsDebug.WARNING) PsDebug.warning("select background grid and pick inside.");
			return;
		}
		PdVector base = pos.getVertex();
		m_xStart.setValue(base.m_data[0]);
		m_yStart.setEntry(0, base.m_data[1]);
		m_pYStart.update(m_yStart);
		solve();
	}
	public void dragInitial(PvPickEvent pos) { pickInitial(pos); }

	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (event == m_h) {
			solve();
			return true;
		} else if (event == m_length) {
			solve();
			return true;
		} else if (event == m_xStart) {
			solve();
			return true;
		} else if (event == m_pYStart) {
			solve();
			return true;
		}
		return false;
	}    
}

