/**	
	Company:		Shout Interactive
	Project:		Shout3D Core Implementation
	Class:			MultiTestPanel
	Date:			April 26, 1999
	Description:	Tests various features. Press '?' for Java Panel instructions
	(C) Copyright Shout Interactive, Inc. - 1997/1998/1999 - All rights reserved
 */

package applets;

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Date;
import java.util.Vector;
import java.net.URL;
import shout3d.*;
import shout3d.math.*;
import shout3d.core.*;

/**
 * 
 * Panel that tests lots of features, including 
 * little markers that show the location and normal of geometry
 * below the mouse.
 * 
 *  Press '?' to see instructions in the Java Panel.
 * 
 * @author Paul Isaacs
 * @author Jim Stewartson
 * @author Dave Westwood
 */

public class MultiTestPanel extends Shout3DPanel implements DeviceObserver, RenderObserver {
	boolean   locationTracking = false;
	boolean   normalTracking = false;
	int		  curMouseX, curMouseY;
	boolean   mouseMoved = false;

	/**
	 * Constructor
	 */
	public MultiTestPanel(Shout3DApplet applet){
		super(applet);
	}
	
	public void customInitialize(){
		// Register to watch rendering, the keyboard and mouse
		addDeviceObserver(this,"KeyboardInput", null);
		addDeviceObserver(this,"MouseInput", null);
		getRenderer().addRenderObserver(this,null);

		String normalTrackingString   = applet.getParameter("normalTracking");
		if (normalTrackingString != null && normalTrackingString.equals("true"))
			normalTracking = true;
		else
			normalTracking = false;

		String locationTrackingString = applet.getParameter("locationTracking");
		if (locationTrackingString != null && locationTrackingString.equals("true"))
			locationTracking = true;
		else
			locationTracking = false;

	}
	    
    /**
	 *  Clean up by unregistering observers
	 */
	protected void finalize() throws Throwable { 
		getRenderer().removeRenderObserver(this);
		removeDeviceObserver(this,"KeyboardInput");
		removeDeviceObserver(this,"MouseInput");
		super.finalize();
	}
	
	//{{ RenderObserver methods
	public void onPreRender(Renderer r, Object userData) {}
	
	/* Override to do a pick and show results, if desired.
	 * Tracking of the pick must be performed during onPostRender.
	 * This is because multithreading may put rendering in a separate thread,
	 * but  trackPick() adds/removes children.
	 * Hence, unless we insure that trackPick() occurs between renders, 
	 * the children could disappear during the middle
	 * of a render, which can cause null pointer exceptions.
	 */
	public void onPostRender(Renderer r, Object userData) {
		if (mouseMoved &&  (locationTracking || normalTracking))
			trackPick();
	}
	//}} RenderObserver methods
	
