/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: J3DKeyBehavior.java
 * Written by Gilda Garreton.
 *
 * Copyright (c) 2005, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.sun.electric.plugins.j3d;

import java.awt.AWTEvent;
import java.awt.event.*;
import java.util.Enumeration;
import java.util.Arrays;

import javax.media.j3d.*;
import javax.vecmath.*;

/** Inspired in example found in Daniel Selman's book "Java 3D Programming"
 * For more information about the original example, contact Daniel Selman: daniel@selman.org
 * Free Software Foundation, Inc.
 * @author  Gilda Garreton
 * @version 0.1
*/

public class J3DKeyBehavior extends Behavior
{
	protected static final double FAST_SPEED = 10.0;
	protected static final double NORMAL_SPEED = 1.0;

	protected TransformGroup tGroup; /* Contains main scene*/
	protected Transform3D transform;
	protected WakeupCondition keyCriterion;

	private double delta = 5;
	private double rotateAmount = Math.PI / 10.0;
	private double speed = NORMAL_SPEED;

	private int forwardKey = KeyEvent.VK_UP;
	private int backKey = KeyEvent.VK_DOWN;
	private int leftKey = KeyEvent.VK_LEFT;
	private int rightKey = KeyEvent.VK_RIGHT;

	public J3DKeyBehavior(TransformGroup tg)
	{
		super();

		tGroup = tg;
		transform = new Transform3D();
	}

	public void initialize()
	{
		WakeupCriterion[] keyEvents = new WakeupCriterion[2];
		keyEvents[0] = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
		keyEvents[1] = new WakeupOnAWTEvent(KeyEvent.KEY_RELEASED);
		keyCriterion = new WakeupOr(keyEvents);

		wakeupOn(keyCriterion);
	}

	public void processStimulus(Enumeration criteria)
	{
		WakeupCriterion wakeup;
		AWTEvent[] event;

		while(criteria.hasMoreElements( ))
		{
			wakeup = (WakeupCriterion) criteria.nextElement( );

			if( !(wakeup instanceof WakeupOnAWTEvent) )	
				continue;

			event = ((WakeupOnAWTEvent)wakeup).getAWTEvent( );

			for( int i = 0; i < event.length; i++ )
			{
				if( event[i].getID( ) == KeyEvent.KEY_PRESSED ||
                    event[i].getID( ) == KeyEvent.KEY_RELEASED )
					processKeyEvent((KeyEvent)event[i]);
			}
		}

		wakeupOn( keyCriterion );
	}

	protected void processKeyEvent(KeyEvent event)
	{
		int keycode = event.getKeyCode();

		if(event.isShiftDown())
			speed = FAST_SPEED;
		else 
			speed = NORMAL_SPEED;

		if( event.isAltDown())
			altMove(keycode);
		else if(event.isControlDown())
			controlMove(keycode );
		else
			standardMove(keycode);
	}

	//moves forward backward or rotates left right
	private void standardMove(int keycode)
	{
		if(keycode == forwardKey)
			moveAlongAxis(Z, 1);
		else if(keycode == backKey)
			moveAlongAxis(Z, -1);
		else if(keycode == leftKey)
			rotateAlongAxis(Y, 1);
		else if(keycode == rightKey)
			rotateAlongAxis(Y, -1);
	}

	//moves left right, rotate up down
	protected void altMove(int keycode)
	{
		if(keycode == forwardKey)
			rotateAlongAxis(X, 1);
		else if(keycode == backKey)
			rotateAlongAxis(X, -1);
		else if(keycode == leftKey)
			moveAlongAxis(X, -1);
		else if(keycode == rightKey)
			moveAlongAxis(X, 1);
	}

	//move up down, rot left right
	protected void controlMove(int keycode)
	{
		if(keycode == forwardKey)	
			moveAlongAxis(Y, 1);
		else if(keycode == backKey)
			moveAlongAxis(Y, -1);
		else if(keycode == leftKey)
			rotateAlongAxis(Z, 1);
		else if(keycode == rightKey)
			rotateAlongAxis(Z, -1);
	}

    private static double[] values = new double[3];
    private static Vector3d move = new Vector3d();

    /**
     * Method to shift along axis in direction provided
     * @param axis
     * @param dir
     */
	public void moveAlongAxis(int axis, int dir)
	{
        Arrays.fill(values, 0);
        values[axis] = dir * getMovementRate();
        move.set(values);
        // If move gets a collision, then move back
		if (!doMove(move, false))
        {
            values[axis] = -dir * getMovementRate();
            move.set(values);
            doMove(move, true);
        }
	}

    /**
     * Method to rotate along given axis and direction provided
     * @param axis
     * @param dir
     */
	protected void rotateAlongAxis(int axis, int dir)
	{
        double radian = rotateAmount * speed;
        // in case of collision, move the opposite direction
		if (!rotate(axis, dir * radian, false))
           rotate(axis, -dir * radian, true);
	}

	protected boolean updateTransform(boolean force)
	{
		tGroup.setTransform(transform);
        return true;
	}

    private static final int X = 0;
    private static final int Y = 1;
    private static final int Z = 2;

    /**
     * Method to set the original rotation
     * @param rotVals
     */
    public void setHomeRotation(double[] rotVals)
    {
        for (int i = 0; i < rotVals.length; i++)
            rotate(i, rotVals[i], true);
    }

    /**
     * Method that rotates along given axis
     * @param axis
     * @param radians
     * @return True if there was no collision
     */
	protected boolean rotate(int axis, double radians, boolean force)
	{
		tGroup.getTransform(transform);
		Transform3D toMove = new Transform3D();
        switch(axis)
        {
            case X:
                toMove.rotX(radians);
                break;
            case Y:
                toMove.rotY(radians);
                break;
            case Z:
                toMove.rotZ(radians);
                break;
        }
		transform.mul(toMove);
        // Need to move in opposite direction to avoid collision
        boolean noCollision = updateTransform(force);
        return (noCollision);
	}

    void zoomInOut(boolean out)
    {
        double z_factor = 0.7;//Math.abs(0.7);
        //double factor = (out) ? (0.5/z_factor) : (2*z_factor);
        double factor = (out) ? (1/z_factor) : (z_factor);

        // Remember old matrix
        tGroup.getTransform(transform);
        Matrix4d mat = new Matrix4d();
        transform.get(mat);
        double dy = transform.getScale() * factor;
        transform.setScale(dy);
        tGroup.setTransform(transform);
    }

	private static Transform3D toMove = new Transform3D();
    private boolean doMove(Vector3d theMove, boolean force)
	{
		tGroup.getTransform(transform);
        toMove.setIdentity();
		toMove.setTranslation(theMove);
		transform.mul(toMove);
		return updateTransform(force);
	}

	protected double getMovementRate()
	{
		return delta * speed;
	}
}
