import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.nio.IntBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.media.opengl.glu.GLU;

import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.gl2.GLUT;
import com.jogamp.opengl.util.GLBuffers;
import com.jogamp.common.nio.Buffers;

/**
 * Diese Klasse soll eine Hilfe f�r meine Tutorials sein, so dass man sich erst einmal 
 * nicht mit der Initialisierung o.�. rum-�rgern muss ;)
 * 
 *  Viel Spa� damit!
 *  
 * @author Joerg Amelunxen - japr0.wordpress.com
 *
 */
public class JoglApp implements GLEventListener, KeyListener, MouseListener, MouseWheelListener, MouseMotionListener {

	private float rotx;
	private float roty;
	private float rotz;
	private float rotv;

	private int prevMouseX;
	private int prevMouseY;
	
	private int centerX; 
	private int centerY;
	
	private static final int mouseSpeed = 1;    
	private static final int keySpeed = 3;    
	private static final int rotVMax = 1;    
	private static final int rotVMin = -1;       
	
	private GLU glu;
	private GLUT glut;
	protected boolean listenersInitialized;
	protected String Name;
    protected int window_x;
    protected int window_y;
    protected GLCanvas canvas;
    protected float transx;
    protected float transy;
    protected float transz;
    
    private Vector3d rotYV;
    
    private double fov = 65;

    JoglApp(String Name_value, int x, int y){
    	// Setze interne Variablen
    	glu = new GLU();
    	glut = new GLUT();
    	listenersInitialized = false;
    	Name = Name_value;
    	window_x = x;
    	window_y = y;
    	rotx = 0;
    	roty = 0;
    	rotz = 0;
    	rotv = 0;
    	transx = 0;
    	transy = 0;
    	transz = 0;
    	
    	// Starte App
    	// Suche passendens Profil und setze es
    	GLProfile glp = GLProfile.getDefault();
        GLCapabilities caps = new GLCapabilities(glp);
        canvas = new GLCanvas(caps);

        // Erstelle neuen Frame und bette die Zeichenfl�che ein
        Frame frame = new Frame(Name);
        frame.setSize(window_x, window_y);
        frame.add(canvas);
        frame.setVisible(true);

        // Erstelle einen Window Listener und sorge f�r korrektes
        // schlie�en des Programmes
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        // Setze den Eventlistener
        canvas.addGLEventListener(this);
        canvas.addMouseWheelListener(this);

        FPSAnimator animator = new FPSAnimator(canvas, 60);
        animator.add(canvas);
        animator.start();	
    }

    /**
     * Diese Methode wird einmal pro Frame aufgerufen und k�mmert sich um 
     * die Berechnungen und das Zeichnen
     * 
     * @param GLAutoDrawable drawable
     */
    public void display(GLAutoDrawable drawable) {
        onFrameMath(drawable);
        render(drawable);
    }

    @Override
    public void dispose(GLAutoDrawable drawable) {
    }

    @Override
    public void init(GLAutoDrawable drawable) {
		GL2 gl = drawable.getGL().getGL2();
		System.out.println("##############< Info >#################");
		System.out.println("JoglApp from japr0.wordpress.com");
		System.out.println("GL_VENDOR: " + gl.glGetString(GL2.GL_VENDOR));
		System.out.println("GL_RENDERER: " + gl.glGetString(GL2.GL_RENDERER));
		System.out.println("GL_VERSION: " + gl.glGetString(GL2.GL_VERSION));
		System.out.println("##############</Info >#################");

		// Listener nur einmal initialisieren !
		if (!listenersInitialized)
		{
			listenersInitialized = true;
			canvas.addMouseListener(this);
			canvas.addMouseMotionListener(this);
			canvas.addKeyListener(this);
		}
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    	
		rotv = 0;
		
        final Rectangle r = canvas.getParent().getBounds();
        final Point p = canvas.getParent().getLocationOnScreen();        
		
		centerX = r.x + p.x + width / 2;        
		centerY = r.y + p.y + height / 2;     
    	
		GL2 gl = drawable.getGL().getGL2();
		// Setze einen passenden Viewport
		gl.glViewport(0, 0, window_x, window_y);
		//Projektionsmatrix 'leeren'
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(fov, (float) window_x / window_y, 0, 100);
		// Modelview 'leeren'
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
    }