	//{{ DeviceObserver methods
	/**
	 * Processes mouse and keyboard input.
	 * 
	 * Moving the mouse records the mouse location for later use during
	 * trackPick(), which is called between renders (see onPostRender above)
	 * 
	 * Pressing keyboard keys performs any of a series of tests.
	 * 
	 */
	public boolean onDeviceInput(DeviceInput di, Object userData) {
		if (di instanceof MouseInput){
			if (((MouseInput)di).which == MouseInput.MOVE) {
				curMouseX = ((MouseInput)di).x;
				curMouseY = ((MouseInput)di).y;
				// Do not do the pick right here.  Set this flag so it will
				// occur during onPostRender. See onPostRender for why.
				mouseMoved = true;
			}
		}
		else {
			KeyboardInput ki = (KeyboardInput) di;
			if (ki.which == KeyboardInput.PRESS) {
				switch(ki.key) {
				case 'w': case 'W':
					// W key pressed. Write the scene to a little text frame
					// (WriterTextFrame is defined in this file.
					if (getScene() != null) {
						WriterTextFrame myFrame = new WriterTextFrame();
						myFrame.resize(400, 600);
						myFrame.show();

						ByteArrayOutputStream os = new ByteArrayOutputStream();
						PrintStream ps = new PrintStream(os);
						Shout3DWriter writer = new Shout3DWriter();
						writer.write(getScene(),ps);
						myFrame.getTextArea().setText(os.toString());
					}
					break;
				case 'n': case 'N':
					// N key pressed. Toggle normal-tracking. Each time the mouse moves, do a 
					// pick and place arrow at picked location, pointing in direction of normal
					// info to the console. 
					normalTracking = !normalTracking;
					if (!normalTracking)
						hideNormalMarker();
					break;
				case 'l': case 'L':
					// L key pressed. Toggle location tracking. Each time the mouse moves, do a pick 
					// and place nubbin at picked location.
					locationTracking = !locationTracking;
					if (!locationTracking)
						hidePositionMarker();
					break;
				case '1':
					//  prints paths to all IndexedFaceSet nodes.
					searchAndPrint("IndexedFaceSet");
					break;
				case '2':
					//  prints paths to all TimeSensor nodes.
					searchAndPrint("TimeSensor");
					break;
				case '3':
					//  prints paths to all Group nodes.
					searchAndPrint("Group");
					break;
				case '4':
					//  prints paths to all nodes.
					searchAndPrint("Node");
					break;
				case '?':
					// Print help message.
					System.out.println("-----------------------------------");
					System.out.println("KEY:	ACTION:");
					System.out.println("n       NORMAL picking toggle.  Turns tracking of surface normal on/off");
					System.out.println("l       LOCATION picking toggle.  Turns tracking of picked location on/off");
					System.out.println("w       WRITE scene to textArea in VRML-like syntax");
					System.out.println("1       #1--diagnostic - search for IndexedFaceSets");
					System.out.println("			and print paths to them");
					System.out.println("2       #2--diagnostic - search for TimeSensors");
					System.out.println("			and print paths to them");
					System.out.println("3       #3--diagnostic - search for Groups");
					System.out.println("			and print paths to them");
					System.out.println("4       #4--diagnostic - search for all Nodes");
					System.out.println("			and print paths to them");
				}
			}
		}
		// Let other entities have a chance to handle the input
		return false;
	}
	//}} DeviceObserver methods
	
	/**
	 * Searches for paths to all nodes of the given type, printing the results
	 * to the console.
	 */
	void searchAndPrint(String typeName) {
		System.out.println("RESULTS OF SEARCH FOR ALL " + typeName + "s :");
		Searcher s = getNewSearcher();
		s.setType(typeName);
		Node[][] paths = s.searchAll(getScene());
		if (paths == null || paths.length == 0) {
			System.out.println("No paths found");
			return;
		}
		System.out.println("Number of paths is " + paths.length);
		for (int i = 0; i < paths.length; i++) {
			Node[] p = paths[i];
			System.out.println("PATH " + i);
			for (int j = 0; j < p.length; j++) {
				if (p[j] == null) 
					System.out.println("   node " + j + " is null");
				else {
					String name = p[j].getDEFName();
					if (name == null)
						name = "";
					System.out.println("   node " + j + " name:" + name + " type " + p[j].getTypeName());
				}
			}
		}
		System.out.println("-------------------------------------------");
	}

	Picker myPicker;	
	private Node[] pickPath;
	/**
	 * Tracks the current cursor location with a nubbin or an arrow.
	 * 
	 * If normalTracking, then an arrow will be stuck at the picked
	 * location, pointing in the direction of the pick-point-normal.
	 * 
	 * If locationTracking, then a nubbin will be placed at the picked location.
	 */
	void trackPick() {
		if (myPicker == null)
			myPicker = getNewPicker();
		myPicker.setPickInfo(Picker.POINT, (normalTracking || locationTracking));//Only needed for these two
		myPicker.setPickInfo(Picker.NORMAL, normalTracking);//Only if normalTracking
		// Hide the markers, so that the markers can't be picked.
		hideNormalMarker();
		hidePositionMarker();
		pickPath = myPicker.pickClosest(curMouseX, curMouseY);
		float[] pickedPoint = myPicker.getPickInfo(Picker.POINT);//Garbage if (!normalTracking&&!locationTracking)
		float[] pickedNormal = myPicker.getPickInfo(Picker.NORMAL);//Garbage if !normalTracking
		if (pickPath != null){
			if (locationTracking)
				showPositionMarker(pickedPoint);
			if (normalTracking)
				showNormalMarker(pickedPoint, pickedNormal);
		}
		mouseMoved = false;
	}
	
	// These store references to the markers
	Transform positionMarker, normalMarker;
	// These are cached to easily call addChidren/removeChildren
	Node[]    positionMarkerKidArray;
	Node[]    normalMarkerKidArray;
	// Scale applied to the marker,
	// can be set with the markerScale parameter in the html file.
	float[] markerScale = { .1f, .1f, .1f};
	
