import com.xith3d.scenegraph.TransformGroup;
import java.awt.event.AWTEventListener;
import java.awt.event.MouseEvent;
import java.awt.event.KeyEvent;
import javax.vecmath.Vector3f;
import java.awt.AWTEvent;
import java.awt.Toolkit;
import java.awt.Robot;
import java.awt.Point;

public class Xith3DCamera
{
  private Vector3f  upVector          = new Vector3f(0f, 0f, 1f),
                    tempVector        = new Vector3f(0f, 0f,.0f),
                    viewVector        = new Vector3f(0f, 1f,.5f),
                    axisVector        = new Vector3f(0f, 0f, 0f),
                    strafeVector      = new Vector3f(0f, 0f, 0f),
                    positionVector    = new Vector3f(0f, 0f,.0f),
                    moveCameraVector  = new Vector3f(0f, 0f, 0f);

  private boolean   keys[]            = null,
                    frameActive       = false;

  private Point     mousePosition     = null,
                    componentPosition = null;

  private Robot     mouseRobot        = null;

  private float     currentRotX       =   0,
                    cameraSpeed       =  50,
                    frameInterval     =   0;

  private long      frameTime         =   0;

  private int       screenWidth       =   0,
                    screenHeight      =   0;

  Xith3DCamera(boolean keys[]){

    this.keys = keys;
    frameTime = System.currentTimeMillis();

    Toolkit.getDefaultToolkit().addAWTEventListener( new EventListener(),
                                                     AWTEvent.KEY_EVENT_MASK        |
                                                     AWTEvent.MOUSE_EVENT_MASK      |
                                                     AWTEvent.MOUSE_MOTION_EVENT_MASK);
    try{
      mouseRobot = new Robot();
    }
    catch(Exception e){}
  }

  public void setScreenSize(int width, int height){
    screenWidth   = width;
    screenHeight  = height;
    mousePosition = new Point(width>>1,height>>1);
  }

  public void positionCamera(float xPosition, float yPosition, float zPosition,
                             float xView    , float yView    , float zView    ,
                             float xUpVector, float yUpVector, float zUpVector){
    positionVector.set( xPosition, yPosition, zPosition);
    viewVector.set(     xView    , yView    , zView    );
    upVector.set(       xUpVector, yUpVector, zUpVector);
  }

  public void moveCamera(float speed){
    moveCameraVector.sub(viewVector, positionVector);
    moveCameraVector.normalize();

    positionVector.scaleAdd(speed, moveCameraVector, positionVector);
    viewVector.scaleAdd(    speed, moveCameraVector, viewVector    );
  }

  public void setViewByMouse(){
    float angleY  = 0f,
          angleZ  = 0f;

    int   middleX = screenWidth  >> 1,
          middleY = screenHeight >> 1;

    if( (mousePosition.x == middleX) && (mousePosition.y == middleY) )
      return;

    angleY = (float)( (middleX - mousePosition.x) ) / 500.0f;
    angleZ = (float)( (middleY - mousePosition.y) ) / 500.0f;

    if(frameActive)
      mouseRobot.mouseMove(componentPosition.x + middleX, componentPosition.y + middleY);

    currentRotX -= angleZ;

    if(currentRotX > 1.0f)
      currentRotX = 1.0f;
    else
      if(currentRotX < -1.0f)
        currentRotX = -1.0f;
      else{
        axisVector.sub(  viewVector, positionVector);
        axisVector.cross(axisVector, upVector      );
        axisVector.normalize();
        rotateView(angleZ, axisVector.x, axisVector.y, axisVector.z);
        rotateView(angleY, 0, 1, 0);
      }
  }

  private void rotateView(float angle, float x, float y, float z){
    tempVector.sub(viewVector, positionVector);

    float cosTheta         = FastTrig.cos(angle),
          sinTheta         = FastTrig.sin(angle),
          oneMinusCosTheta = 1f     -   cosTheta;

    viewVector.x  = (cosTheta + oneMinusCosTheta * x * x)      * tempVector.x;
    viewVector.x += (oneMinusCosTheta * x * y - z * sinTheta)  * tempVector.y;
    viewVector.x += (oneMinusCosTheta * x * z + y * sinTheta)  * tempVector.z;

    viewVector.y  = (oneMinusCosTheta * x * y + z * sinTheta)  * tempVector.x;
    viewVector.y += (cosTheta + oneMinusCosTheta * y * y)      * tempVector.y;
    viewVector.y += (oneMinusCosTheta * y * z - x * sinTheta)  * tempVector.z;

    viewVector.z  = (oneMinusCosTheta * x * z - y * sinTheta)  * tempVector.x;
    viewVector.z += (oneMinusCosTheta * y * z + x * sinTheta)  * tempVector.y;
    viewVector.z += (cosTheta + oneMinusCosTheta * z * z)      * tempVector.z;

    viewVector.add(positionVector);
  }

  private void strafeCamera(float speed){
    positionVector.x += strafeVector.x * speed;
    positionVector.z += strafeVector.z * speed;

    viewVector.x     += strafeVector.x * speed;
    viewVector.z     += strafeVector.z * speed;
  }

  private void elevateCamera(float speed){
    positionVector.y += upVector.y*speed;
    viewVector.y     += upVector.y*speed;
  }

  private void checkForMovement(){
    float speed = cameraSpeed * frameInterval;
    if(keys[KeyEvent.VK_UP       ] || keys['W']) moveCamera(    speed);
    if(keys[KeyEvent.VK_DOWN     ] || keys['S']) moveCamera(   -speed);
    if(keys[KeyEvent.VK_LEFT     ] || keys['A']) strafeCamera( -speed);
    if(keys[KeyEvent.VK_RIGHT    ] || keys['D']) strafeCamera(  speed);
    if(keys[KeyEvent.VK_PAGE_UP  ] || keys['E']) elevateCamera( speed);
    if(keys[KeyEvent.VK_PAGE_DOWN] || keys['Q']) elevateCamera(-speed);
  }

  public void update(long currentTime){

    frameInterval   = ((float)(currentTime - frameTime))/500f;
    frameTime       = currentTime;

    strafeVector.sub(  viewVector  , positionVector);
    strafeVector.cross(strafeVector, upVector);
    strafeVector.normalize();

    setViewByMouse();
    checkForMovement();
  }

  public void look(TransformGroup group){
    group.getTransform().lookAt(new Vector3f(positionVector),
                                new Vector3f(viewVector    ),
                                new Vector3f(upVector      ));
  }

  public Vector3f getPositionVector() { return positionVector;}
  public Vector3f getStrafeVector()   { return strafeVector  ;}
  public Vector3f getViewVector()     { return viewVector    ;}
  public Vector3f getUpVector()       { return upVector      ;}

  public boolean  getFrameState(){
    return frameActive;
  }

  public void setComponentPosition(int x, int y){
    if(componentPosition == null)
      componentPosition = new Point();

    componentPosition.setLocation(x,y);
  }

  public void setFrameState(boolean state){
    frameActive = state;
  }

  public void mouseMoved(MouseEvent me){
     mousePosition = me.getPoint();
  }

  private class EventListener implements AWTEventListener{

    public void eventDispatched(AWTEvent event){
      if(event instanceof MouseEvent){
        MouseEvent e = (MouseEvent) event;
        switch(e.getID()){
          case MouseEvent.MOUSE_MOVED: mouseMoved(e); break;
        }
      }
    }
  }
}