    /**
     * Diese Methode macht die Berechnungen pro Frame
     * 
     * @param GLAutodrawable drawable
     */
    private void onFrameMath(GLAutoDrawable drawable) {
    	
    	this.applyMovement(drawable.getGL().getGL2());
    	this.changeFoV(drawable.getGL().getGL2());
    }

    /**
     * Diese Methode zeichnet ein Koordinatensystem von min bis max
     * 
     * @param GL2 gl, float min, float max 
     */
	public void print_koordinates(GL2 gl, float min, float max){
		gl.glBegin(GL.GL_LINES);
		
		// x Achse 
		gl.glVertex3f(min,0f,0f);
		gl.glVertex3f(max,0f,0f);
		
		// y Achse
		gl.glVertex3f(0f,min,0f);
		gl.glVertex3f(0f,max,0f);
		
		// z Achse
		gl.glVertex3f(0f,0f,min);
		gl.glVertex3f(0f,0f,max);
		
		gl.glEnd();
		
		// 1er Teilstriche
		for( float i = min; i < max; i++ ){
			gl.glBegin(GL.GL_LINES);		
			// Striche auf der X - Achse
			gl.glColor3d(1.0, 0.0, 0.0);
			gl.glVertex3f(i,	0f,		0.2f);
			gl.glVertex3f(i,	0f,		-0.2f);	
			gl.glVertex3f(i,	0.2f,	0f);
			gl.glVertex3f(i,	-0.2f,	0f);
			
			// Striche auf der Y - Achse
			gl.glColor3d(0.0, 1.0, 0.0);
			gl.glVertex3f(0.2f	,i	,	0f);
			gl.glVertex3f(-0.2f	,i	,	0f);	
			gl.glVertex3f(0f	,i	,	0.2f);
			gl.glVertex3f(0f	,i	,	-0.2f);

			// Striche auf der Z - Achse
			gl.glColor3d(0.0, 0.0, 1.0);
			gl.glVertex3f(0.2f	,0	,	i);
			gl.glVertex3f(-0.2f	,0	,	i);	
			gl.glVertex3f(0f	,0.2f	,	i);
			gl.glVertex3f(0f	,-0.2f	,	i);
			
			gl.glEnd();
		}
		
		// 0.1er Teilstriche
		for( float i = min; i < max; i+=0.1f ){
			gl.glBegin(GL.GL_LINES);		
			// Striche auf der X - Achse
			gl.glColor3d(1.0, 0.0, 0.0);
			gl.glVertex3f(i,	0f,		0.02f);
			gl.glVertex3f(i,	0f,		-0.02f);	
			gl.glVertex3f(i,	0.02f,	0f);
			gl.glVertex3f(i,	-0.02f,	0f);
			
			// Striche auf der Y - Achse
			gl.glColor3d(0.0, 1.0, 0.0);
			gl.glVertex3f(0.02f	,i	,	0f);
			gl.glVertex3f(-0.02f,i	,	0f);	
			gl.glVertex3f(0f	,i	,	0.02f);
			gl.glVertex3f(0f	,i	,	-0.02f);
			
			// Striche auf der Z - Achse
			gl.glColor3d(0.0, 0.0, 1.0);
			gl.glVertex3f(0.02f	,0	,	i);
			gl.glVertex3f(-0.02f,0	,	i);	
			gl.glVertex3f(0f	,0.02f	,	i);
			gl.glVertex3f(0f	,-0.02f	,	i);
			gl.glEnd();
			

			gl.glColor3d(1.0, 1.0, 1.0);
		}
	}
	
    private void render(GLAutoDrawable drawable) {
        GL2 gl = drawable.getGL().getGL2();

        gl.glClear(GL.GL_COLOR_BUFFER_BIT);

        // Zeichne ein Koordinatensystem
        print_koordinates(gl, -10 , 10);
    }