	/**
	 * Hides the normal marker by removing from the scene.
	 */
	void hideNormalMarker(){
		if (normalMarker != null) {
			getScene().removeChildren(normalMarkerKidArray);
		}
	}
	/**
	 * Hides the position marker by removing from the scene.
	 */
	void hidePositionMarker(){
		if (positionMarker != null){
			getScene().removeChildren(positionMarkerKidArray);
		}
	}
	/**
	 * Shows the normal marker and places according to input parameters.
	 */
	void showNormalMarker(float[/*3*/]position, float[/*3*/] direction){
		if (normalMarker == null){
			initMarkerGeoms();
		}
		normalMarker.translation.setValue(position);
		//Set rotation to point the y axis towards the direction. Axis is cross product, angle is acos(dotProduct)
		// Axis is cross product yAxis.cross(direction), which is (dz, 0, -dx)
		normalMarker.rotation.getValue()[0] = direction[2];
		normalMarker.rotation.getValue()[1] = 0;
		normalMarker.rotation.getValue()[2] = -direction[0];
		MatUtil.normalize(normalMarker.rotation.getValue()); // will just normalize 1st 3 of 4 components
		normalMarker.rotation.getValue()[3] = (float) Math.acos(direction[1]); // angle whose cosine is dotprod of (0,1,0) and direction
		normalMarker.rotation.setValue(normalMarker.rotation.getValue());
		getScene().addChildren(normalMarkerKidArray);
	}
	/**
	 * Shows the position marker and places according to input parameters.
	 */
	void showPositionMarker(float[/*3*/]position){
		if (positionMarker == null){
			initMarkerGeoms();
		}
		positionMarker.translation.setValue(position);
		getScene().addChildren(positionMarkerKidArray);
	}		
	
	/**
	 * Reads the file given by the parameter "markerGeometry"
	 * and searches inside for two named nodes:
	 * "PositionMarker" and "NormalMarker"
	 * 
	 * Saves these and also scales them by markerScale (which
	 * may be set as an html input parameter)
	 */
	void initMarkerGeoms(){
		Transform markerRoot = new Transform();
		String markerGeomString = applet.getParameter("markerGeometry");
		if (markerGeomString == null){
			System.out.println("Error -- Mo markerGeometry parameter specified for applet");
			System.out.println("         Can not show picked location");
			return;
		}
		String[] urlArray = { markerGeomString };
		// Don't load in a separate thread:
		boolean wasSeparate = isLoadResourcesInSeparateThread();
		setLoadResourcesInSeparateThread(false);
		loadURL(urlArray, markerRoot);
		setLoadResourcesInSeparateThread(wasSeparate);
		if (markerRoot == null) {
			throw new Shout3DException("ERROR - could not load marker geometry");
		}
		Searcher mySearcher = getNewSearcher();
		
		// initialize positionMarker
		mySearcher.setDefName("PositionMarker");
		Node[] sResult = mySearcher.searchFirst(markerRoot);
		if (sResult==null){
			throw new Shout3DException("ERROR - could not find PositionMarker");
		}
		positionMarker = (Transform) sResult[sResult.length-1];
		// Create the array used to add the positionMarker to the scene.
		positionMarkerKidArray = new Node[1];
		positionMarkerKidArray[0] = positionMarker;
		
		// initialize normalMarker
		mySearcher.setDefName("NormalMarker");
		sResult = mySearcher.searchFirst(markerRoot);
		if (sResult==null){
			throw new Shout3DException("ERROR - could not find NormalMarker");
		}
		normalMarker = (Transform) sResult[sResult.length-1];
		// Create the array used to add the normalMarker to the scene.
		normalMarkerKidArray = new Node[1];
		normalMarkerKidArray[0] = normalMarker;

	    String markerScaleParamString = applet.getParameter("markerScale");
		if (markerScaleParamString != null){
			markerScale[0] = markerScale[1] = markerScale[2] = Float.valueOf(markerScaleParamString).floatValue();
		}

		positionMarker.scale.setValue(markerScale);
		normalMarker.scale.setValue(markerScale);
		
	}
		
}

/**
 * A little class that puts a text area in its own 
 * free floating frame.
 */
class WriterTextFrame extends Frame {
	TextArea myTextArea;
	WriterTextFrame() {
		setLayout(new BorderLayout());
		myTextArea = new TextArea();
		this.add("Center",myTextArea);
	}
	
	TextArea  getTextArea() { return myTextArea; }
}