    /**
     * Diese Methode �berpr�ft die Mausbewegungen ( ziehen der Maus )
     * 
     * @param MouseEvent e
     */
	public void mouseDragged(MouseEvent e) {
		
		//Boolean value = null;

        //if ((value = keys.get(space)) != null && value == true) {
		/*
			rotz -= (centerX - e.getXOnScreen()) / 1000.0 * mouseSpeed;
            rotv -= (centerY - e.getYOnScreen()) / 1000.0 * mouseSpeed;

            if (rotv > rotVMax) {

				rotv = rotVMax;
			}

			if (rotv < rotVMin) {

				rotv = rotVMin;
			}

			rotx = (float) Math.cos(roty) * rotv;            
			roty = (float) Math.sin(roty) * rotv;
		//*/         
		//} 
		
		// aktuelle Koordinaten
		//*
		int x = e.getX();
		int y = e.getY();
		int z = e.getY();
		
		Dimension size = e.getComponent().getSize();

		// Linke MT -> rotieren
		if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0)
		{
			float thetaY = 360f * ((float) (x - prevMouseX) / (float) size.width);
			float thetaX = 360f * ((float) (y - prevMouseY) / (float) size.height);
			float thetaZ = 360f * ((float) (z - prevMouseY) / (float) size.height);
			
			// Vorg�nger setzen
			prevMouseX = x;
			prevMouseY = y;
			// Rotation anwenden
			
			//rotation around the y axis
			roty += thetaY;
									
			//roration around the x axis
			double xCosinus = (0.5 * (Math.cos(2 * roty - Math.PI / 2.0) + 1));
			rotx += thetaX;// * (xCosinus);
			
			//rotation around the z axis
			double zSinus = (0.5 * (Math.sin(2 * roty - Math.PI / 2.0) + 1));
			//rotz += thetaZ * (zSinus);
		}
		
		// Rechte MT -> bewegen
		if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
		{
			float thetaX = 2f * ((float) (x - prevMouseX) / (float) size.width); 
			float thetaY = 2f * ((float) (prevMouseY - y) / (float) size.height);
			prevMouseX = x;
			prevMouseY = y;
			transx += thetaX;
			transy += thetaY;
		}
		//*/
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mousePressed(MouseEvent e) {
		prevMouseX = e.getX();
		prevMouseY = e.getY();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

    /**
     * implements the mouseWheelMoved method of the
     * MouseWheelListener interface
     * 
     * @param evt mouse wheel movement that fires the mouseWheelEvent 
     */
	public void mouseWheelMoved(MouseWheelEvent evt) {

        double change = 2.0;// / 30.0;
        int notches = evt.getWheelRotation();
        double upperBound = 95;
        double lowerBound = 35;
        Point evtPoint = evt.getPoint();
        Component evtSource = (Component) evt.getSource();
        
        if(evtSource.contains(evtPoint)) {
        	
	        if(notches > 0) {
	
	            //zoom out
	            fov += change;
	        }
	        else {
	
	            //zoom in
	            fov -= change;
	        }
	
	        if(fov < lowerBound) {
	
	            fov = lowerBound;
	        }
	        else if(fov > upperBound) {
	
	            fov = upperBound;
	        }
        }
    }

	@Override
	public void keyPressed(KeyEvent e) {
		if (e.getKeyCode() == KeyEvent.VK_UP){
			transz -= 0.2f;
		}
		else if (e.getKeyCode() == KeyEvent.VK_DOWN){
			transz += 0.2f;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	protected void changeFoV(GL2 gl) {
    	    	
		// Setze einen passenden Viewport
		gl.glViewport(0, 0, window_x, window_y);
		//Projektionsmatrix 'leeren'
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		glu.gluPerspective(fov, (float) window_x / window_y, 0, 100);
		// Modelview 'leeren'
		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}
	
	protected void applyMovement(GL2 gl) {
				
		IntBuffer buffer = com.jogamp.common.nio.Buffers.newDirectIntBuffer(1);
		gl.glGetIntegerv(GL2.GL_MATRIX_MODE, buffer);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glTranslatef(transx, transy, transz);
		gl.glRotatef(rotx, 1f, 0f, 0f);
		gl.glRotatef(roty, 0f, 1f, 0f);
		gl.glRotatef(rotz, 0f, 0f, 1f);
		gl.glMatrixMode(buffer.get(0));
		
		this.resetAllVars();
	}
	
	public void resetAllVars() {
		
		transx = 0f;
		transy = 0f;
		transz = 0f;
		
		rotx = 0f;
		roty = 0f;
		rotz = 0f;
	}
}